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.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
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.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.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
* @version $Id: ExecMojo.java 19887 2014-07-22 20:00:24Z dantran $
* @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.
*
*
* 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", required = true )
private String executable;
/**
* 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;
/**
*
* 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 meaningfull
/**
*
* This will control if you like to fail the build if an arguments element is empty. This means that for arguments
* which would evaluate to Null
an ""
empty string will be added to the command line of
* the {@code executable} which will be called.
*
*
* @since 1.3
* @see #arguments
*/
@Parameter( defaultValue = "true" )
private boolean failWithEmptyArgument; // TODO: Remove this related to http://jira.codehaus.org/browse/MEXEC-127
/**
*
* The following will control if you like to get a warning during the build if an entry either key/value of
* environmentVariables will be evaluated to Null
an ""
empty string will be used instead.
*
*
* @since 1.3
* @see #environmentVariables
*/
@Parameter( defaultValue = "true" )
private boolean failWithNullKeyOrValueInEnvironment; // TODO: Remove this related to
// http://jira.codehaus.org/browse/MEXEC-127
/**
* @since 1.0
*/
@Parameter( readonly = true, required = true, defaultValue = "${basedir}" )
private File basedir;
/**
* Environment variables to pass to the executed program.
*
* @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.
*/
@Component
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;
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
{
if ( isSkip() )
{
getLog().info( "skipping execute as per configuraion" );
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 DefaultExecutor();
exec.setWorkingDirectory( workingDirectory );
fillSuccessCodes( exec );
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
{
resultCode = executeCommandLine( exec, commandLine, enviro, System.out, System.err );
}
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 );
}
}
private Map handleSystemEnvVariables()
throws MojoExecutionException
{
validateEnvironmentVars();
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;
}
private void validateEnvironmentVars()
throws MojoExecutionException
{
if ( environmentVariables != null )
{
for ( Map.Entry item : environmentVariables.entrySet() )
{
getLog().debug( "Entry: key:" + item.getKey() + " value:" + item.getValue() );
// The following checks are only in relationship with MEXEC-108
// (https://issues.apache.org/jira/browse/EXEC-80)
if ( item.getKey() == null )
{
if ( failWithNullKeyOrValueInEnvironment )
{
throw new MojoExecutionException(
"The defined environment contains an entry with null key. This could cause failures." );
}
else
{
getLog().warn( "The defined environment contains an entry with null key. This could cause failures." );
}
}
if ( item.getValue() == null )
{
if ( failWithNullKeyOrValueInEnvironment )
{
throw new MojoExecutionException(
"The defined environment contains an entry with null value (key:"
+ item.getKey() + "). This could cause failures." );
}
else
{
getLog().warn( "The defined environment contains an entry with null value (key:"
+ item.getKey() + "). This could cause failures." );
}
}
}
}
}
/**
* 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( computeClasspath( 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
{
for ( int i = 0; i < arguments.size(); i++ )
{
Object argument = arguments.get( i );
String arg;
if ( argument == null )
{
if ( failWithEmptyArgument )
{
throw new MojoExecutionException( "Misconfigured argument (" + ( i + 1 ) + "), value is null. "
+ "Set the argument to an empty value if this is the required behaviour." );
}
else
{
// Just add an empty string to the argument list.
commandArguments.add( "" );
}
}
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 );
}
}
}
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 ) );
}
/**
* 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( Classpath specifiedClasspath )
{
List resultList = computeClasspath( 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 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 ( File f : theClasspathFiles )
{
resultList.add( f.getAbsolutePath() );
}
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 final String[] WINDOWS_SPECIAL_EXTS = new String[] { ".bat", ".cmd" };
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 );
for ( String extension : WINDOWS_SPECIAL_EXTS )
{
String ex = !executable.contains( "." ) ? executable + extension : executable;
File f = new File( dir, ex );
if ( f.isFile() )
{
exec = ex;
}
if ( exec == null )
{
for ( String elem : paths )
{
f = new File( new File( elem ), ex );
if ( f.isFile() )
{
exec = ex;
break;
}
}
}
}
}
}
}
if ( exec == null )
{
exec = executable;
}
CommandLine toRet;
if ( ( OS.isFamilyWindows() && exec.toLowerCase( Locale.getDefault() ).endsWith( ".bat" ) )
|| OS.isFamilyWindows() && exec.toLowerCase( Locale.getDefault() ).endsWith( ".cmd" ) )
{
// run the windows batch script in isolation and exit at the end
toRet = new CommandLine( "cmd" );
toRet.addArgument( "/c" );
toRet.addArgument( exec );
}
else
{
toRet = new CommandLine( exec );
}
return toRet;
}
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: dont use BufferedOutputStream here since it delays the outputs MEXEC-138
PumpStreamHandler psh = new PumpStreamHandler( out, err, System.in );
exec.setStreamHandler( psh );
int result;
try
{
psh.start();
result = exec.execute( commandLine, enviro );
}
finally
{
psh.stop();
}
return result;
}
protected int executeCommandLine( Executor exec, CommandLine commandLine, Map enviro,
FileOutputStream outputFile )
throws ExecuteException, IOException
{
BufferedOutputStream bos = new BufferedOutputStream( outputFile );
PumpStreamHandler psh = new PumpStreamHandler( bos );
exec.setStreamHandler( psh );
int result;
try
{
psh.start();
result = exec.execute( commandLine, enviro );
}
finally
{
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( "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.
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;
}
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 );
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;
}
}