org.codehaus.mojo.exec.ExecMojo Maven / Gradle / Ivy
package org.codehaus.mojo.exec;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.
*/
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteResultHandler;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.OS;
import org.apache.commons.exec.ProcessDestroyer;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.exec.ShutdownHookProcessDestroyer;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
import org.apache.maven.artifact.resolver.filter.IncludesArtifactFilter;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import org.codehaus.plexus.util.cli.DefaultConsumer;
import org.codehaus.plexus.util.cli.StreamConsumer;
/**
* A Plugin for executing external programs.
*
* @author Jerome Lacoste ([email protected])
* @version $Id$
* @since 1.0
*/
@Mojo(name = "exec", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST)
public class ExecMojo extends AbstractExecMojo {
/**
* Trying to recognize whether the given {@link #executable} might be a {@code java} binary.
*/
private static final Pattern ENDS_WITH_JAVA = Pattern.compile("^.*java(\\.exe|\\.bin)?$", Pattern.CASE_INSENSITIVE);
/**
*
* The executable. Can be a full path or the name of the executable. In the latter case, the executable must be in
* the PATH for the execution to work. Omit when using executableDependency
.
*
*
* The plugin will search for the executable in the following order:
*
* - relative to the root of the project
* - as toolchain executable
* - relative to the working directory (Windows only)
* - relative to the directories specified in the system property PATH (Windows Only)
*
* Otherwise use the executable as is.
*
*
* @since 1.0
*/
@Parameter(property = "exec.executable")
private String executable;
/**
*
* Timeout in full milliseconds, default is {@code 0}.
*
*
* When set to a value larger than zero, the executable is forcefully
* terminated if it did not finish within this time, and the build will
* fail.
*
*
* @since 3.0.0
*/
@Parameter(property = "exec.timeout", defaultValue = "0")
private int timeout;
/**
*
* The toolchain. If omitted, "jdk"
is assumed.
*
*/
@Parameter(property = "exec.toolchain", defaultValue = "jdk")
private String toolchain;
/**
* The current working directory. Optional. If not specified, basedir will be used.
*
* @since 1.0
*/
@Parameter(property = "exec.workingdir")
private File workingDirectory;
/**
* Program standard and error output will be redirected to the file specified by this optional field. If not
* specified the standard Maven logging is used.
* Note: Be aware that System.out
and System.err
use buffering, so don't
* rely on the order!
*
* @since 1.1-beta-2
* @see java.lang.System#err
* @see java.lang.System#in
*/
@Parameter(property = "exec.outputFile")
private File outputFile;
/**
* Program standard input, output and error streams will be inherited from the maven process.
* This allow tighter control of the streams and the console.
*
* @since 3.0.1
* @see ProcessBuilder#inheritIO()
*/
@Parameter(property = "exec.inheritIo")
private boolean inheritIo;
/**
* When enabled, program standard and error output will be redirected to the
* Maven logger as Info and Error level logs, respectively. If not enabled the
* traditional behavior of program output being directed to standard System.out
* and System.err is used.
*
* NOTE: When enabled, to log the program standard out as Maven Debug level instead of
* Info level use {@code exec.quietLogs=true}.
*
* This option can be extremely helpful when combined with multithreaded builds
* for two reasons:
*
* - Program output is suffixed with the owning thread name, making it easier
* to trace execution of a specific projects build thread.
* - Program output will not get jumbled with other maven log messages.
*
*
* For Example, if using {@code exec:exec} to run a script to echo a count from
* 1 to 100 as:
*
*
* for i in {1..100}
* do
* echo "${project.artifactId} - $i"
* done
*
*
* When this script is run multi-threaded on two modules, {@code module1} and
* {@code module2}, you might get output such as:
*
*
* [BuilderThread 1] [INFO] --- exec-maven-plugin:1.6.0:exec (test) @ module1 ---
* [BuilderThread 2] [INFO] --- exec-maven-plugin:1.6.0:exec (test) @ module2 ---
* ...
* module2 - 98
* modu
* module1 - 97
* module1 -
* le2 - 9899
* ...
*
*
* With this flag enabled, the output will instead come something similar to:
*
*
* ...
* [Exec Stream Pumper] [INFO] [BuilderThread 2] module2 - 98
* [Exec Stream Pumper] [INFO] [BuilderThread 1] module1 - 97
* [Exec Stream Pumper] [INFO] [BuilderThread 1] module1 - 98
* [Exec Stream Pumper] [INFO] [BuilderThread 2] module2 - 99
* ...
*
*
* NOTE 1: To show the thread in the Maven log, configure the Maven
* installations conf/logging/simplelogger.properties option:
* {@code org.slf4j.simpleLogger.showThreadName=true}
*
* NOTE 2: This option is ignored when {@code exec.outputFile} is specified.
*
* @since 3.0.0
* @see java.lang.System#err
* @see java.lang.System#in
*/
@Parameter(property = "exec.useMavenLogger", defaultValue = "false")
private boolean useMavenLogger;
/**
* When combined with {@code exec.useMavenLogger=true}, prints all executed
* program output at debug level instead of the default info level to the Maven
* logger.
*
* @since 3.0.0
*/
@Parameter(property = "exec.quietLogs", defaultValue = "false")
private boolean quietLogs;
/**
*
* A list of arguments passed to the {@code executable}, which should be of type <argument>
or
* <classpath>
. Can be overridden by using the exec.args
environment variable.
*
*
* @since 1.0
*/
@Parameter
private List> arguments; // TODO: Change ? into something more meaningful
/**
* @since 1.0
*/
@Parameter(readonly = true, required = true, defaultValue = "${basedir}")
private File basedir;
/**
* @since 3.0.0
*/
@Parameter(readonly = true, required = true, defaultValue = "${project.build.directory}")
private File buildDirectory;
/**
* Environment variables to pass to the executed program. For example if you want to set the LANG var:
* <environmentVariables>
* <LANG>en_US</LANG>
* </environmentVariables>
*
*
*
* @since 1.1-beta-2
*/
@Parameter
private Map environmentVariables = new HashMap<>();
/**
* Environment script to be merged with environmentVariables This script is platform specifics, on Unix its
* must be Bourne shell format. Use this feature if you have a need to create environment variable dynamically such
* as invoking Visual Studio environment script file
*
* @since 1.4.0
*/
@Parameter
private File environmentScript = null;
/**
* The current build session instance. This is used for toolchain manager API calls.
*/
@Parameter(defaultValue = "${session}", readonly = true)
private MavenSession session;
/**
* Exit codes to be resolved as successful execution for non-compliant applications (applications not returning 0
* for success).
*
* @since 1.1.1
*/
@Parameter
private int[] successCodes;
/**
* If set to true the classpath and the main class will be written to a MANIFEST.MF file and wrapped into a jar.
* Instead of '-classpath/-cp CLASSPATH mainClass' the exec plugin executes '-jar maven-exec.jar'.
*
* @since 1.1.2
*/
@Parameter(property = "exec.longClasspath", defaultValue = "false")
private boolean longClasspath;
/**
* If set to true the modulepath and the main class will be written as an @arg file
* Instead of '--module-path/-p MODULEPATH ' the exec plugin executes '@modulepath'.
*
* @since 1.1.2
*/
@Parameter(property = "exec.longModulepath", defaultValue = "true")
private boolean longModulepath;
/**
* Forces the plugin to recognize the given executable as java executable. This helps with {@link #longClasspath}
* and {@link #longModulepath} parameters.
*
* You shouldn't normally be needing this unless you renamed your java binary or are executing tools
* other than {@code java} which need modulepath or classpath parameters in a separate file.
*
* @since 3.1.1
*/
@Parameter(property = "exec.forceJava", defaultValue = "false")
private boolean forceJava;
/**
* If set to true the child process executes asynchronously and build execution continues in parallel.
*/
@Parameter(property = "exec.async", defaultValue = "false")
private boolean async;
/**
* If set to true, the asynchronous child process is destroyed upon JVM shutdown. If set to false, asynchronous
* child process continues execution after JVM shutdown. Applies only to asynchronous processes; ignored for
* synchronous processes.
*/
@Parameter(property = "exec.asyncDestroyOnShutdown", defaultValue = "true")
private boolean asyncDestroyOnShutdown = true;
@Component
private ToolchainManager toolchainManager;
public static final String CLASSPATH_TOKEN = "%classpath";
public static final String MODULEPATH_TOKEN = "%modulepath";
/**
* priority in the execute method will be to use System properties arguments over the pom specification.
*
* @throws MojoExecutionException if a failure happens
*/
public void execute() throws MojoExecutionException {
if (executable == null) {
if (executableDependency == null) {
throw new MojoExecutionException("The parameter 'executable' is missing or invalid");
}
executable = findExecutableArtifact().getFile().getAbsolutePath();
getLog().debug("using executable dependency " + executable);
}
if (isSkip()) {
getLog().info("skipping execute as per configuration");
return;
}
if (basedir == null) {
throw new IllegalStateException("basedir is null. Should not be possible.");
}
try {
handleWorkingDirectory();
String argsProp = getSystemProperty("exec.args");
List commandArguments = new ArrayList<>();
if (hasCommandlineArgs()) {
handleCommandLineArgs(commandArguments);
} else if (!StringUtils.isEmpty(argsProp)) {
handleSystemPropertyArguments(argsProp, commandArguments);
} else {
if (arguments != null) {
handleArguments(commandArguments);
}
}
Map enviro = handleSystemEnvVariables();
CommandLine commandLine = getExecutablePath(enviro, workingDirectory);
String[] args = commandArguments.toArray(new String[commandArguments.size()]);
commandLine.addArguments(args, false);
Executor exec = new ExtendedExecutor(inheritIo);
if (this.timeout > 0) {
exec.setWatchdog(new ExecuteWatchdog(this.timeout));
}
exec.setWorkingDirectory(workingDirectory);
fillSuccessCodes(exec);
if (OS.isFamilyOpenVms() && inheritIo) {
getLog().warn(
"The inheritIo flag is not supported on OpenVMS, execution will proceed without stream inheritance.");
}
getLog().debug("Executing command line: " + commandLine);
try {
int resultCode;
if (outputFile != null) {
if (!outputFile.getParentFile().exists()
&& !outputFile.getParentFile().mkdirs()) {
getLog().warn("Could not create non existing parent directories for log file: " + outputFile);
}
try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
resultCode = executeCommandLine(exec, commandLine, enviro, outputStream);
}
} else if (useMavenLogger) {
getLog().debug("Will redirect program output to Maven logger");
// If running parallel, append the projects original (i.e. current) thread name to the program
// output as a log prefix, to enable easy tracing of program output when intermixed with other
// Maven log output. NOTE: The accept(..) methods are running in PumpStreamHandler thread,
// which is why we capture the thread name prefix here.
final String logPrefix =
session.isParallel() ? "[" + Thread.currentThread().getName() + "] " : "";
Consumer mavenOutRedirect = logMessage -> {
if (quietLogs) {
getLog().debug(logPrefix + logMessage);
} else {
getLog().info(logPrefix + logMessage);
}
};
Consumer mavenErrRedirect = logMessage -> getLog().error(logPrefix + logMessage);
try (OutputStream out = new LineRedirectOutputStream(mavenOutRedirect);
OutputStream err = new LineRedirectOutputStream(mavenErrRedirect)) {
resultCode = executeCommandLine(exec, commandLine, enviro, out, err);
}
} else {
resultCode = executeCommandLine(exec, commandLine, enviro, System.out, System.err);
}
if (isResultCodeAFailure(resultCode)) {
String message = "Result of " + commandLine.toString() + " execution is: '" + resultCode + "'.";
getLog().error(message);
throw new MojoExecutionException(message);
}
} catch (ExecuteException e) {
if (exec.getWatchdog() != null && exec.getWatchdog().killedProcess()) {
final String message = "Timeout. Process runs longer than " + this.timeout + " ms.";
getLog().error(message);
throw new MojoExecutionException(message, e);
} else {
getLog().error("Command execution failed.", e);
throw new MojoExecutionException("Command execution failed.", e);
}
} catch (IOException e) {
getLog().error("Command execution failed.", e);
throw new MojoExecutionException("Command execution failed.", e);
}
registerSourceRoots();
} catch (IOException e) {
throw new MojoExecutionException("I/O Error", e);
}
}
private Map handleSystemEnvVariables() throws MojoExecutionException {
// Avoid creating env vars that differ only in case on Windows.
// https://github.com/mojohaus/exec-maven-plugin/issues/328
// It is not enough to avoid duplicates; we must preserve the case found in the "natural" environment.
// https://developercommunity.visualstudio.com/t/Build-Error:-MSB6001-in-Maven-Build/10527486?sort=newest
Map enviro = new HashMap<>(System.getenv());
if (environmentVariables != null) {
enviro.putAll(environmentVariables);
}
if (this.environmentScript != null) {
getLog().info("Pick up external environment script: " + this.environmentScript);
Map envVarsFromScript = this.createEnvs(this.environmentScript);
if (envVarsFromScript != null) {
enviro.putAll(envVarsFromScript);
}
}
if (this.getLog().isDebugEnabled()) {
Set keys = new TreeSet<>();
keys.addAll(enviro.keySet());
for (String key : keys) {
this.getLog().debug("env: " + key + "=" + enviro.get(key));
}
}
return enviro;
}
/**
* This is a convenient method to make the execute method a little bit more readable. It will define the
* workingDirectory to be the baseDir in case of workingDirectory is null. If the workingDirectory does not exist it
* will created.
*
* @throws MojoExecutionException
*/
private void handleWorkingDirectory() throws MojoExecutionException {
if (workingDirectory == null) {
workingDirectory = basedir;
}
if (!workingDirectory.exists()) {
getLog().debug("Making working directory '" + workingDirectory.getAbsolutePath() + "'.");
if (!workingDirectory.mkdirs()) {
throw new MojoExecutionException(
"Could not make working directory: '" + workingDirectory.getAbsolutePath() + "'");
}
}
}
private void handleSystemPropertyArguments(String argsProp, List commandArguments)
throws MojoExecutionException {
getLog().debug("got arguments from system properties: " + argsProp);
try {
String[] args = CommandLineUtils.translateCommandline(argsProp);
commandArguments.addAll(Arrays.asList(args));
} catch (Exception e) {
throw new MojoExecutionException("Couldn't parse systemproperty 'exec.args'");
}
}
private void handleCommandLineArgs(List commandArguments) throws MojoExecutionException, IOException {
String[] args = parseCommandlineArgs();
for (int i = 0; i < args.length; i++) {
if (isLongClassPathArgument(args[i])) {
// it is assumed that starting from -cp or -classpath the arguments
// are: -classpath/-cp %classpath mainClass
// the arguments are replaced with: -jar $TMP/maven-exec.jar
// NOTE: the jar will contain the classpath and the main class
commandArguments.add("-jar");
File tmpFile = createJar(computePath(null), args[i + 2]);
commandArguments.add(tmpFile.getAbsolutePath());
i += 2;
} else if (args[i].contains(CLASSPATH_TOKEN)) {
commandArguments.add(args[i].replace(CLASSPATH_TOKEN, computeClasspathString(null)));
} else {
commandArguments.add(args[i]);
}
}
}
private void handleArguments(List commandArguments) throws MojoExecutionException, IOException {
String specialArg = null;
for (int i = 0; i < arguments.size(); i++) {
Object argument = arguments.get(i);
if (specialArg != null) {
if (isLongClassPathArgument(specialArg) && argument instanceof Classpath) {
// it is assumed that starting from -cp or -classpath the arguments
// are: -classpath/-cp %classpath mainClass
// the arguments are replaced with: -jar $TMP/maven-exec.jar
// NOTE: the jar will contain the classpath and the main class
commandArguments.add("-jar");
File tmpFile = createJar(computePath((Classpath) argument), (String) arguments.get(++i));
commandArguments.add(tmpFile.getAbsolutePath());
} else if (isLongModulePathArgument(specialArg) && argument instanceof Modulepath) {
String filePath = new File(buildDirectory, "modulepath").getAbsolutePath();
StringBuilder modulePath = new StringBuilder();
modulePath.append('"');
for (Iterator it =
computePath((Modulepath) argument).iterator();
it.hasNext(); ) {
modulePath.append(it.next().replace("\\", "\\\\"));
if (it.hasNext()) {
modulePath.append(File.pathSeparatorChar);
}
}
modulePath.append('"');
createArgFile(filePath, Arrays.asList("-p", modulePath.toString()));
commandArguments.add('@' + filePath);
} else {
commandArguments.add(specialArg);
}
specialArg = null;
continue;
}
if (argument instanceof Classpath) {
Classpath specifiedClasspath = (Classpath) argument;
commandArguments.add(computeClasspathString(specifiedClasspath));
} else if (argument instanceof Modulepath) {
Modulepath specifiedModulepath = (Modulepath) argument;
commandArguments.add(computeClasspathString(specifiedModulepath));
} else if ((argument instanceof String)
&& (isLongModulePathArgument((String) argument) || isLongClassPathArgument((String) argument))) {
specialArg = (String) argument;
} else if (argument == null) {
commandArguments.add("");
} else {
commandArguments.add((String) argument);
}
}
}
private void fillSuccessCodes(Executor exec) {
if (successCodes != null && successCodes.length > 0) {
exec.setExitValues(successCodes);
}
}
boolean isResultCodeAFailure(int result) {
if (successCodes == null || successCodes.length == 0) {
return result != 0;
}
for (int successCode : successCodes) {
if (successCode == result) {
return false;
}
}
return true;
}
private boolean isLongClassPathArgument(String arg) {
return isJavaExec() && longClasspath && ("-classpath".equals(arg) || "-cp".equals(arg));
}
private boolean isLongModulePathArgument(String arg) {
return isJavaExec() && longModulepath && ("--module-path".equals(arg) || "-p".equals(arg));
}
/**
* Returns {@code true} when a java binary is being executed.
*
* @return {@code true} when a java binary is being executed.
*/
private boolean isJavaExec() {
if (forceJava) {
return true;
}
if (this.executable.contains("%JAVA_HOME")
|| this.executable.contains("${JAVA_HOME}")
|| this.executable.contains("$JAVA_HOME")) {
// also applies to *most* other tools.
return true;
}
return ENDS_WITH_JAVA.matcher(this.executable).matches();
}
/**
* Compute the classpath from the specified Classpath. The computed classpath is based on the classpathScope. The
* plugin cannot know from maven the phase it is executed in. So we have to depend on the user to tell us he wants
* the scope in which the plugin is expected to be executed.
*
* @param specifiedClasspath Non null when the user restricted the dependencies, null
otherwise (the
* default classpath will be used)
* @return a platform specific String representation of the classpath
*/
private String computeClasspathString(AbstractPath specifiedClasspath) throws MojoExecutionException {
List resultList = computePath(specifiedClasspath);
StringBuffer theClasspath = new StringBuffer();
for (String str : resultList) {
addToClasspath(theClasspath, str);
}
return theClasspath.toString();
}
/**
* Compute the classpath from the specified Classpath. The computed classpath is based on the classpathScope. The
* plugin cannot know from maven the phase it is executed in. So we have to depend on the user to tell us he wants
* the scope in which the plugin is expected to be executed.
*
* @param specifiedClasspath Non null when the user restricted the dependencies, null
otherwise (the
* default classpath will be used)
* @return a list of class path elements
*/
private List computePath(AbstractPath specifiedClasspath) throws MojoExecutionException {
List artifacts = new ArrayList<>();
List theClasspathFiles = new ArrayList<>();
List resultList = new ArrayList<>();
collectProjectArtifactsAndClasspath(artifacts, theClasspathFiles);
Set pluginDependencies = determineRelevantPluginDependencies();
if (pluginDependencies != null) {
artifacts.addAll(pluginDependencies);
}
if ((specifiedClasspath != null) && (specifiedClasspath.getDependencies() != null)) {
artifacts = filterArtifacts(artifacts, specifiedClasspath.getDependencies());
}
for (Path f : theClasspathFiles) {
resultList.add(f.toAbsolutePath().toString());
}
for (Artifact artifact : artifacts) {
getLog().debug("dealing with " + artifact);
resultList.add(artifact.getFile().getAbsolutePath());
}
return resultList;
}
private static void addToClasspath(StringBuffer theClasspath, String toAdd) {
if (theClasspath.length() > 0) {
theClasspath.append(File.pathSeparator);
}
theClasspath.append(toAdd);
}
private List filterArtifacts(List artifacts, Collection dependencies) {
AndArtifactFilter filter = new AndArtifactFilter();
filter.add(new IncludesArtifactFilter(new ArrayList<>(dependencies))); // gosh
List filteredArtifacts = new ArrayList<>();
for (Artifact artifact : artifacts) {
if (filter.include(artifact)) {
getLog().debug("filtering in " + artifact);
filteredArtifacts.add(artifact);
}
}
return filteredArtifacts;
}
private ProcessDestroyer processDestroyer;
CommandLine getExecutablePath(Map enviro, File dir) {
File execFile = new File(executable);
String exec = null;
if (execFile.isFile()) {
getLog().debug("Toolchains are ignored, 'executable' parameter is set to " + executable);
exec = execFile.getAbsolutePath();
}
if (exec == null) {
Toolchain tc = getToolchain();
// if the file doesn't exist & toolchain is null, the exec is probably in the PATH...
// we should probably also test for isFile and canExecute, but the second one is only
// available in SDK 6.
if (tc != null) {
getLog().info("Toolchain in exec-maven-plugin: " + tc);
exec = tc.findTool(executable);
} else {
if (OS.isFamilyWindows()) {
List paths = this.getExecutablePaths(enviro);
paths.add(0, dir.getAbsolutePath());
exec = findExecutable(executable, paths);
}
}
}
if (exec == null) {
exec = executable;
}
CommandLine toRet;
if (OS.isFamilyWindows() && !hasNativeExtension(exec) && hasExecutableExtension(exec)) {
// run the windows batch script in isolation and exit at the end
final String comSpec = System.getenv("ComSpec");
toRet = new CommandLine(comSpec == null ? "cmd" : comSpec);
toRet.addArgument("/c");
toRet.addArgument(exec);
} else {
toRet = new CommandLine(exec);
}
return toRet;
}
static String findExecutable(final String executable, final List paths) {
File f = null;
search:
for (final String path : paths) {
f = new File(path, executable);
if (!OS.isFamilyWindows() && f.isFile()) break;
else
for (final String extension : getExecutableExtensions()) {
f = new File(path, executable + extension);
if (f.isFile()) break search;
}
}
if (f == null || !f.exists()) return null;
return f.getAbsolutePath();
}
private static boolean hasNativeExtension(final String exec) {
final String lowerCase = exec.toLowerCase();
return lowerCase.endsWith(".exe") || lowerCase.endsWith(".com");
}
private static boolean hasExecutableExtension(final String exec) {
final String lowerCase = exec.toLowerCase();
for (final String ext : getExecutableExtensions()) if (lowerCase.endsWith(ext)) return true;
return false;
}
private static List getExecutableExtensions() {
final String pathExt = System.getenv("PATHEXT");
return pathExt == null
? Arrays.asList(".bat", ".cmd")
: Arrays.asList(StringUtils.split(pathExt.toLowerCase(), File.pathSeparator));
}
private List getExecutablePaths(Map enviro) {
List paths = new ArrayList<>();
paths.add("");
String path = enviro.get("PATH");
if (path != null) {
paths.addAll(Arrays.asList(StringUtils.split(path, File.pathSeparator)));
}
return paths;
}
protected int executeCommandLine(
Executor exec, CommandLine commandLine, Map enviro, OutputStream out, OutputStream err)
throws IOException {
// note: don't use BufferedOutputStream here since it delays the outputs MEXEC-138
PumpStreamHandler psh = new PumpStreamHandler(out, err, System.in);
return executeCommandLine(exec, commandLine, enviro, psh);
}
protected int executeCommandLine(
Executor exec, CommandLine commandLine, Map enviro, FileOutputStream outputFile)
throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(outputFile);
PumpStreamHandler psh = new PumpStreamHandler(bos);
return executeCommandLine(exec, commandLine, enviro, psh);
}
protected int executeCommandLine(
Executor exec, final CommandLine commandLine, Map enviro, final PumpStreamHandler psh)
throws IOException {
exec.setStreamHandler(psh);
int result;
try {
psh.start();
if (async) {
if (asyncDestroyOnShutdown) {
exec.setProcessDestroyer(getProcessDestroyer());
}
exec.execute(commandLine, enviro, new ExecuteResultHandler() {
public void onProcessFailed(ExecuteException e) {
getLog().error("Async process failed for: " + commandLine, e);
}
public void onProcessComplete(int exitValue) {
getLog().info("Async process complete, exit value = " + exitValue + " for: " + commandLine);
try {
psh.stop();
} catch (IOException e) {
getLog().error("Error stopping async process stream handler for: " + commandLine, e);
}
}
});
result = 0;
} else {
result = exec.execute(commandLine, enviro);
}
} finally {
if (!async) {
psh.stop();
}
}
return result;
}
//
// methods used for tests purposes - allow mocking and simulate automatic setters
//
void setExecutable(String executable) {
this.executable = executable;
}
String getExecutable() {
return executable;
}
void setWorkingDirectory(String workingDir) {
setWorkingDirectory(new File(workingDir));
}
void setWorkingDirectory(File workingDir) {
this.workingDirectory = workingDir;
}
void setArguments(List> arguments) {
this.arguments = arguments;
}
void setBasedir(File basedir) {
this.basedir = basedir;
}
void setProject(MavenProject project) {
this.project = project;
}
protected String getSystemProperty(String key) {
return System.getProperty(key);
}
public void setSuccessCodes(Integer... list) {
this.successCodes = new int[list.length];
for (int index = 0; index < list.length; index++) {
successCodes[index] = list[index];
}
}
public int[] getSuccessCodes() {
return successCodes;
}
private Toolchain getToolchain() {
// session and toolchainManager can be null in tests ...
if (session != null && toolchainManager != null) {
return toolchainManager.getToolchainFromBuildContext(toolchain, session);
}
return null;
}
/**
* Create a jar with just a manifest containing a Main-Class entry for SurefireBooter and a Class-Path entry for all
* classpath elements. Copied from surefire (ForkConfiguration#createJar())
*
* @param classPath List<String> of all classpath elements.
* @return
* @throws IOException
*/
private File createJar(List classPath, String mainClass) throws IOException {
File file = Files.createTempFile("maven-exec", ".jar").toFile();
file.deleteOnExit();
try (FileOutputStream fos = new FileOutputStream(file);
JarOutputStream jos = new JarOutputStream(fos)) {
jos.setLevel(JarOutputStream.STORED);
JarEntry je = new JarEntry("META-INF/MANIFEST.MF");
jos.putNextEntry(je);
Manifest man = new Manifest();
// we can't use StringUtils.join here since we need to add a '/' to
// the end of directory entries - otherwise the jvm will ignore them.
StringBuilder cp = new StringBuilder();
for (String el : classPath) {
// NOTE: if File points to a directory, this entry MUST end in '/'.
cp.append(new URL(new File(el).toURI().toASCIIString()).toExternalForm() + " ");
}
man.getMainAttributes().putValue("Manifest-Version", "1.0");
man.getMainAttributes().putValue("Class-Path", cp.toString().trim());
man.getMainAttributes().putValue("Main-Class", mainClass);
man.write(jos);
}
return file;
}
private void createArgFile(String filePath, List lines) throws IOException {
final String EOL = System.getProperty("line.separator", "\\n");
try (FileWriter writer = new FileWriter(filePath)) {
for (String line : lines) {
writer.append(line).append(EOL);
}
}
}
protected Map createEnvs(File envScriptFile) throws MojoExecutionException {
Map results = null;
File tmpEnvExecFile = null;
try {
tmpEnvExecFile = this.createEnvWrapperFile(envScriptFile);
Commandline cl = new Commandline(); // commons-exec instead?
cl.setExecutable(tmpEnvExecFile.getAbsolutePath());
if (!OS.isFamilyWindows()) {
cl.setExecutable("sh");
cl.createArg().setFile(tmpEnvExecFile);
}
// pickup the initial env vars so that the env script can used if necessary
if (environmentVariables != null) {
for (Map.Entry item : environmentVariables.entrySet()) {
cl.addEnvironment(item.getKey(), item.getValue());
}
}
EnvStreamConsumer stdout = new EnvStreamConsumer();
StreamConsumer stderr = new DefaultConsumer();
CommandLineUtils.executeCommandLine(cl, stdout, stderr);
if (!stdout.getUnparsedLines().isEmpty()) {
getLog().warn("The following lines could not be parsed into environment variables :");
for (String line : stdout.getUnparsedLines()) {
getLog().warn(line);
}
}
results = stdout.getParsedEnv();
} catch (CommandLineException | IOException e) {
throw new MojoExecutionException(e.getMessage());
} finally {
if (tmpEnvExecFile != null) {
tmpEnvExecFile.delete();
}
}
return results;
}
protected File createEnvWrapperFile(File envScript) throws IOException {
File tmpFile = null;
if (OS.isFamilyWindows()) {
tmpFile = Files.createTempFile("env", ".bat").toFile();
try (PrintWriter writer = new PrintWriter(tmpFile)) {
writer.append("@echo off").println();
writer.append("call \"")
.append(envScript.getCanonicalPath())
.append("\"")
.println();
writer.append("echo " + EnvStreamConsumer.START_PARSING_INDICATOR)
.println();
writer.append("set").println();
writer.flush();
}
} else {
tmpFile = Files.createTempFile("env", ".sh").toFile();
// tmpFile.setExecutable( true );//java 6 only
try (PrintWriter writer = new PrintWriter(tmpFile)) {
writer.append("#! /bin/sh").println();
writer.append(". ").append(envScript.getCanonicalPath()).println(); // works on all unix??
writer.append("echo " + EnvStreamConsumer.START_PARSING_INDICATOR)
.println();
writer.append("env").println();
writer.flush();
}
}
return tmpFile;
}
protected ProcessDestroyer getProcessDestroyer() {
if (processDestroyer == null) {
processDestroyer = new ShutdownHookProcessDestroyer();
}
return processDestroyer;
}
}