org.codehaus.mojo.exec.ExecMojo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of exec-maven-plugin Show documentation
Show all versions of exec-maven-plugin Show documentation
A plugin to allow execution of system and Java programs
The newest version!
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.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
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.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.OS;
import org.apache.commons.exec.PumpStreamHandler;
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.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.maven.toolchain.Toolchain;
import org.apache.maven.toolchain.ToolchainManager;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.cli.CommandLineUtils;
/**
* A Plugin for executing external programs.
*
* @author Jerome Lacoste
* @version $Id: ExecMojo.java 12386 2010-07-16 22:10:38Z rfscholte $
* @goal exec
* @requiresDependencyResolution test
* @since 1.0
*/
public class ExecMojo extends AbstractExecMojo {
/**
* The executable. Can be a full path or a the name executable. In the latter case, the executable must be in the
* PATH for the execution to work.
*
* @parameter expression="${exec.executable}"
* @required
* @since 1.0
*/
private String executable;
/**
* The current working directory. Optional. If not specified, basedir will be used.
*
* @parameter expression="${exec.workingdir}
* @since 1.0
*/
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.
*
* @parameter expression="${exec.outputFile}"
* @since 1.1-beta-2
*/
private File outputFile;
/**
* Can be of type <argument>
or <classpath>
Can be overriden using "exec.args"
* env. variable
*
* @parameter
* @since 1.0
*/
private List arguments;
/**
* @parameter expression="${basedir}"
* @required
* @readonly
* @since 1.0
*/
private File basedir;
/**
* Environment variables to pass to the executed program.
*
* @parameter
* @since 1.1-beta-2
*/
private Map environmentVariables = new HashMap();
/**
* The current build session instance. This is used for toolchain manager API calls.
*
* @parameter expression="${session}"
* @required
* @readonly
*/
private MavenSession session;
/**
* Exit codes to be resolved as successful execution for non-compliant applications (applications not returning 0
* for success).
*
* @parameter
* @since 1.1.1
*/
private List 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'.
*
* @parameter expression="${exec.longClasspath}" default-value="false"
* @since 1.1.2
*/
private boolean longClasspath;
public static final String CLASSPATH_TOKEN = "%classpath";
/**
* 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 {
try {
if (isSkip()) {
getLog().info("skipping execute as per configuraion");
return;
}
if (basedir == null) {
throw new IllegalStateException("basedir is null. Should not be possible.");
}
String argsProp = getSystemProperty("exec.args");
List commandArguments = new ArrayList();
if (hasCommandlineArgs()) {
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(computeClasspath(null), args[i + 2]);
commandArguments.add(tmpFile.getAbsolutePath());
i += 2;
} else if (CLASSPATH_TOKEN.equals(args[i])) {
commandArguments.add(computeClasspathString(null));
} else {
commandArguments.add(args[i]);
}
}
} else if (!isEmpty(argsProp)) {
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'");
}
} else {
if (arguments != null) {
for (int i = 0; i < arguments.size(); i++) {
Object argument = arguments.get(i);
String arg;
if (argument == null) {
throw new MojoExecutionException("Misconfigured argument, value is null. "
+ "Set the argument to an empty value if this is the required behaviour.");
} else if (argument instanceof String && isLongClassPathArgument((String) argument)) {
// 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(computeClasspath((Classpath) arguments.get(i + 1)),
(String) arguments.get(i + 2));
commandArguments.add(tmpFile.getAbsolutePath());
i += 2;
} else if (argument instanceof Classpath) {
Classpath specifiedClasspath = (Classpath) argument;
arg = computeClasspathString(specifiedClasspath);
commandArguments.add(arg);
} else {
arg = argument.toString();
commandArguments.add(arg);
}
}
}
}
Map enviro = new HashMap();
try {
Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
enviro.putAll(systemEnvVars);
} catch (IOException x) {
getLog().error("Could not assign default system enviroment variables.", x);
}
if (environmentVariables != null) {
Iterator iter = environmentVariables.keySet().iterator();
while (iter.hasNext()) {
String key = (String) iter.next();
String value = (String) environmentVariables.get(key);
enviro.put(key, value);
}
}
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() + "'");
}
}
CommandLine commandLine = getExecutablePath(enviro, workingDirectory);
String[] args = new String[commandArguments.size()];
for (int i = 0; i < commandArguments.size(); i++) {
args[i] = (String) commandArguments.get(i);
}
commandLine.addArguments(args, false);
Executor exec = getExecutor();
exec.setWorkingDirectory(workingDirectory);
// this code ensures the output gets logged vai maven logging, but at the same time prevents
// partial line output, like input prompts.
// final Log outputLog = getExecOutputLog();
// LogOutputStream stdout = new LogOutputStream()
// {
// protected void processLine( String line, int level )
// {
// outputLog.info( line );
// }
// };
//
// LogOutputStream stderr = new LogOutputStream()
// {
// protected void processLine( String line, int level )
// {
// outputLog.info( line );
// }
// };
OutputStream stdout = System.out;
OutputStream stderr = System.err;
try {
getLog().debug("Executing command line: " + commandLine);
int resultCode = executeCommandLine(exec, commandLine, enviro, stdout, stderr);
if (isResultCodeAFailure(resultCode)) {
throw new MojoExecutionException("Result of " + commandLine + " execution is: '" + resultCode
+ "'.");
}
} catch (ExecuteException e) {
throw new MojoExecutionException("Command execution failed.", e);
} catch (IOException e) {
throw new MojoExecutionException("Command execution failed.", e);
}
registerSourceRoots();
} catch (IOException e) {
throw new MojoExecutionException("I/O Error", e);
}
}
protected Executor getExecutor() {
DefaultExecutor exec = new DefaultExecutor();
if (successCodes != null) {
int size = successCodes.size();
int[] exitValues = new int[size];
for (int i = 0; i < size; i++) {
exitValues[i] = new Integer(successCodes.get(i) + "");
}
exec.setExitValues(exitValues);
}
return exec;
}
boolean isResultCodeAFailure(int result) {
if (successCodes == null || successCodes.size() == 0) {
return result != 0;
}
for (Iterator it = successCodes.iterator(); it.hasNext();) {
int code = Integer.parseInt((String) it.next());
if (code == result) {
return false;
}
}
return true;
}
private boolean isLongClassPathArgument(String arg) {
return longClasspath && ("-classpath".equals(arg) || "-cp".equals(arg));
}
private Log getExecOutputLog() {
Log log = getLog();
if (outputFile != null) {
try {
if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
getLog().warn("Could not create non existing parent directories for log file: " + outputFile);
}
PrintStream stream = new PrintStream(new FileOutputStream(outputFile));
log = new StreamLog(stream);
} catch (Exception e) {
getLog().warn("Could not open " + outputFile + ". Using default log", e);
}
}
return log;
}
/**
* 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 dependenceis, null otherwise (the default classpath will be
* used)
* @return a platform specific String representation of the classpath
*/
private String computeClasspathString(Classpath specifiedClasspath) {
List resultList = computeClasspath(specifiedClasspath);
StringBuffer theClasspath = new StringBuffer();
for (Iterator it = resultList.iterator(); it.hasNext();) {
String str = (String) it.next();
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 dependenceis, null otherwise (the default classpath will be
* used)
* @return a list of class path elements
*/
private List computeClasspath(Classpath specifiedClasspath) {
List artifacts = new ArrayList();
List theClasspathFiles = new ArrayList();
List resultList = new ArrayList();
collectProjectArtifactsAndClasspath(artifacts, theClasspathFiles);
if ((specifiedClasspath != null) && (specifiedClasspath.getDependencies() != null)) {
artifacts = filterArtifacts(artifacts, specifiedClasspath.getDependencies());
}
for (Iterator it = theClasspathFiles.iterator(); it.hasNext();) {
File f = (File) it.next();
resultList.add(f.getAbsolutePath());
}
for (Iterator it = artifacts.iterator(); it.hasNext();) {
Artifact artifact = (Artifact) it.next();
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 (Iterator it = artifacts.iterator(); it.hasNext();) {
Artifact artifact = (Artifact) it.next();
if (filter.include(artifact)) {
getLog().debug("filtering in " + artifact);
filteredArtifacts.add(artifact);
}
}
return filteredArtifacts;
}
CommandLine getExecutablePath(Map enviro, File dir) {
File execFile = new File(executable);
String exec = null;
if (execFile.exists()) {
getLog().debug("Toolchains are ignored, 'executable' parameter is set to " + executable);
exec = execFile.getAbsolutePath();
} else {
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()) {
String ex = executable.indexOf(".") < 0 ? executable + ".bat" : executable;
File f = new File(dir, ex);
if (f.exists()) {
exec = ex;
} else {
// now try to figure the path from PATH, PATHEXT env vars
// if bat file, wrap in cmd /c
String path = (String) enviro.get("PATH");
if (path != null) {
String[] elems = StringUtils.split(path, File.pathSeparator);
for (int i = 0; i < elems.length; i++) {
f = new File(new File(elems[i]), ex);
if (f.exists()) {
exec = ex;
break;
}
}
}
}
}
}
}
if (exec == null) {
exec = executable;
}
CommandLine toRet;
if (OS.isFamilyWindows() && exec.toLowerCase(Locale.getDefault()).endsWith(".bat")) {
toRet = new CommandLine("cmd");
toRet.addArgument("/c");
toRet.addArgument(exec);
} else {
toRet = new CommandLine(exec);
}
return toRet;
}
// private String[] DEFAULT_PATH_EXT = new String[] {
// .COM; .EXE; .BAT; .CMD; .VBS; .VBE; .JS; .JSE; .WSF; .WSH
// ".COM", ".EXE", ".BAT", ".CMD"
// };
private static boolean isEmpty(String string) {
return string == null || string.length() == 0;
}
//
// methods used for tests purposes - allow mocking and simulate automatic setters
//
protected int executeCommandLine(Executor exec, CommandLine commandLine, Map enviro, OutputStream out,
OutputStream err) throws ExecuteException, IOException {
exec.setStreamHandler(new PumpStreamHandler(out, err, System.in));
return exec.execute(commandLine, enviro);
}
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(List list) {
this.successCodes = list;
}
public List getSuccessCodes() {
return successCodes;
}
private Toolchain getToolchain() {
Toolchain tc = null;
try {
if (session != null) // session is null in tests..
{
ToolchainManager toolchainManager = (ToolchainManager) session.getContainer().lookup(
ToolchainManager.ROLE);
if (toolchainManager != null) {
tc = toolchainManager.getToolchainFromBuildContext("jdk", session);
}
}
} catch (ComponentLookupException componentLookupException) {
// just ignore, could happen in pre-2.0.9 builds..
}
return tc;
}
/**
* 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 = File.createTempFile("maven-exec", ".jar");
file.deleteOnExit();
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.
String cp = "";
for (Iterator it = classPath.iterator(); it.hasNext();) {
String el = (String) it.next();
// NOTE: if File points to a directory, this entry MUST end in '/'.
cp += UrlUtils.getURL(new File(el)).toExternalForm() + " ";
}
man.getMainAttributes().putValue("Manifest-Version", "1.0");
man.getMainAttributes().putValue("Class-Path", cp.trim());
man.getMainAttributes().putValue("Main-Class", mainClass);
man.write(jos);
jos.close();
return file;
}
}