/*
* .NET tools :: FxCop Runner
* 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.dotnet.tools.fxcop;
import java.io.File;
import java.util.Collection;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.utils.command.Command;
import org.sonar.dotnet.tools.commons.utils.FileFinder;
import org.sonar.dotnet.tools.commons.visualstudio.VisualStudioProject;
import org.sonar.dotnet.tools.commons.visualstudio.VisualStudioSolution;
import com.google.common.collect.Lists;
/**
* Class used to build the command line to run FxCop.
*/
public class FxCopCommandBuilder { // NOSONAR Not final, because can't be mocked otherwise
private static final Logger LOG = LoggerFactory.getLogger(FxCopCommandBuilder.class);
private static final int DEFAULT_TIMEOUT = 10;
private static final int MINUTES_TO_SECONDS = 60;
private VisualStudioSolution solution;
private VisualStudioProject vsProject;
private File fxCopExecutable;
private File fxCopConfigFile;
private File fxCopReportFile;
private File silverlightFolder;
private String[] assembliesToScan = new String[] {};
private String[] assemblyDependencyDirectories = new String[] {};
private boolean ignoreGeneratedCode;
private int timeoutMinutes = DEFAULT_TIMEOUT;
private String buildConfigurations = "Debug";
private FxCopCommandBuilder() {
}
/**
* Constructs a {@link FxCopCommandBuilder} object for the given Visual Studio project.
* @param solution
* the current VS solution
* @param project
* the VS project to analyse
*
* @return a FxCop builder for this project
*/
public static FxCopCommandBuilder createBuilder(VisualStudioSolution solution, VisualStudioProject project) {
FxCopCommandBuilder builder = new FxCopCommandBuilder();
builder.solution = solution;
builder.vsProject = project;
return builder;
}
/**
* Sets the executable
*
* @param fxCopExecutable
* the executable
* @return the current builder
*/
public FxCopCommandBuilder setExecutable(File fxCopExecutable) {
this.fxCopExecutable = fxCopExecutable;
return this;
}
/**
* Sets FxCop configuration file that must be used to perform the analysis. It is mandatory.
*
* @param fxCopConfigFile
* the file
* @return the current builder
*/
public FxCopCommandBuilder setConfigFile(File fxCopConfigFile) {
this.fxCopConfigFile = fxCopConfigFile;
return this;
}
/**
* Sets the report file to generate
*
* @param reportFile
* the report file
* @return the current builder
*/
public FxCopCommandBuilder setReportFile(File reportFile) {
this.fxCopReportFile = reportFile;
return this;
}
/**
* Sets the Silverlight folder
*
* @param silverlightFolder
* the Silverlight folder
* @return the current builder
*/
public FxCopCommandBuilder setSilverlightFolder(File silverlightFolder) {
this.silverlightFolder = silverlightFolder;
return this;
}
/**
* Sets the assemblies to scan if the information should not be taken from the VS configuration files.
*
* @param assembliesToScan
* the assemblies to scan
* @return the current builder
*/
public FxCopCommandBuilder setAssembliesToScan(String... assembliesToScan) {
this.assembliesToScan = assembliesToScan;
return this;
}
/**
* Sets the assembly dependencies directories if needed.
*
* @param assemblyDependencyDirectories
* the folders containing the dependencies
* @return the current builder
*/
public FxCopCommandBuilder setAssemblyDependencyDirectories(String... assemblyDependencyDirectories) {
this.assemblyDependencyDirectories = assemblyDependencyDirectories;
return this;
}
/**
* Sets the parameter that allows to ignore generated code
*
* @param ignoreGeneratedCode
* true to ignore generated code
* @return the current builder
*/
public FxCopCommandBuilder setIgnoreGeneratedCode(boolean ignoreGeneratedCode) {
this.ignoreGeneratedCode = ignoreGeneratedCode;
return this;
}
/**
* Sets the timeout (in minutes) used for the FxCop plugin.
*
* @param timeout
* the timeout
* @return the current builder
*/
public FxCopCommandBuilder setTimeoutMinutes(int timeout) {
this.timeoutMinutes = timeout;
return this;
}
/**
* Sets the build configurations. By default, it is "Debug".
*
* @param buildConfigurations
* the build configurations
* @return the current builder
*/
public FxCopCommandBuilder setBuildConfigurations(String buildConfigurations) {
this.buildConfigurations = buildConfigurations;
return this;
}
/**
* Transforms this command object into a array of string that can be passed to the CommandExecutor.
*
* @return the Command that represent the command to launch.
*/
public Command toCommand() throws FxCopException {
Collection<File> assemblyToScanFiles = findAssembliesToScan();
Collection<File> assemblyDependencyDirectoriesFiles
= FileFinder.findDirectories(solution, vsProject, assemblyDependencyDirectories);
validate(assemblyToScanFiles);
LOG.debug("- FxCop program : " + fxCopExecutable);
Command command = Command.create(fxCopExecutable.getAbsolutePath());
LOG.debug("- Project file : " + fxCopConfigFile);
command.addArgument("/p:" + fxCopConfigFile.getAbsolutePath());
LOG.debug("- Report file : " + fxCopReportFile);
command.addArgument("/out:" + fxCopReportFile.getAbsolutePath());
LOG.debug("- Scanned assemblies :");
for (File checkedAssembly : assemblyToScanFiles) {
LOG.debug(" o " + checkedAssembly);
command.addArgument("/f:" + checkedAssembly.getAbsolutePath());
}
LOG.debug("- Assembly dependencies :");
for (File assemblyDependencyDir : assemblyDependencyDirectoriesFiles) {
LOG.debug(" o " + assemblyDependencyDir);
command.addArgument("/d:" + assemblyDependencyDir.getAbsolutePath());
}
if (isSilverlightUsed()) {
if (silverlightFolder == null || !silverlightFolder.isDirectory()) {
throw new FxCopException("The following Silverlight directory does not exist, please check your plugin configuration: "
+ (silverlightFolder == null ? "NULL" : silverlightFolder.getPath()));
}
LOG.debug(" o [Silverlight] " + silverlightFolder.getAbsolutePath());
command.addArgument("/d:" + silverlightFolder.getAbsolutePath());
}
if (ignoreGeneratedCode) {
LOG.debug("- Ignoring generated code");
command.addArgument("/igc");
}
command.addArgument("/to:" + timeoutMinutes * MINUTES_TO_SECONDS);
command.addArgument("/gac");
if (isAspUsed()) {
command.addArgument("/aspnet");
}
return command;
}
private boolean isAspUsed() {
boolean isAspUsed = false;
if (vsProject != null) {
isAspUsed = vsProject.isWebProject();
} else if (solution != null) {
isAspUsed = solution.isAspUsed();
}
return isAspUsed;
}
private boolean isSilverlightUsed() {
return vsProject.isSilverlightProject();
}
private Collection<File> findAssembliesToScan() {
final Collection<File> assemblyFiles;
if (assembliesToScan.length == 0) {
LOG.debug("No assembly specified: will look into 'csproj' files to find which should be analyzed.");
assemblyFiles = Lists.newArrayList();
addProjectAssembly(assemblyFiles, vsProject);
} else {
// Some assemblies have been specified: let's analyze them
assemblyFiles = FileFinder.findFiles(solution, vsProject, assembliesToScan);
}
return assemblyFiles;
}
private void addProjectAssembly(Collection<File> assemblyFileList, VisualStudioProject visualStudioProject) {
Set<File> assemblies = visualStudioProject.getGeneratedAssemblies(buildConfigurations);
for (File assembly : assemblies) {
if (assembly != null && assembly.isFile()) {
LOG.debug(" - Found {}", assembly.getAbsolutePath());
assemblyFileList.add(assembly);
}
}
}
private void validate(Collection<File> assemblyToScanFiles) {
if (fxCopConfigFile == null || !fxCopConfigFile.exists()) {
throw new IllegalStateException("The FxCop configuration file does not exist.");
}
if (assemblyToScanFiles.isEmpty()) {
throw new IllegalStateException(
"No assembly to scan. Please check your project's FxCop plugin configuration ('sonar.fxcop.assemblies' property).");
}
}
}