package jetbrains.buildserver.sonarplugin;
import jetbrains.buildServer.RunBuildException;
import jetbrains.buildServer.agent.plugins.beans.PluginDescriptor;
import jetbrains.buildServer.agent.runner.*;
import jetbrains.buildServer.runner.JavaRunnerConstants;
import jetbrains.buildServer.util.OSType;
import jetbrains.buildServer.util.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FilenameFilter;
import java.util.*;
import static jetbrains.buildServer.util.OSType.WINDOWS;
/**
* Created by Andrey Titov on 4/3/14.
*
* SonarQube Runner wrapper process.
*/
public class SQRBuildService extends CommandLineBuildService {
private static final String BUNDLED_SQR_RUNNER_PATH = "sonar-qube-runner";
private static final String SQR_RUNNER_PATH_PROPERTY = "teamcity.tool.sonarquberunner";
@NotNull
private final PluginDescriptor myPluginDescriptor;
@NotNull
private final SonarProcessListener mySonarProcessListener;
@NotNull
private final OSType myOsType;
public SQRBuildService(@NotNull final PluginDescriptor pluginDescriptor,
@NotNull final SonarProcessListener sonarProcessListener,
@NotNull final OSType osType) {
myPluginDescriptor = pluginDescriptor;
mySonarProcessListener = sonarProcessListener;
myOsType = osType;
}
@NotNull
@Override
public ProgramCommandLine makeProgramCommandLine() throws RunBuildException {
final List<String> programArgs = composeSQRArgs(
getRunnerContext().getRunnerParameters(),
getBuild().getSharedConfigParameters()
);
final String jdkHome = getRunnerContext().getRunnerParameters().get(JavaRunnerConstants.TARGET_JDK_HOME);
if (jdkHome != null) {
getRunnerContext().addEnvironmentVariable("JAVA_HOME", jdkHome);
}
final String executablePath = getExecutablePath();
final SimpleProgramCommandLine cmd = new SimpleProgramCommandLine(getRunnerContext(), executablePath, programArgs);
getLogger().message("Starting SQR in " + executablePath);
for (String str : cmd.getArguments()) {
getLogger().message(str);
}
return cmd;
}
/**
* Composes SonarQube Runner arguments.
* @param runnerParameters Parameters to compose arguments from
* @param sharedConfigParameters Shared config parameters to compose arguments from
* @return List of arguments to be passed to the SQR
*/
private List<String> composeSQRArgs(@NotNull final Map<String, String> runnerParameters,
@NotNull final Map<String, String> sharedConfigParameters) {
final List<String> res = new LinkedList<String>();
final Map<String, String> allParameters = new HashMap<String, String>(runnerParameters);
allParameters.putAll(sharedConfigParameters);
final SQRParametersAccessor accessor = new SQRParametersAccessor(allParameters);
addSQRArg(res, "-Dproject.home", ".", myOsType);
addSQRArg(res, "-Dsonar.host.url", accessor.getHostUrl(), myOsType);
addSQRArg(res, "-Dsonar.jdbc.url", accessor.getJDBCUrl(), myOsType);
addSQRArg(res, "-Dsonar.jdbc.username", accessor.getJDBCUsername(), myOsType);
addSQRArg(res, "-Dsonar.jdbc.password", accessor.getJDBCPassword(), myOsType);
addSQRArg(res, "-Dsonar.projectKey", getProjectKey(accessor.getProjectKey()), myOsType);
addSQRArg(res, "-Dsonar.projectName", accessor.getProjectName(), myOsType);
addSQRArg(res, "-Dsonar.projectVersion", accessor.getProjectVersion(), myOsType);
addSQRArg(res, "-Dsonar.sources", accessor.getProjectSources(), myOsType);
addSQRArg(res, "-Dsonar.tests", accessor.getProjectTests(), myOsType);
addSQRArg(res, "-Dsonar.binaries", accessor.getProjectBinaries(), myOsType);
addSQRArg(res, "-Dsonar.java.binaries", accessor.getProjectBinaries(), myOsType);
addSQRArg(res, "-Dsonar.modules", accessor.getProjectModules(), myOsType);
addSQRArg(res, "-Dsonar.password", accessor.getPassword(), myOsType);
addSQRArg(res, "-Dsonar.login", accessor.getLogin(), myOsType);
final String additionalParameters = accessor.getAdditionalParameters();
if (additionalParameters != null) {
res.addAll(Arrays.asList(additionalParameters.split("\\n")));
}
final Set<String> collectedReports = mySonarProcessListener.getCollectedReports();
if (!collectedReports.isEmpty() && (accessor.getAdditionalParameters() == null || !accessor.getAdditionalParameters().contains("-Dsonar.junit.reportsPath"))) {
addSQRArg(res, "-Dsonar.dynamicAnalysis", "reuseReports", myOsType);
addSQRArg(res, "-Dsonar.junit.reportsPath", collectReportsPath(collectedReports, accessor.getProjectModules()), myOsType);
}
final String jacocoExecFilePath = sharedConfigParameters.get("teamcity.jacoco.coverage.datafile");
if (jacocoExecFilePath != null) {
final File file = new File(jacocoExecFilePath);
if (file.exists() && file.isFile() && file.canRead()) {
addSQRArg(res, "-Dsonar.java.coveragePlugin", "jacoco", myOsType);
addSQRArg(res, "-Dsonar.jacoco.reportPath", jacocoExecFilePath, myOsType);
}
}
return res;
}
protected static String getProjectKey(String projectKey) {
if (!StringUtil.isEmpty(projectKey)) {
projectKey = projectKey.replaceAll("[^\\w\\-.:]", "_");
}
return projectKey;
}
@Nullable
private String collectReportsPath(Set<String> collectedReports, String projectModules) {
StringBuilder sb = new StringBuilder();
final String[] modules = projectModules != null ? projectModules.split(",") : new String[0];
Set<String> filteredReports = new HashSet<String>();
for (String report : collectedReports) {
if (!new File(report).exists()) continue;
for (String module : modules) {
final int indexOf = report.indexOf(module);
if (indexOf > 0) {
report = report.substring(indexOf + module.length() + 1);
}
}
filteredReports.add(report);
}
for (String report : filteredReports) {
sb.append(report).append(',');
break; // At the moment sonar.junit.reportsPath doesn't accept several paths
}
return sb.length() > 0 ? sb.substring(0, sb.length() - 1) : null;
}
/**
* Adds argument only if it's value is not null
* @param argList Result list of arguments
* @param key Argument key
* @param value Argument value
* @param osType
*/
protected static void addSQRArg(@NotNull final List<String> argList, @NotNull final String key, @Nullable final String value, @NotNull final OSType osType) {
if (!Util.isEmpty(value)) {
final String paramValue = key + "=" + value;
argList.add(osType == WINDOWS ? StringUtil.doubleQuote(StringUtil.escapeQuotes(paramValue)) : paramValue);
// final String paramValue = key + "=" + doubleQuote(escapeQuotes(unquoteString(value)));
}
}
/**
* @return Classpath for SonarQube Runner
* @throws SQRJarException
*/
@NotNull
private String getClasspath() throws SQRJarException {
final File pluginJar[] = getSQRJar(myPluginDescriptor.getPluginRoot());
final StringBuilder builder = new StringBuilder();
for (final File file : pluginJar) {
builder.append(file.getAbsolutePath()).append(File.pathSeparatorChar);
}
return builder.substring(0, builder.length() - 1);
}
@NotNull
private String getExecutablePath() throws RunBuildException {
File sqrRoot = myPluginDescriptor.getPluginRoot();
final String path = getRunnerContext().getConfigParameters().get(SQR_RUNNER_PATH_PROPERTY);
File exec;
String execName = myOsType == WINDOWS ? "sonar-runner.bat" : "sonar-runner";
if (path != null) {
exec = new File(path + File.separatorChar + "bin" + File.separatorChar + execName);
} else {
exec = new File(sqrRoot, BUNDLED_SQR_RUNNER_PATH + File.separatorChar + "bin" + File.separatorChar + execName);
}
if (!exec.exists()) {
throw new RunBuildException("SonarQube executable doesn't exist: " + exec.getAbsolutePath());
}
if (!exec.isFile()) {
throw new RunBuildException("SonarQube executable is not a file: " + exec.getAbsolutePath());
}
return exec.getAbsolutePath();
}
/**
* @param sqrRoot SQR root directory
* @return SonarQube Runner jar location
* @throws SQRJarException
*/
@NotNull
private File[] getSQRJar(@NotNull final File sqrRoot) throws SQRJarException {
final String path = getRunnerContext().getConfigParameters().get(SQR_RUNNER_PATH_PROPERTY);
File baseDir;
if (path != null) {
baseDir = new File(path);
} else {
baseDir = new File(sqrRoot, BUNDLED_SQR_RUNNER_PATH);
}
final File libPath = new File(baseDir, "lib");
if (!libPath.exists()) {
throw new SQRJarException("SonarQube Runner lib path doesn't exist: " + libPath.getAbsolutePath());
} else if (!libPath.isDirectory()) {
throw new SQRJarException("SonarQube Runner lib path is not a directory: " + libPath.getAbsolutePath());
} else if (!libPath.canRead()) {
throw new SQRJarException("Cannot read SonarQube Runner lib path: " + libPath.getAbsolutePath());
}
final File[] jars = libPath.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith("jar");
}
});
if (jars.length == 0) {
throw new SQRJarException("No JAR files found in lib path: " + libPath);
}
return jars;
}
}