/*
* Sonar C# Plugin :: Core
* Copyright (C) 2010 Jose Chillan, Alexandre Victoor and SonarSource
* dev@sonar.codehaus.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
package org.sonar.plugins.csharp.core;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.bootstrap.ProjectBuilder;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.bootstrap.ProjectReactor;
import org.sonar.api.utils.SonarException;
import org.sonar.dotnet.tools.commons.DotNetToolsException;
import org.sonar.dotnet.tools.commons.visualstudio.ModelFactory;
import org.sonar.dotnet.tools.commons.visualstudio.SourceFile;
import org.sonar.dotnet.tools.commons.visualstudio.VisualStudioProject;
import org.sonar.dotnet.tools.commons.visualstudio.VisualStudioSolution;
import org.sonar.plugins.csharp.api.CSharpConfiguration;
import org.sonar.plugins.csharp.api.CSharpConstants;
import org.sonar.plugins.csharp.api.MicrosoftWindowsEnvironment;
/**
* Project Builder created and executed once per build to override the project definition, based on the Visual Studio files found in the
* sources.
*/
public class VisualStudioProjectBuilder extends ProjectBuilder {
private static final Logger LOG = LoggerFactory.getLogger(VisualStudioProjectBuilder.class);
private CSharpConfiguration configuration;
private MicrosoftWindowsEnvironment microsoftWindowsEnvironment;
/**
* Creates a new {@link VisualStudioProjectBuilder}
*
* @param reactor
* the reactor
* @param configuration
* the shared C# configuration
* @param microsoftWindowsEnvironment
* the shared Microsoft Windows Environment
*/
public VisualStudioProjectBuilder(ProjectReactor reactor, CSharpConfiguration configuration,
MicrosoftWindowsEnvironment microsoftWindowsEnvironment) {
super(reactor);
this.configuration = configuration;
this.microsoftWindowsEnvironment = microsoftWindowsEnvironment;
}
@Override
protected void build(ProjectReactor reactor) {
if ("cs".equals(configuration.getString("sonar.language", ""))) {
LOG.debug("Executing VisualStudioProjectBuilder");
ProjectDefinition root = reactor.getRoot();
// First, read all the plugin configuration details related to MS Windows
retrieveMicrosoftWindowsEnvironmentConfig();
// Then create the Visual Studio Solution object from the ".sln" file
createVisualStudioSolution(root.getBaseDir());
// And finally create the Sonar projects definition
createMultiProjectStructure(root);
// lock the MicrosoftWindowsEnvironment object so that nobody can modify it afterwards
microsoftWindowsEnvironment.lock();
}
}
private void createMultiProjectStructure(ProjectDefinition root) {
VisualStudioSolution currentSolution = microsoftWindowsEnvironment.getCurrentSolution();
root.resetSourceDirs();
LOG.debug("- Root Project: {}", root.getName());
String workDir = root.getWorkDir().getAbsolutePath().substring(root.getBaseDir().getAbsolutePath().length() + 1);
microsoftWindowsEnvironment.setWorkingDirectory(workDir);
Properties rootProps = enhanceRootProperties(root);
for (VisualStudioProject vsProject : currentSolution.getProjects()) {
String projectKey = StringUtils.substringBefore(root.getKey(), ":") + ":" + StringUtils.deleteWhitespace(vsProject.getName());
if (projectKey.equals(root.getKey())) {
throw new SonarException("The solution and one of its projects have the same key ('" + projectKey
+ "'). Please set a unique 'sonar.projectKey' for the solution.");
}
ProjectDefinition subProject = ProjectDefinition.create((Properties) rootProps.clone()).setBaseDir(vsProject.getDirectory())
.setWorkDir(new File(vsProject.getDirectory(), workDir)).setKey(projectKey).setVersion(root.getVersion())
.setName(vsProject.getName()).addContainerExtension(microsoftWindowsEnvironment);
if (vsProject.isTest()) {
subProject.setTestDirs(".");
for (SourceFile sourceFile : vsProject.getSourceFiles()) {
subProject.addTestFiles(sourceFile.getFile());
}
} else {
subProject.setSourceDirs(".");
for (SourceFile sourceFile : vsProject.getSourceFiles()) {
subProject.addSourceFiles(sourceFile.getFile());
}
}
LOG.debug(" - Adding Sub Project => {}", subProject.getName());
root.addSubProject(subProject);
}
}
protected Properties enhanceRootProperties(ProjectDefinition root) {
Properties props = root.getProperties();
// Handling encoding
if (StringUtils.isBlank(props.getProperty("sonar.sourceEncoding"))) {
LOG.info("'sonar.sourceEncoding' has not been defined: setting it to default value 'UTF-8'.");
props.setProperty("sonar.sourceEncoding", "UTF-8");
}
// Handling exclusions
if (configuration.getBoolean(CSharpConstants.EXCLUDE_GENERATED_CODE_KEY, CSharpConstants.EXCLUDE_GENERATED_CODE_DEFVALUE)) {
String exclusions = props.getProperty("sonar.exclusions", "");
StringBuilder newExclusions = new StringBuilder(exclusions);
if ( !StringUtils.isEmpty(exclusions)) {
newExclusions.append(",");
}
newExclusions.append(CSharpConstants.DEFAULT_FILES_TO_EXCLUDE);
props.setProperty("sonar.exclusions", newExclusions.toString());
}
return props;
}
private void retrieveMicrosoftWindowsEnvironmentConfig() {
// .NET version
String dotnetVersion = configuration.getString(CSharpConstants.DOTNET_VERSION_KEY, CSharpConstants.DOTNET_VERSION_DEFVALUE);
microsoftWindowsEnvironment.setDotnetVersion(dotnetVersion);
// .NET SDK folder
File dotnetSdkDirectory = new File(configuration.getString(CSharpConstants.getDotnetSdkDirKey(dotnetVersion),
CSharpConstants.getDotnetSdkDirDefaultValue(dotnetVersion)));
if ( !dotnetSdkDirectory.isDirectory()) {
throw new SonarException("The following .NET SDK directory does not exist, please check your plugin configuration: "
+ dotnetSdkDirectory.getPath());
} else {
microsoftWindowsEnvironment.setDotnetSdkDirectory(dotnetSdkDirectory);
}
// Silverlight version
String silverlightVersion = configuration.getString(CSharpConstants.SILVERLIGHT_VERSION_KEY,
CSharpConstants.SILVERLIGHT_VERSION_DEFVALUE);
microsoftWindowsEnvironment.setSilverlightVersion(silverlightVersion);
// Silverlight folder
String defaultSilverlightPath = CSharpConstants.getSilverlightDirDefaultValue(silverlightVersion);
String silverlightPath = configuration.getString(CSharpConstants.getSilverlightDirKey(silverlightVersion), defaultSilverlightPath);
File silverlightDirectory = new File(silverlightPath);
if (defaultSilverlightPath.equals(silverlightPath)) {
// default value used, no validity check
LOG.debug("Default silverlight path will be used");
} else if ( !silverlightDirectory.isDirectory()) {
throw new SonarException("The following silverlight SDK directory does not exist, please check your plugin configuration: "
+ silverlightDirectory.getPath());
}
microsoftWindowsEnvironment.setSilverlightDirectory(silverlightDirectory);
}
private void createVisualStudioSolution(File baseDir) {
File slnFile = findSlnFile(baseDir);
if (slnFile == null) {
throw new SonarException("No valid '.sln' file could be found. Please read the previous log messages to know more.");
}
LOG.info("The following 'sln' file has been found and will be used: " + slnFile.getAbsolutePath());
try {
ModelFactory.setTestProjectNamePattern(configuration.getString(CSharpConstants.TEST_PROJET_PATTERN_KEY,
CSharpConstants.TEST_PROJET_PATTERN_DEFVALUE));
VisualStudioSolution solution = ModelFactory.getSolution(slnFile);
microsoftWindowsEnvironment.setCurrentSolution(solution);
} catch (IOException e) {
throw new SonarException("Error occured while reading Visual Studio files.", e);
} catch (DotNetToolsException e) {
throw new SonarException("Error occured while reading Visual Studio files.", e);
}
}
private File findSlnFile(File baseDir) {
String slnFilePath = configuration.getString(CSharpConstants.SOLUTION_FILE_KEY, CSharpConstants.SOLUTION_FILE_DEFVALUE);
final File slnFile;
if (StringUtils.isEmpty(slnFilePath)) {
LOG.info("No '.sln' file found or specified: trying to find one...");
slnFile = searchForSlnFile(baseDir);
} else {
final File confSlnFile = new File(baseDir, slnFilePath);
if (confSlnFile.isFile()) {
slnFile = confSlnFile;
} else {
slnFile = null;
LOG.warn("The specified '.sln' path does not point to an existing file: " + confSlnFile.getAbsolutePath());
}
}
return slnFile;
}
private File searchForSlnFile(File baseDir) {
File slnFile = null;
@SuppressWarnings("unchecked")
Collection<File> foundSlnFiles = FileUtils.listFiles(baseDir, new String[] { "sln" }, false);
if (foundSlnFiles.isEmpty()) {
LOG.warn("No '.sln' file specified, and none found at the root of the project: " + baseDir.getAbsolutePath());
} else if (foundSlnFiles.size() > 1) {
LOG.warn("More than one '.sln' file found at the root of the project: please tell which one to use via the configuration ("
+ CSharpConstants.SOLUTION_FILE_KEY + ").");
} else {
slnFile = foundSlnFiles.iterator().next();
}
return slnFile;
}
}