package org.jfrog.bamboo.task; import com.atlassian.bamboo.build.ErrorLogEntry; import com.atlassian.bamboo.build.logger.BuildLogger; import com.atlassian.bamboo.build.logger.interceptors.ErrorMemorisingInterceptor; import com.atlassian.bamboo.build.test.TestCollationService; import com.atlassian.bamboo.process.EnvironmentVariableAccessor; import com.atlassian.bamboo.process.ExternalProcessBuilder; import com.atlassian.bamboo.process.ProcessService; import com.atlassian.bamboo.task.TaskContext; import com.atlassian.bamboo.task.TaskException; import com.atlassian.bamboo.task.TaskResult; import com.atlassian.bamboo.task.TaskResultBuilder; import com.atlassian.bamboo.v2.build.BuildContext; import com.atlassian.bamboo.v2.build.agent.capability.CapabilityContext; import com.atlassian.spring.container.ContainerManager; import com.atlassian.utils.process.ExternalProcess; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.apache.log4j.Logger; import org.apache.tools.ant.types.Commandline; import org.jetbrains.annotations.NotNull; import org.jfrog.bamboo.builder.ArtifactoryBuildInfoPropertyHelper; import org.jfrog.bamboo.builder.BuilderDependencyHelper; import org.jfrog.bamboo.context.AbstractBuildContext; import org.jfrog.bamboo.context.Maven3BuildContext; import org.jfrog.bamboo.util.MavenPropertyHelper; import org.jfrog.bamboo.util.PluginProperties; import org.jfrog.bamboo.util.TaskUtils; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Map; /** * Invocation of the Maven 3 task * * @author Tomer Cohen */ public class ArtifactoryMaven3Task extends ArtifactoryTaskType { public static final String TASK_NAME = "maven3Task"; private static final Logger log = Logger.getLogger(ArtifactoryMaven3Task.class); private final ProcessService processService; private final EnvironmentVariableAccessor environmentVariableAccessor; private final CapabilityContext capabilityContext; private BuilderDependencyHelper dependencyHelper; private String mavenDependenciesDir; private String buildInfoPropertiesFile; private boolean activateBuildInfoRecording; public ArtifactoryMaven3Task(final ProcessService processService, final EnvironmentVariableAccessor environmentVariableAccessor, final CapabilityContext capabilityContext, TestCollationService testCollationService) { super(testCollationService, environmentVariableAccessor); this.processService = processService; this.environmentVariableAccessor = environmentVariableAccessor; this.capabilityContext = capabilityContext; dependencyHelper = new BuilderDependencyHelper("artifactoryMaven3Builder"); ContainerManager.autowireComponent(dependencyHelper); } @Override @NotNull public TaskResult execute(@NotNull TaskContext taskContext) throws TaskException { BuildLogger logger = getBuildLogger(taskContext); String artifactoryPluginVersion = getArtifactoryVersion(); logger.addBuildLogEntry("Bamboo Artifactory Plugin version: " + artifactoryPluginVersion); final ErrorMemorisingInterceptor errorLines = new ErrorMemorisingInterceptor(); logger.getInterceptorStack().add(errorLines); Maven3BuildContext mavenBuildContext = createBuildContext(taskContext); initEnvironmentVariables(mavenBuildContext); long serverId = mavenBuildContext.getArtifactoryServerId(); File rootDirectory = taskContext.getRootDirectory(); try { mavenDependenciesDir = extractMaven3Dependencies(rootDirectory, serverId, mavenBuildContext); } catch (IOException e) { mavenDependenciesDir = null; logger.addBuildLogEntry(new ErrorLogEntry( "Error occurred while preparing Artifactory Maven Runner dependencies. Build Info support is " + "disabled: " + e.getMessage())); log.error("Error occurred while preparing Artifactory Maven Runner dependencies. " + "Build Info support is disabled.", e); } if (StringUtils.isNotBlank(mavenDependenciesDir)) { ArtifactoryBuildInfoPropertyHelper propertyHelper = new MavenPropertyHelper(); propertyHelper.init(buildParamsOverrideManager, taskContext.getBuildContext()); buildInfoPropertiesFile = propertyHelper.createFileAndGetPath(mavenBuildContext, logger, environmentVariableAccessor.getEnvironment(taskContext), environmentVariableAccessor.getEnvironment(), artifactoryPluginVersion); if (StringUtils.isNotBlank(buildInfoPropertiesFile)) { activateBuildInfoRecording = true; } } String subDirectory = mavenBuildContext.getWorkingSubDirectory(); if (StringUtils.isNotBlank(subDirectory)) { rootDirectory = new File(rootDirectory, subDirectory); } List<String> command = getCommand(mavenBuildContext); String mavenHome = getMavenHome(mavenBuildContext); if (StringUtils.isBlank(mavenHome)) { log.error(logger.addErrorLogEntry("Maven home is not defined!")); return TaskResultBuilder.newBuilder(taskContext).failed().build(); } appendClassPathArguments(command, mavenHome); appendClassWorldsConfArgument(command, mavenHome); appendBuildInfoPropertiesArgument(command); appendMavenOpts(command, mavenBuildContext); addMavenHome(command, mavenHome); addMavenMultiModuleProjectPath(command, rootDirectory); command.add("org.codehaus.plexus.classworlds.launcher.Launcher"); appendGoals(command, mavenBuildContext); appendAdditionalMavenParameters(command, mavenBuildContext); log.debug("Running maven command: " + command.toString()); ExternalProcessBuilder processBuilder = new ExternalProcessBuilder().workingDirectory(rootDirectory).command(command).env(environmentVariables); try { ExternalProcess process = processService.createExternalProcess(taskContext, processBuilder); process.execute(); if (process.getHandler() != null && !process.getHandler().succeeded()) { String externalProcessOutput = getErrorMessage(process); logger.addBuildLogEntry(externalProcessOutput); log.debug("Process command error: " + externalProcessOutput); } return collectTestResults(mavenBuildContext, taskContext, process); } finally { taskContext.getBuildContext().getBuildResult().addBuildErrors(errorLines.getErrorStringList()); } } /** * Returns the path of the java executable of the select JDK * * @return Java bin path */ public String getExecutable(AbstractBuildContext context) throws TaskException { String jdkPath = getConfiguredJdkPath(buildParamsOverrideManager, context, capabilityContext); StringBuilder binPathBuilder = new StringBuilder(jdkPath); if (SystemUtils.IS_OS_WINDOWS) { binPathBuilder.append("bin").append(File.separator).append("java.exe"); } else { // IBM's AIX JDK has different locations String aixJdkLocation = "jre" + File.separator + "sh" + File.separator + "java"; File aixJdk = new File(binPathBuilder.toString() + aixJdkLocation); if (aixJdk.isFile()) { binPathBuilder.append(aixJdkLocation); } else { binPathBuilder.append("bin").append(File.separator).append("java"); } } String binPath = binPathBuilder.toString(); binPath = getCanonicalPath(binPath); return binPath; } private Maven3BuildContext createBuildContext(TaskContext context) { Map<String, String> combinedMap = Maps.newHashMap(); combinedMap.putAll(context.getConfigurationMap()); BuildContext parentBuildContext = context.getBuildContext().getParentBuildContext(); if (parentBuildContext != null) { Map<String, String> customBuildData = parentBuildContext.getBuildResult().getCustomBuildData(); combinedMap.putAll(customBuildData); } return new Maven3BuildContext(combinedMap); } private void addMavenHome(List<String> command, String mavenHome) { command.add(Commandline.quoteArgument("-Dmaven.home" + "=" + mavenHome)); } //Starting from Maven 3.3.3 private void addMavenMultiModuleProjectPath(List<String> command, File rootDirectory) { command.add(Commandline.quoteArgument("-Dmaven.multiModuleProjectDirectory" + "=" + rootDirectory.getPath())); } private List<String> getCommand(Maven3BuildContext mavenBuildContext) throws TaskException { List<String> command = Lists.newArrayList(); String executable = getExecutable(mavenBuildContext); if (StringUtils.isBlank(executable)) { log.error("No Maven executable found"); return command; } if (SystemUtils.IS_OS_WINDOWS) { command.add("cmd.exe"); command.add("/c"); command.add("call"); command.add(Commandline.quoteArgument(executable)); } else { command.add(Commandline.quoteArgument(executable)); } return command; } private void appendMavenOpts(List<String> arguments, Maven3BuildContext mavenBuildContext) { String mavenOpts = mavenBuildContext.getMavenOpts(); if (StringUtils.isNotBlank(mavenOpts)) { String[] mavenOptsToken = StringUtils.split(mavenOpts, " "); for (String opt : mavenOptsToken) { if (StringUtils.isNotBlank(opt)) { arguments.add(Commandline.quoteArgument(opt)); } } } } /** * Appends the maven classworlds classpath arguments to the command * * @param arguments Aggregated command arguments * @param mavenHomePath Path to Maven installation home */ private void appendClassPathArguments(List<String> arguments, String mavenHomePath) { arguments.add("-cp"); StringBuilder classPathBuilder = getPathBuilder(mavenHomePath).append("boot"); String mavenBootPath = classPathBuilder.toString(); File mavenBootFolder = new File(mavenBootPath); if (!mavenBootFolder.isDirectory()) { throw new IllegalStateException("Could not find the Maven lib directory in the expected path: " + mavenBootPath + "."); } String[] bootJars = mavenBootFolder.list(); for (String bootJar : bootJars) { if (StringUtils.startsWithIgnoreCase(bootJar, "plexus-classworlds") && StringUtils.endsWithIgnoreCase(bootJar, ".jar")) { classPathBuilder.append(File.separator).append(bootJar); String classPath = getCanonicalPath(classPathBuilder.toString()); arguments.add(Commandline.quoteArgument(classPath)); return; } } throw new IllegalStateException("Could not find plexus classworlds jar in " + mavenBootPath + "."); } private void appendGoals(List<String> arguments, Maven3BuildContext context) { String goals = context.getGoals(); if (context.releaseManagementContext.isActivateReleaseManagement()) { String altTasks = context.releaseManagementContext.getAlternativeTasks(); if (StringUtils.isNotBlank(altTasks)) { goals = altTasks; } } goals = getStringWithoutNewLines(goals); String[] goalArray = StringUtils.split(goals, " "); arguments.addAll(Arrays.asList(goalArray)); } /** * Appends the maven classworlds configuration file argument to the command * * @param arguments Aggregated command arguments * @param mavenHomePath Path to Maven installation home */ private void appendClassWorldsConfArgument(List<String> arguments, String mavenHomePath) { String originalConfPath = getPathBuilder(mavenHomePath).append("bin").append(File.separator).append("m2.conf"). toString(); String m2ConfPath; /** * Customize the classworlds conf to activate the build info recorder only if received a valid dependency * directory path */ if (activateBuildInfoRecording) { try { List m2ConfLines = FileUtils.readLines(new File(originalConfPath), "utf-8"); m2ConfLines.add("load " + mavenDependenciesDir + File.separator + "*.jar"); File tempM2Conf = File.createTempFile("artifactoryM2", "conf"); FileUtils.writeLines(tempM2Conf, m2ConfLines); m2ConfPath = tempM2Conf.getCanonicalPath(); } catch (IOException ioe) { throw new RuntimeException("Error occurred while writing Maven 3 customized m2.conf", ioe); } } else { m2ConfPath = originalConfPath; } arguments.add(Commandline.quoteArgument("-Dclassworlds.conf=" + m2ConfPath)); } private void appendBuildInfoPropertiesArgument(List<String> arguments) { if (activateBuildInfoRecording) { TaskUtils.appendBuildInfoPropertiesArgument(arguments, buildInfoPropertiesFile); } } /** * Extracts the Artifactory Maven 3 recorder and all the needed to dependencies * * @return Path of recorder and dependency jar folder if extraction succeeded. Null if not */ private String extractMaven3Dependencies(File rootDir, long artifactoryServerId, Maven3BuildContext mavenBuildContext) throws IOException { if (artifactoryServerId == -1) { return null; } return dependencyHelper.downloadDependenciesAndGetPath(rootDir, mavenBuildContext, PluginProperties.getPluginProperty(PluginProperties.MAVEN3_DEPENDENCY_FILENAME_KEY)); } private void appendAdditionalMavenParameters(List<String> arguments, Maven3BuildContext context) { String additionalParams = context.getAdditionalMavenParams(); if (StringUtils.isNotBlank(additionalParams)) { String formattedParams = getStringWithoutNewLines(additionalParams); String[] paramArray = StringUtils.split(formattedParams, " "); arguments.addAll(Arrays.asList(paramArray)); } String projectFile = context.getProjectFile(); if (StringUtils.isNotBlank(projectFile)) { arguments.addAll(Arrays.asList("-f", projectFile)); } } private String getMavenHome(Maven3BuildContext context) { return capabilityContext.getCapabilityValue("system.builder.maven." + context.getExecutable()); } private String getStringWithoutNewLines(String stringToModify) { return StringUtils.replaceChars(stringToModify, "\r\n", " "); } }