package com.googlecode.mycontainer.maven.plugin; /* * 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 <jerome@coffeebreaks.org> * @version $Id: ExecMojo.java 12386 2010-07-16 22:10:38Z rfscholte $ * @since 1.0 */ public abstract 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}" * @since 1.0 */ protected 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 <code><argument></code> or * <code><classpath></code> Can be overriden using "exec.args" env. * variable * * @parameter * @since 1.0 */ protected 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); Executor exec = new DefaultExecutor(); String[] args = new String[commandArguments.size()]; for (int i = 0; i < commandArguments.size(); i++) { args[i] = (String) commandArguments.get(i); } commandLine.addArguments(args, false); 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); } } 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; } }