com.marvelution.bamboo.plugins.sonar.Sonar Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bamboo-sonar-plugin Show documentation
Show all versions of bamboo-sonar-plugin Show documentation
Bamboo plugin to integrate Sonar code quality platform with Bamboo
/*
* Licensed to Marvelution under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Marvelution licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.marvelution.bamboo.plugins.sonar;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.maven.model.Build;
import org.apache.maven.model.Model;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.WriterFactory;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.jetbrains.annotations.NotNull;
import com.atlassian.bamboo.build.Artifact;
import com.atlassian.bamboo.build.DefaultArtifact;
import com.atlassian.bamboo.builder.Maven2Builder;
import com.atlassian.bamboo.repository.RepositoryException;
import com.atlassian.bamboo.resultsummary.ExtendedBuildResultsSummary;
import com.atlassian.bamboo.v2.build.BuildContext;
import com.atlassian.bamboo.v2.build.BuildPlanDefinition;
/**
* Helper class for Sonar specific requirements, like:
*
* - generating a sonar pom file
* - generating sonar {@link Maven2Builder} command
*
*
* @author Mark Rekveld
*/
public final class Sonar {
private static final Logger LOGGER = Logger.getLogger(Sonar.class);
private static final Pattern ALTERNATE_POM_PATTERN = Pattern.compile("\\s*(-f|--file)\\s+");
public static final String SONAR_PROJECT_KEY = "custom.sonar.project.key";
public static final String SONAR_BUILD_STATE_KEY = "custom.sonar.build.state";
public static final String SONAR_ARTIFACT_LABEL = "Sonar Analysis Log";
public static final String SONAR_ARTIFACT_COPY_PATTERN = "sonar-log.log";
public static final String SONAR_ARTIFACT_SRC_DIRECTORY = "";
public static final Artifact SONAR_LOG_ARTIFACT =
new DefaultArtifact(SONAR_ARTIFACT_LABEL, SONAR_ARTIFACT_COPY_PATTERN, SONAR_ARTIFACT_SRC_DIRECTORY);
public static final String SONAR_SERVER = "custom.sonar.server";
public static final String BUILD_PLAN_SPECIFIC = "buildPlanSpecific";
public static final String SONAR_RUN = "custom.sonar.run";
public static final String SONAR_HOST_URL = "custom.sonar.host.url";
public static final String SONAR_HOST_USERNAME = "custom.sonar.host.username";
public static final String SONAR_HOST_PASSWORD = "custom.sonar.host.password";
public static final String SONAR_JDBC_URL = "custom.sonar.jdbc.url";
public static final String SONAR_JDBC_DRIVER = "custom.sonar.jdbc.driver";
public static final String SONAR_JDBC_USERNAME = "custom.sonar.jdbc.username";
public static final String SONAR_JDBC_PASSWORD = "custom.sonar.jdbc.password";
public static final Map SONAR_GOAL_PROPERTIES = new HashMap();
static {
SONAR_GOAL_PROPERTIES.put(SONAR_HOST_URL, "http://localhost:9000/");
SONAR_GOAL_PROPERTIES.put(SONAR_JDBC_URL, "");
SONAR_GOAL_PROPERTIES.put(SONAR_JDBC_DRIVER, "");
SONAR_GOAL_PROPERTIES.put(SONAR_JDBC_USERNAME, "sonar");
SONAR_GOAL_PROPERTIES.put(SONAR_JDBC_PASSWORD, "sonar");
}
public static final String SONAR_BUILD_JDK = "custom.sonar.build.jdk";
public static final String SONAR_BUILD_JDK_DEFAULT = "Reuse Build Defined JDK";
public static final String SONAR_ADD_ARGS = "custom.sonar.add.args";
public static final String SONAR_REUSE_REPORTS = "custom.sonar.reusereports";
public static final String SONAR_OVERRIDE_GLOBALS = "custom.sonar.override.globals";
public static final String SONAR_SKIP_ON_BUILD_FAILURE = "custom.sonar.skip.build.failure";
public static final String SONAR_SKIP_ON_MANUAL_BUILD = "custom.sonar.skip.manual.build";
public static final String SONAR_SKIP_ON_NO_CODE_CHANGES = "custom.sonar.skip.no.code.changes";
public static final String SONAR_FAILURE_BEHAVIOR = "custom.sonar.failure.behavior";
public static final String SONAR_IGNORE_BEHAVIOR = "ignore";
public static final String SONAR_FAIL_BEHAVIOR = "fail";
public static final String SONAR_LABEL_BEHAVIOR = "label";
public static final String[] SONAR_FAILURE_BEHAVIORS = {
SONAR_IGNORE_BEHAVIOR, SONAR_FAIL_BEHAVIOR, SONAR_LABEL_BEHAVIOR
};
public static final String SONAR_LIGHT = "custom.sonar.light.run";
public static final String SONAR_LIGHT_GROUPID = "custom.sonar.light.groupId";
public static final String SONAR_LIGHT_ARTIFACTID = "custom.sonar.light.artifactId";
public static final String SONAR_LIGHT_VERSION = "custom.sonar.light.version";
public static final String SONAR_LIGHT_NAME = "custom.sonar.light.name";
public static final String SONAR_LIGHT_DESCRIPTION = "custom.sonar.light.description";
public static final String SONAR_LIGHT_SOURCES = "custom.sonar.light.sources";
public static final String SONAR_LIGHT_TARGET = "custom.sonar.light.target";
public static final String SONAR_LIGHT_JDK = "custom.sonar.light.jdk";
public static final String SONAR_LIGHT_POM = "sonar-pom.xml";
public static final String BUILDER_HAS_TESTS = "testChecked";
public static final String BUILDER_TESTS_PATH = "testResultsDirectory";
public static final String CLOVER_EXISTS = "custom.clover.exists";
public static final String CLOVER_PATH = "custom.clover.path";
public static final String COBERTURA_EXISTS = "custom.cobertura.exists";
public static final String COBERTURA_PATH = "custom.cobertura.path";
/**
* Generate the Sonar Light pom.xml and write it to the given repository directory
*
* @param repositoryDirectory the source repository {@link File} directory
* @param customConfiguration the custom configuration of the Build
* @throws IOException in case the generated sonar-pom.xml file cannot be written to the repository directory
*/
public void generateSonarLightPom(@NotNull File repositoryDirectory,
@NotNull Map customConfiguration) throws IOException {
final Model model = new Model();
model.setModelVersion("4.0.0");
model.setGroupId(customConfiguration.get(SONAR_LIGHT_GROUPID));
model.setArtifactId(customConfiguration.get(SONAR_LIGHT_ARTIFACTID));
if (StringUtils.isNotBlank(customConfiguration.get(SONAR_LIGHT_VERSION))) {
model.setVersion(customConfiguration.get(SONAR_LIGHT_VERSION));
} else {
model.setVersion("1.0");
}
model.setName(customConfiguration.get(SONAR_LIGHT_NAME));
if (StringUtils.isNotBlank(customConfiguration.get(SONAR_LIGHT_DESCRIPTION))) {
model.setDescription(customConfiguration.get(SONAR_LIGHT_DESCRIPTION));
}
final Properties props = new Properties();
if (Boolean.parseBoolean((String) customConfiguration.get(SONAR_REUSE_REPORTS))) {
props.setProperty("sonar.dynamicAnalysis", "reusereports");
}
for (Entry entry : customConfiguration.entrySet()) {
if (entry.getKey().endsWith(BUILDER_HAS_TESTS) && Boolean.parseBoolean(entry.getValue())) {
final String pathKey = entry.getKey().replace(BUILDER_HAS_TESTS, BUILDER_TESTS_PATH);
props.setProperty("sonar.surefire.reportPath", customConfiguration.get(pathKey));
break;
}
}
if (Boolean.parseBoolean((String) customConfiguration.get(COBERTURA_EXISTS))) {
props.setProperty("sonar.cobertura.reportPath", customConfiguration.get(COBERTURA_PATH));
}
if (Boolean.parseBoolean((String) customConfiguration.get(CLOVER_EXISTS))) {
props.setProperty("sonar.clover.reportPath", customConfiguration.get(CLOVER_PATH));
}
model.setProperties(props);
model.setBuild(new Build());
final List sourceDirectories = Arrays.asList(customConfiguration.get(SONAR_LIGHT_SOURCES).split(", "));
model.getBuild().setSourceDirectory(sourceDirectories.get(0));
model.getBuild().setOutputDirectory(customConfiguration.get(SONAR_LIGHT_TARGET));
if (sourceDirectories.size() > 1) {
final Plugin sourcesPlugin = new Plugin();
sourcesPlugin.setGroupId("org.codehaus.mojo");
sourcesPlugin.setArtifactId("build-helper-maven-plugin");
sourcesPlugin.setVersion("1.1");
final PluginExecution execution = new PluginExecution();
execution.setId("add-source");
execution.setPhase("generate-sources");
execution.setGoals(Collections.singletonList("add-source"));
final Xpp3Dom configuration = new Xpp3Dom("configuration");
final Xpp3Dom sources = new Xpp3Dom("sources");
for (int i = 1; i < sourceDirectories.size(); i++) {
final Xpp3Dom source = new Xpp3Dom("source");
source.setValue(sourceDirectories.get(i));
sources.addChild(source);
}
configuration.addChild(sources);
execution.setConfiguration(configuration);
sourcesPlugin.getExecutions().add(execution);
model.getBuild().getPlugins().add(sourcesPlugin);
}
String jdkVersion = "1.5";
if (StringUtils.isNotBlank(customConfiguration.get(SONAR_LIGHT_JDK))) {
jdkVersion = customConfiguration.get(SONAR_LIGHT_JDK);
}
final Plugin compiler = new Plugin();
compiler.setArtifactId("maven-compiler-plugin");
final Xpp3Dom configuration = new Xpp3Dom("configuration");
final Xpp3Dom source = new Xpp3Dom("source");
source.setValue(jdkVersion);
configuration.addChild(source);
final Xpp3Dom target = new Xpp3Dom("target");
target.setValue(jdkVersion);
configuration.addChild(target);
compiler.setConfiguration(configuration);
model.getBuild().getPlugins().add(compiler);
final MavenXpp3Writer pomWriter = new MavenXpp3Writer();
Writer fileWriter = null;
try {
fileWriter = WriterFactory.newXmlWriter(new File(repositoryDirectory, SONAR_LIGHT_POM));
pomWriter.write(fileWriter, model);
} finally {
IOUtil.close(fileWriter);
}
}
/**
* Generate the Sonar goals and arguments for the {@link Maven2Builder}
*
* @param configuration the {@link Map} containing the custom configuration of the current Build Plan
* @return the Sonar goals and arguments for the {@link Maven2Builder}
*/
@NotNull
public String generateSonarGoals(@NotNull Map configuration) {
final StringBuilder goalsBuilder = new StringBuilder();
goalsBuilder.append("-B");
if (Boolean.parseBoolean((String) configuration.get(SONAR_LIGHT))) {
goalsBuilder.append(" --file " + SONAR_LIGHT_POM);
}
for (Entry property : SONAR_GOAL_PROPERTIES.entrySet()) {
final String propertyName = property.getKey().substring("custom.".length());
if (StringUtils.isNotBlank(configuration.get(property.getKey()))) {
goalsBuilder.append(" -D" + propertyName + "=" + configuration.get(property.getKey()));
} else if (StringUtils.isNotBlank(property.getValue())) {
goalsBuilder.append(" -D" + propertyName + "=" + property.getValue());
}
}
if (StringUtils.isNotBlank(configuration.get(SONAR_ADD_ARGS))) {
goalsBuilder.append(" " + configuration.get(SONAR_ADD_ARGS));
}
goalsBuilder.append(" sonar:sonar");
LOGGER.debug("Generated provided configuration to Sonar goals: " + goalsBuilder.toString());
return goalsBuilder.toString();
}
/**
* Copy the {@link BambooSonarServer} configuration to the given {@link BuildPlanDefinition}
*
* @param server the {@link BambooSonarServer} configuration to copy
* @param buildDefinition the {@link BuildPlanDefinition} to copy the configuration to
*/
public void copySonarServerToBuildPlanDefinition(BambooSonarServer server, BuildPlanDefinition buildDefinition) {
buildDefinition.getCustomConfiguration().put(SONAR_HOST_URL, server.getHost());
buildDefinition.getCustomConfiguration().put(SONAR_JDBC_URL, server.getDatabaseUrl());
buildDefinition.getCustomConfiguration().put(SONAR_JDBC_DRIVER, server.getDatabaseDriver());
buildDefinition.getCustomConfiguration().put(SONAR_JDBC_USERNAME, server.getDatabaseUsername());
buildDefinition.getCustomConfiguration().put(SONAR_JDBC_PASSWORD, server.getDatabasePassword());
String planAddArgs = buildDefinition.getCustomConfiguration().get(SONAR_ADD_ARGS);
// Fix for Issue MARVBAMBOOSONAR-15: the server additional arguments should only be added if they are not in
// the plan additional arguments
if (StringUtils.isBlank(planAddArgs)) {
planAddArgs = "";
}
if (!planAddArgs.contains(server.getAdditionalArguments())) {
buildDefinition.getCustomConfiguration().put(SONAR_ADD_ARGS,
(planAddArgs + " " + server.getAdditionalArguments()).trim());
}
}
/**
* Get the Sonar project key from the {@link BuildContext}
*
* @param buildContext the {@link BuildContext} to get the project key from
* @return the Sonar project key
*/
public String getSonarProjectKeyFromBuildContext(BuildContext buildContext) {
final MavenXpp3Reader reader = new MavenXpp3Reader();
String filename = null;
if (buildContext.getBuildPlanDefinition().getBuilderV2() instanceof Maven2Builder) {
final Maven2Builder builder = (Maven2Builder) buildContext.getBuildPlanDefinition().getBuilderV2();
filename = getPomFileNewFromGoals(builder.getGoal());
} else {
filename = SONAR_LIGHT_POM;
}
FileInputStream fileInput = null;
try {
if (StringUtils.isBlank(filename)) {
filename = "pom.xml";
}
fileInput = new FileInputStream(new File(buildContext.getBuildPlanDefinition()
.getRepositoryV2().getSourceCodeDirectory(buildContext.getPlanKey()), filename));
final Model model = reader.read(fileInput);
return model.getGroupId() + ":" + model.getArtifactId();
} catch (FileNotFoundException e) {
LOGGER.error("Could not find POM file: " + filename, e);
} catch (IOException e) {
LOGGER.error("Failed to read POM file: " + filename, e);
} catch (XmlPullParserException e) {
LOGGER.error("Failed to parse POM file: " + filename, e);
} catch (RepositoryException e) {
LOGGER.error("Failed to get the Source repository for the Build Plan", e);
} finally {
IOUtils.closeQuietly(fileInput);
}
return "";
}
/**
* Get the SonarProject key from the {@link com.atlassian.bamboo.build.Build} results data
*
* @param build the {@link com.atlassian.bamboo.build.Build} to get the Sonar Project key from
* @return the Sonar Project key, may be blank
*/
@SuppressWarnings("unchecked")
public String getSonarProjectFromBuildResultsData(com.atlassian.bamboo.build.Build build) {
String sonarProject = "";
for (final ExtendedBuildResultsSummary result : (List)
build.getBuildResultSummaries()) {
if (StringUtils.isNotBlank(result.getCustomBuildData().get(Sonar.SONAR_PROJECT_KEY))) {
sonarProject = result.getCustomBuildData().get(Sonar.SONAR_PROJECT_KEY);
}
}
return sonarProject;
}
/**
* Get the POM filename from the goals given
*
* @param goals the Maven 2 goals to get the POM filename from
* @return the POM filename, may be null
in which case the default pom.xml
is used
*/
public String getPomFileNewFromGoals(String goals) {
if (goals == null) {
return null;
}
final Matcher matcher = ALTERNATE_POM_PATTERN.matcher(goals);
if (matcher.find()) {
final String[] options = goals.split("\\s");
for (int i = 0; i < options.length; i++) {
if ("-f".equals(options[i]) || "--file".equals(options[i])) {
return options[i + 1];
}
}
}
return null;
}
}