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.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.Properties;
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 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.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.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.IOUtil;
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
{
/**
*
* 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;
/**
* 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;
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 );
}
FileOutputStream outputStream = null;
try
{
outputStream = new FileOutputStream( outputFile );
resultCode = executeCommandLine( exec, commandLine, enviro, outputStream );
}
finally
{
IOUtil.close( outputStream );
}
}
else if (useMavenLogger)
{
getLog().debug("Will redirect program output to Maven logger");
final String parentThreadName = Thread.currentThread().getName();
final String logSuffix = "[" + parentThreadName + "] ";
Consumer mavenOutRedirect = new Consumer()
{
@Override
public void accept(String logMessage)
{
if (quietLogs)
{
getLog().debug(logSuffix + logMessage);
}
else
{
getLog().info(logSuffix + logMessage);
}
}
};
Consumer mavenErrRedirect = new Consumer()
{
@Override
public void accept(String logMessage)
{
getLog().error(logSuffix + 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
{
Map enviro = new HashMap();
try
{
Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
for ( Map.Entry, ?> entry : systemEnvVars.entrySet() )
{
enviro.put( (String) entry.getKey(), (String) entry.getValue() );
}
}
catch ( IOException x )
{
getLog().error( "Could not assign default system enviroment variables.", x );
}
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 longClasspath && ( "-classpath".equals( arg ) || "-cp".equals( arg ) );
}
private boolean isLongModulePathArgument( String arg )
{
return longModulepath && ( "--module-path".equals( arg ) || "-p".equals( arg ) );
}
/**
* 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 )
{
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 )
{
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 ( 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 ExecuteException, 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 ExecuteException, 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 ExecuteException, 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()
{
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( toolchain, 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.
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 );
jos.close();
return file;
}
private void createArgFile( String filePath, List lines )
throws IOException
{
final String EOL = System.getProperty( "line.separator", "\\n" );
FileWriter writer = null;
try
{
writer = new FileWriter( filePath );
for ( String line : lines )
{
writer.append( line ).append( EOL );
}
}
finally
{
IOUtil.close( writer );
}
}
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 e )
{
throw new MojoExecutionException( e.getMessage() );
}
catch ( IOException e )
{
throw new MojoExecutionException( e.getMessage() );
}
finally
{
if ( tmpEnvExecFile != null )
{
tmpEnvExecFile.delete();
}
}
return results;
}
protected File createEnvWrapperFile( File envScript )
throws IOException
{
PrintWriter writer = null;
File tmpFile = null;
try
{
if ( OS.isFamilyWindows() )
{
tmpFile = File.createTempFile( "env", ".bat" );
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 = File.createTempFile( "env", ".sh" );
// tmpFile.setExecutable( true );//java 6 only
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();
}
}
finally
{
IOUtil.close( writer );
}
return tmpFile;
}
protected ProcessDestroyer getProcessDestroyer()
{
if ( processDestroyer == null )
{
processDestroyer = new ShutdownHookProcessDestroyer();
}
return processDestroyer;
}
}