org.hudsonci.maven.plugin.builder.internal.MavenProcessBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of maven3-plugin Show documentation
Show all versions of maven3-plugin Show documentation
This plug-in adds Maven3 support to Hudson. It adds a builder to Freestyle Project to build maven projects.
/**
* The MIT License
*
* Copyright (c) 2010-2011 Sonatype, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.hudsonci.maven.plugin.builder.internal;
import org.hudsonci.utils.common.TestAccessible;
import org.hudsonci.maven.model.PropertiesDTO;
import org.hudsonci.maven.model.config.BuildConfigurationDTO;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.util.ArgumentListBuilder;
import hudson.util.ClasspathBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.OutputStream;
import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.hudsonci.maven.eventspy.common.Constants.PORT_PROPERTY;
import static org.hudsonci.maven.model.ModelUtil.isEmpty;
import static org.hudsonci.maven.model.ModelUtil.isSet;
import static org.hudsonci.maven.plugin.builder.internal.MavenConstants.*;
import static org.hudsonci.maven.plugin.builder.internal.PathNormalizer.Platform.UNIX;
import static org.hudsonci.maven.plugin.builder.internal.PathNormalizer.Platform.WINDOWS;
/**
* Helper to build the arguments and environment for launching Maven.
*
* @author Jason Dillon
* @since 2.1.0
*/
public class MavenProcessBuilder
{
private final Logger log;
private boolean windows;
private BuildConfigurationDTO config;
private FilePath tmpDir;
private FilePath mavenHome;
private FilePath executable;
private ClasspathBuilder extClasspath;
private FilePath repository;
private EnvVars env;
private Map buildVariables;
private Integer port;
private FilePath workingDir;
private OutputStream standardOutput;
public MavenProcessBuilder(final Logger log) {
this.log = checkNotNull(log);
}
@TestAccessible
MavenProcessBuilder() {
this.log = LoggerFactory.getLogger(getClass());
}
public MavenProcessBuilder withWindows(final boolean flag) {
this.windows = flag;
return this;
}
public MavenProcessBuilder withTmpDir(final FilePath dir) {
this.tmpDir = dir;
return this;
}
public MavenProcessBuilder withMavenHome(final FilePath dir) {
this.mavenHome = dir;
return this;
}
public MavenProcessBuilder withMavenExecutable(final FilePath file) {
this.executable = file;
return this;
}
public MavenProcessBuilder withExtClasspath(final ClasspathBuilder classpath) {
this.extClasspath = classpath;
return this;
}
public MavenProcessBuilder withRepository(final FilePath dir) {
this.repository = dir;
return this;
}
public MavenProcessBuilder withBuildVariables(final Map variables) {
this.buildVariables = variables;
return this;
}
public MavenProcessBuilder withEnv(final EnvVars env) {
this.env = env;
return this;
}
public MavenProcessBuilder withPort(final int port) {
this.port = port;
return this;
}
public MavenProcessBuilder withConfiguration(final BuildConfigurationDTO config) {
this.config = config;
return this;
}
public MavenProcessBuilder withWorkingDirectory(final FilePath dir) {
this.workingDir = dir;
return this;
}
public MavenProcessBuilder withStandardOutput(final OutputStream output) {
this.standardOutput = output;
return this;
}
private T ensureNotNull(final T obj) {
if (obj == null) {
throw new IllegalStateException();
}
return obj;
}
/**
* Allows sub-class to perform variable resolution. All textual user-inputs should pass though this method.
*/
protected String resolve(final String value) {
return value;
}
private PathNormalizer normalizer;
private PathNormalizer getNormalizer() {
if (normalizer == null) {
normalizer = new PathNormalizer(windows ? WINDOWS : UNIX);
}
return normalizer;
}
private String normalize(final String path) {
return getNormalizer().normalize(path);
}
private FilePath normalize(final FilePath path) {
return getNormalizer().normalize(path);
}
private ClasspathBuilder normalize(final ClasspathBuilder path) {
return getNormalizer().normalize(path);
}
/**
* Build the arguments.
*/
public ArgumentListBuilder buildArguments() throws Exception {
ensureNotNull(config);
ArgumentListBuilder args = new ArgumentListBuilder();
args.add(normalize(ensureNotNull(executable)));
if (!isEmpty(config.getGoals())) {
String goals = resolve(config.getGoals());
args.addTokenized(goals);
detectSuperfluousOptions(args);
if (detectBannedOptions(args)) {
// Do not attempt to go any further if we found banned options, the mvn execution won't work as expected
throw new AbortException("Detected banned options");
}
}
// Always show the version being used.
args.add("-V");
// Enable non-interactive (batch) mode.
args.add("-B");
if (isSet(config.getErrors())) {
args.add("-e");
}
// FIXME: This does not properly resolve the values or trim them for spaces
args.addKeyValuePairs("-D", ensureNotNull(buildVariables));
if (config.getProperties() != null) {
for (PropertiesDTO.Entry entry : config.getProperties().getEntries()) {
String value = resolve(entry.getValue());
value = value.trim();
args.add(String.format("-D%s=%s", entry.getName(), value));
}
}
// Just warn about banned properties, don't puke (may be setting some debug bits)
detectBannedProperties(args);
// Add eventspy configuration, needs to be here to avoid problems with spaces in paths
args.add(String.format("-D%s=%s", MAVEN_EXT_CLASS_PATH, normalize(ensureNotNull(extClasspath)).toString(windows ? ";" : ":")));
args.add(String.format("-D%s=%d", PORT_PROPERTY, ensureNotNull(port)));
if (isSet(config.getPrivateRepository())) {
// Complain when private repo configured as well as custom repository location via properties
if (config.getProperties() != null && config.getProperties().contains(MAVEN_REPO_LOCAL)) {
log.warn("Private repository configured as well as custom '{}' property; Custom property will take precedence", MAVEN_REPO_LOCAL);
}
else {
args.add(String.format("-D%s=%s", MAVEN_REPO_LOCAL, normalize(ensureNotNull(repository))));
}
}
if (!isEmpty(config.getPomFile())) {
String file = resolve(config.getPomFile());
args.add("-f").add(file);
}
if (isSet(config.getOffline())) {
args.add("-o");
}
if (!isSet(config.getRecursive())) {
args.add("-N");
}
if (config.getProfiles() != null) {
for (String profile : config.getProfiles()) {
profile = resolve(profile);
args.add("-P").add(profile);
}
}
if (config.getProjects() != null) {
for (String project : config.getProjects()) {
project = resolve(project);
args.add("-pl").add(project);
}
}
if (!isEmpty(config.getResumeFrom())) {
String resumeFrom = resolve(config.getResumeFrom());
args.add("-rf").add(resumeFrom);
}
if (config.getVerbosity() != null) {
switch (config.getVerbosity()) {
case QUIET:
args.add("-q");
break;
case DEBUG:
args.add("-X");
break;
}
}
if (config.getChecksumMode() != null) {
switch (config.getChecksumMode()) {
case LAX:
args.add("-c");
break;
case STRICT:
args.add("-C");
}
}
if (config.getFailMode() != null) {
switch (config.getFailMode()) {
case FAST:
args.add("-ff");
break;
case AT_END:
args.add("-fae");
break;
case NEVER:
args.add("-fn");
break;
}
}
if (config.getMakeMode() != null) {
switch (config.getMakeMode()) {
case DEPENDENCIES:
args.add("-am");
break;
case DEPENDENTS:
args.add("-amd");
break;
case BOTH:
args.add("-am").add("-amd");
break;
}
}
if (config.getSnapshotUpdateMode() != null) {
switch (config.getSnapshotUpdateMode()) {
case FORCE:
args.add("-U");
break;
case SUPPRESS:
args.add("-nsu");
break;
}
}
if (!isEmpty(config.getThreading())) {
String threading = resolve(config.getThreading());
args.add("-T").add(threading);
}
if (log.isDebugEnabled()) {
log.debug("Arguments:");
for (String arg : args.toCommandArray()) {
log.debug(" '{}'", arg);
}
}
return args;
}
/**
* Build MAVEN_OPTS values.
*/
public ArgumentListBuilder buildOpts() throws Exception {
ensureNotNull(config);
ArgumentListBuilder mavenOpts = new ArgumentListBuilder();
if (!isEmpty(config.getMavenOpts())) {
String opts = resolve(config.getMavenOpts());
mavenOpts.addTokenized(opts);
detectSuperfluousOptions(mavenOpts);
detectBannedProperties(mavenOpts);
if (detectBannedOptions(mavenOpts)) {
// Do not attempt to go any further if we found banned options, the mvn execution won't work as expected
throw new AbortException("Detected banned options");
}
}
if (isSet(config.getPrivateTmpdir())) {
// Add tmpdir if not already configured
String customTmpdir = findArgumentWithPrefix(mavenOpts, String.format("-D%s=", JAVA_IO_TMPDIR));
if (customTmpdir != null) {
log.warn("Using user-configured tmpdir: {}", customTmpdir);
}
else {
if (tmpDir != null) {
String path = tmpDir.getRemote();
if (path.contains(" ")) {
log.warn("Path contains spaces: {}", path);
}
// Ensure that the path has a trailing separator char, some bits look like they expect it (hsperfdata* tmpdir)
String sep = windows ? "\\" : "/";
if (!path.endsWith(sep)) {
path += sep;
}
mavenOpts.add(String.format("-D%s=%s", JAVA_IO_TMPDIR, normalize(path)));
}
else {
log.warn("Using default tmpdir");
}
}
}
return mavenOpts;
}
private String findArgumentWithPrefix(final ArgumentListBuilder args, final String prefix) {
for (String arg : args.toList()) {
if (arg.startsWith(prefix)) {
return arg;
}
}
return null;
}
/**
* Build the environment.
*/
public EnvVars buildEnv() throws Exception {
ensureNotNull(env);
// These are for windows only
if (windows) {
env.put(MAVEN_TERMINATE_CMD, ON);
env.put(MAVEN_BATCH_ECHO, OFF);
env.put(MAVEN_BATCH_PAUSE, OFF);
}
env.put(M2_HOME, normalize(ensureNotNull(mavenHome)).getRemote());
env.put(MAVEN_OPTS, buildOpts().toStringWithQuote());
env.put(MAVEN_SKIP_RC, TRUE);
if (log.isDebugEnabled()) {
log.debug("Environment:");
for (Map.Entry entry : env.entrySet()) {
log.debug(" {}='{}'", entry.getKey(), entry.getValue());
}
}
return env;
}
/**
* Build the process starter.
*/
public Launcher.ProcStarter build(final Launcher.ProcStarter starter) throws Exception {
assert starter != null;
return starter
.cmds(buildArguments())
.envs(buildEnv())
.pwd(ensureNotNull(workingDir))
.stdout(ensureNotNull(standardOutput));
}
// FIXME: Due to the use of commons-cli which does not require the fully qualified option name to be used,
// FIXME: ... these checks will fail to catch some uses of options. May need to either use commons-cli here
// FIXME: ... to validate or figure out what the unique prefix for each is and check for arg startsWith()
/**
* Superfluous options, users should be warned when configuring these via goals or mavenOpts, as they are pointless.
*/
public static final String[] SUPERFLUOUS_OPTIONS = {
"-B",
"--batch-mode",
"-batch-mode",
"-V",
"--show-version",
"-show-version"
// Maven will also warn about deprecated options, so no need to add checks for them here
};
/**
* Complain if any superfluous options were configured.
*/
boolean detectSuperfluousOptions(final ArgumentListBuilder args) {
assert args != null;
boolean detected = false;
for (String arg : args.toCommandArray()) {
for (String opt : SUPERFLUOUS_OPTIONS) {
if (arg.equals(opt)) {
log.warn("Detected superfluous option: {}", arg);
detected = true;
}
}
}
return detected;
}
/**
* Banned options, users should be warned when configuring these via goals or mavenOpts, as this will prevent the integration from functioning
* correctly.
*/
public static final String[] BANNED_OPTIONS = {
"--help",
"-help",
"-h",
"--version",
"-version",
"-v",
"-ep",
"--encrypt-password",
"-encrypt-password",
"-emp",
"--encrypt-master-password",
"-encrypt-master-password"
};
/**
* Complain if any banned options were configured.
*/
boolean detectBannedOptions(final ArgumentListBuilder args) {
assert args != null;
boolean detected = false;
for (String arg : args.toCommandArray()) {
for (String opt : BANNED_OPTIONS) {
if (arg.equals(opt)) {
log.error("Detected banned option: {}", arg);
detected = true;
}
}
}
return detected;
}
/**
* Banned properties (prefixes in arg form), users should be warned when configuring these via goals or mavenOpts, as they may prevent the
* integration from functioning correctly.
*/
public static final String[] BANNED_PROPERTIES = {
"-Dmaven.ext.class.path=",
"-Dhudson.eventspy."
};
/**
* Complain if any banned properties were configured.
*/
public boolean detectBannedProperties(final ArgumentListBuilder args) {
assert args != null;
boolean detected = false;
for (String arg : args.toCommandArray()) {
for (String prop : BANNED_PROPERTIES) {
if (arg.startsWith(prop)) {
log.warn("Detected banned property: {}", arg);
detected = true;
}
}
}
return detected;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy