/*
* Copyright (C) 2010 JFrog Ltd.
*
* Licensed 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 org.jfrog.hudson.maven3;
import hudson.*;
import hudson.model.*;
import hudson.remoting.Which;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.tasks.Maven;
import hudson.util.ArgumentListBuilder;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.jfrog.build.api.BuildInfoConfigProperties;
import org.jfrog.build.extractor.maven.Maven3BuildInfoLogger;
import org.jfrog.hudson.action.ActionableHelper;
import org.jfrog.hudson.pipeline.Utils;
import org.jfrog.hudson.util.PluginDependencyHelper;
import org.jfrog.hudson.util.plugins.PluginsUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* Maven3 builder for free style projects. Hudson 1.392 added native support for maven 3 but this one is useful for free style.
*
* @author Yossi Shaul
*/
public class Maven3Builder extends Builder {
public static final String CLASSWORLDS_LAUNCHER = "org.codehaus.plexus.classworlds.launcher.Launcher";
public static final String MAVEN_HOME = "MAVEN_HOME";
private final String mavenName;
private final String rootPom;
private final String goals;
private final String mavenOpts;
@DataBoundConstructor
public Maven3Builder(String mavenName, String rootPom, String goals, String mavenOpts) {
this.mavenName = mavenName;
this.rootPom = rootPom;
this.goals = goals;
this.mavenOpts = mavenOpts;
}
public String getMavenName() {
return mavenName;
}
public String getRootPom() {
return rootPom;
}
public String getGoals() {
return goals;
}
public String getMavenOpts() {
return mavenOpts;
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
listener.getLogger().println("Jenkins Artifactory Plugin version: " + ActionableHelper.getArtifactoryPluginVersion());
EnvVars env = build.getEnvironment(listener);
FilePath workDir = build.getModuleRoot();
ArgumentListBuilder cmdLine = buildMavenCmdLine(build, listener, env, launcher, ((AbstractBuild) build).getWorkspace());
String[] cmds = cmdLine.toCommandArray();
return RunMaven(build, launcher, listener, env, workDir, cmds);
}
public boolean perform(Run<?, ?> build, Launcher launcher, TaskListener listener, EnvVars env, FilePath workDir)
throws InterruptedException, IOException {
listener.getLogger().println("Jenkins Artifactory Plugin version: " + ActionableHelper.getArtifactoryPluginVersion());
ArgumentListBuilder cmdLine = buildMavenCmdLine(build, listener, env, launcher, workDir);
String[] cmds = cmdLine.toCommandArray();
return RunMaven(build, launcher, listener, env, workDir, cmds);
}
private boolean RunMaven(Run<?, ?> build, Launcher launcher, TaskListener listener, EnvVars env, FilePath workDir, String[] cmds) throws InterruptedException {
try {
int exitValue = launcher.launch().cmds(cmds).envs(env).stdout(listener).pwd(workDir).join();
boolean success = (exitValue == 0);
build.setResult(success ? Result.SUCCESS : Result.FAILURE);
return success;
} catch (IOException e) {
Util.displayIOException(e, listener);
e.printStackTrace(listener.fatalError("command execution failed"));
build.setResult(Result.FAILURE);
return false;
}
}
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
}
private ArgumentListBuilder buildMavenCmdLine(Run<?, ?> build, TaskListener listener,
EnvVars env, Launcher launcher, FilePath ws)
throws IOException, InterruptedException {
FilePath mavenHome = getMavenHome(listener, env, launcher);
if (!mavenHome.exists()) {
listener.error("Couldn't find Maven home: " + mavenHome.getRemote());
throw new Run.RunnerAbortedException();
}
ArgumentListBuilder args = new ArgumentListBuilder();
FilePath mavenBootDir = new FilePath(mavenHome, "boot");
FilePath[] classworldsCandidates = mavenBootDir.list("plexus-classworlds*.jar");
if (classworldsCandidates == null || classworldsCandidates.length == 0) {
listener.error("Couldn't find classworlds jar under " + mavenBootDir.getRemote());
throw new Run.RunnerAbortedException();
}
FilePath classWorldsJar = classworldsCandidates[0];
StringBuilder javaPathBuilder = new StringBuilder();
String jdkBinPath = env.get("PATH+JDK");
if (StringUtils.isNotBlank(jdkBinPath)) {
javaPathBuilder.append(jdkBinPath).append("/");
}
javaPathBuilder.append("java");
if (!launcher.isUnix()) {
javaPathBuilder.append(".exe");
}
args.add(javaPathBuilder.toString());
// classpath
args.add("-classpath");
args.add(classWorldsJar.getRemote());
// maven home
args.addKeyValuePair("-D", "maven.home", mavenHome.getRemote(), false);
String buildInfoPropertiesFile = env.get(BuildInfoConfigProperties.PROP_PROPS_FILE);
boolean artifactoryIntegration = StringUtils.isNotBlank(buildInfoPropertiesFile);
listener.getLogger().println("Artifactory integration is " + (artifactoryIntegration ? "enabled" : "disabled"));
String classworldsConfPath;
if (artifactoryIntegration) {
args.addKeyValuePair("-D", BuildInfoConfigProperties.PROP_PROPS_FILE, buildInfoPropertiesFile, false);
// use the classworlds conf packaged with this plugin and resolve the extractor libs
File maven3ExtractorJar = Which.jarFile(Maven3BuildInfoLogger.class);
FilePath actualDependencyDirectory =
PluginDependencyHelper.getActualDependencyDirectory(maven3ExtractorJar, Utils.getNode(launcher).getRootPath());
if (getMavenOpts() == null || !getMavenOpts().contains("-Dm3plugin.lib")) {
args.addKeyValuePair("-D", "m3plugin.lib", actualDependencyDirectory.getRemote(), false);
}
URL resource = getClass().getClassLoader().getResource("org/jfrog/hudson/maven3/classworlds-freestyle.conf");
classworldsConfPath = copyClassWorldsFile(ws, resource).getRemote();
} else {
classworldsConfPath = new FilePath(mavenHome, "bin/m2.conf").getRemote();
}
args.addKeyValuePair("-D", "classworlds.conf", classworldsConfPath, false);
//Starting from Maven 3.3.3
args.addKeyValuePair("-D", "maven.multiModuleProjectDirectory", getMavenProjectPath(build, ws), false);
// maven opts
if (StringUtils.isNotBlank(getMavenOpts())) {
String mavenOpts = getMavenOpts();
if (build instanceof AbstractBuild) {
// If we aren't in pipeline job we, might need to evaluate the variable real value.
mavenOpts = Util.replaceMacro(getMavenOpts(), ((AbstractBuild) build).getBuildVariables());
}
// HAP-314 - We need to separate the args, same as jenkins maven plugin does
args.addTokenized(mavenOpts);
}
// classworlds launcher main class
args.add(CLASSWORLDS_LAUNCHER);
// pom file to build
String rootPom = getRootPom();
if (StringUtils.isNotBlank(rootPom)) {
args.add("-f", rootPom);
}
// maven goals
args.addTokenized(Util.replaceMacro(getGoals(), env));
return args;
}
private FilePath getMavenHome(TaskListener listener, EnvVars env, Launcher launcher) throws IOException, InterruptedException {
if (StringUtils.isNotEmpty(mavenName)) {
Maven.MavenInstallation mi = getMaven();
if (mi == null) {
listener.error("Couldn't find Maven executable.");
throw new Run.RunnerAbortedException();
} else {
Node node = Utils.getNode(launcher);
mi = mi.forNode(node, listener);
mi = mi.forEnvironment(env);
}
return new FilePath(launcher.getChannel(), mi.getHome());
}
if (env.get(MAVEN_HOME) != null) {
return new FilePath(launcher.getChannel(), env.get(MAVEN_HOME));
}
throw new RuntimeException("Couldn't find maven installation");
}
private Maven.MavenInstallation getMaven() {
Maven.MavenInstallation[] installations = getDescriptor().getInstallations();
for (Maven.MavenInstallation i : installations) {
if (mavenName != null && mavenName.equals(i.getName())) {
return i;
}
}
return null;
}
private String getMavenProjectPath(Run<?, ?> build, FilePath ws) {
if (build instanceof AbstractBuild) {
if (StringUtils.isNotBlank(getRootPom())) {
return ((AbstractBuild) build).getModuleRoot().getRemote() + File.separatorChar +
getRootPom().replace("/pom.xml", StringUtils.EMPTY);
}
return ((AbstractBuild) build).getModuleRoot().getRemote();
}
return ws.getRemote();
}
/**
* Copies a classworlds file to a temporary location either on the local filesystem or on a slave depending on the
* node type.
*
* @return The path of the classworlds.conf file
*/
private FilePath copyClassWorldsFile(FilePath ws, URL resource) {
try {
FilePath remoteClassworlds =
ws.createTextTempFile("classworlds", "conf", "", false);
remoteClassworlds.copyFrom(resource);
return remoteClassworlds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
public DescriptorImpl() {
load();
}
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return hudson.model.FreeStyleProject.class.isAssignableFrom(jobType) ||
hudson.matrix.MatrixProject.class.isAssignableFrom(jobType) ||
(Jenkins.getInstance().getPlugin(PluginsUtils.MULTIJOB_PLUGIN_ID) != null &&
com.tikal.jenkins.plugins.multijob.MultiJobProject.class.isAssignableFrom(jobType));
}
@Override
public String getHelpFile() {
return "/help/project-config/maven.html";
}
@Override
public String getDisplayName() {
return Messages.step_displayName();
}
public Maven.MavenInstallation[] getInstallations() {
return Jenkins.getInstance().getDescriptorByType(Maven.DescriptorImpl.class).getInstallations();
}
@Override
public Maven3Builder newInstance(StaplerRequest request, JSONObject formData) throws FormException {
return (Maven3Builder) request.bindJSON(clazz, formData);
}
}
}