Clover Coverage Report - Exec Maven Plugin 1.1
Coverage timestamp: Wed Dec 31 1969 19:00:00 EST
../../../../img/srcFileCovDistChart0.png 0% of files have more coverage
203   615   82   8.46
92   366   0.4   24
24     3.42  
1    
 
  ExecMojo       Line # 68 203 0% 82 319 0% 0.0
 
No Tests
 
1    package org.codehaus.mojo.exec;
2   
3    /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements. See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership. The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10    * with the License. You may obtain a copy of the License at
11    *
12    * http://www.apache.org/licenses/LICENSE-2.0
13    *
14    * Unless required by applicable law or agreed to in writing,
15    * software distributed under the License is distributed on an
16    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17    * KIND, either express or implied. See the License for the
18    * specific language governing permissions and limitations
19    * under the License.
20    */
21   
22    import java.io.File;
23    import java.io.FileOutputStream;
24    import java.io.IOException;
25    import java.io.OutputStream;
26    import java.io.PrintStream;
27    import java.util.ArrayList;
28    import java.util.Arrays;
29    import java.util.Collection;
30    import java.util.HashMap;
31    import java.util.Iterator;
32    import java.util.List;
33    import java.util.Locale;
34    import java.util.Map;
35    import java.util.Properties;
36    import java.util.jar.JarEntry;
37    import java.util.jar.JarOutputStream;
38    import java.util.jar.Manifest;
39   
40    import org.apache.commons.exec.CommandLine;
41    import org.apache.commons.exec.DefaultExecutor;
42    import org.apache.commons.exec.ExecuteException;
43    import org.apache.commons.exec.Executor;
44    import org.apache.commons.exec.OS;
45    import org.apache.commons.exec.PumpStreamHandler;
46    import org.apache.maven.artifact.Artifact;
47    import org.apache.maven.artifact.resolver.filter.AndArtifactFilter;
48    import org.apache.maven.artifact.resolver.filter.IncludesArtifactFilter;
49    import org.apache.maven.execution.MavenSession;
50    import org.apache.maven.plugin.MojoExecutionException;
51    import org.apache.maven.plugin.logging.Log;
52    import org.apache.maven.project.MavenProject;
53    import org.apache.maven.toolchain.Toolchain;
54    import org.apache.maven.toolchain.ToolchainManager;
55    import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
56    import org.codehaus.plexus.util.StringUtils;
57    import org.codehaus.plexus.util.cli.CommandLineUtils;
58   
59    /**
60    * A Plugin for executing external programs.
61    *
62    * @author Jerome Lacoste <jerome@coffeebreaks.org>
63    * @version $Id: ExecMojo.java 12386 2010-07-16 22:10:38Z rfscholte $
64    * @goal exec
65    * @requiresDependencyResolution test
66    * @since 1.0
67    */
 
68    public class ExecMojo extends AbstractExecMojo {
69    /**
70    * The executable. Can be a full path or a the name executable. In the latter case, the executable must be in the
71    * PATH for the execution to work.
72    *
73    * @parameter expression="${exec.executable}"
74    * @required
75    * @since 1.0
76    */
77    private String executable;
78   
79    /**
80    * The current working directory. Optional. If not specified, basedir will be used.
81    *
82    * @parameter expression="${exec.workingdir}
83    * @since 1.0
84    */
85    private File workingDirectory;
86   
87    /**
88    * Program standard and error output will be redirected to the file specified by this optional field. If not
89    * specified the standard maven logging is used.
90    *
91    * @parameter expression="${exec.outputFile}"
92    * @since 1.1-beta-2
93    */
94    private File outputFile;
95   
96    /**
97    * Can be of type <code>&lt;argument&gt;</code> or <code>&lt;classpath&gt;</code> Can be overriden using "exec.args"
98    * env. variable
99    *
100    * @parameter
101    * @since 1.0
102    */
103    private List arguments;
104   
105    /**
106    * @parameter expression="${basedir}"
107    * @required
108    * @readonly
109    * @since 1.0
110    */
111    private File basedir;
112   
113    /**
114    * Environment variables to pass to the executed program.
115    *
116    * @parameter
117    * @since 1.1-beta-2
118    */
119    private Map environmentVariables = new HashMap();
120   
121    /**
122    * The current build session instance. This is used for toolchain manager API calls.
123    *
124    * @parameter expression="${session}"
125    * @required
126    * @readonly
127    */
128    private MavenSession session;
129   
130    /**
131    * Exit codes to be resolved as successful execution for non-compliant applications (applications not returning 0
132    * for success).
133    *
134    * @parameter
135    * @since 1.1.1
136    */
137    private List successCodes;
138   
139    /**
140    * If set to true the classpath and the main class will be written to a MANIFEST.MF file and wrapped into a jar.
141    * Instead of '-classpath/-cp CLASSPATH mainClass' the exec plugin executes '-jar maven-exec.jar'.
142    *
143    * @parameter expression="${exec.longClasspath}" default-value="false"
144    * @since 1.1.2
145    */
146    private boolean longClasspath;
147   
148    public static final String CLASSPATH_TOKEN = "%classpath";
149   
150    /**
151    * priority in the execute method will be to use System properties arguments over the pom specification.
152    *
153    * @throws MojoExecutionException
154    * if a failure happens
155    */
 
156  0 toggle public void execute() throws MojoExecutionException {
157  0 try {
158  0 if (isSkip()) {
159  0 getLog().info("skipping execute as per configuraion");
160  0 return;
161    }
162   
163  0 if (basedir == null) {
164  0 throw new IllegalStateException("basedir is null. Should not be possible.");
165    }
166   
167  0 String argsProp = getSystemProperty("exec.args");
168   
169  0 List commandArguments = new ArrayList();
170   
171  0 if (hasCommandlineArgs()) {
172  0 String[] args = parseCommandlineArgs();
173  0 for (int i = 0; i < args.length; i++) {
174  0 if (isLongClassPathArgument(args[i])) {
175    // it is assumed that starting from -cp or -classpath the arguments
176    // are: -classpath/-cp %classpath mainClass
177    // the arguments are replaced with: -jar $TMP/maven-exec.jar
178    // NOTE: the jar will contain the classpath and the main class
179  0 commandArguments.add("-jar");
180  0 File tmpFile = createJar(computeClasspath(null), args[i + 2]);
181  0 commandArguments.add(tmpFile.getAbsolutePath());
182  0 i += 2;
183  0 } else if (CLASSPATH_TOKEN.equals(args[i])) {
184  0 commandArguments.add(computeClasspathString(null));
185    } else {
186  0 commandArguments.add(args[i]);
187    }
188    }
189  0 } else if (!isEmpty(argsProp)) {
190  0 getLog().debug("got arguments from system properties: " + argsProp);
191   
192  0 try {
193  0 String[] args = CommandLineUtils.translateCommandline(argsProp);
194  0 commandArguments.addAll(Arrays.asList(args));
195    } catch (Exception e) {
196  0 throw new MojoExecutionException("Couldn't parse systemproperty 'exec.args'");
197    }
198    } else {
199  0 if (arguments != null) {
200  0 for (int i = 0; i < arguments.size(); i++) {
201  0 Object argument = arguments.get(i);
202  0 String arg;
203  0 if (argument == null) {
204  0 throw new MojoExecutionException("Misconfigured argument, value is null. "
205    + "Set the argument to an empty value if this is the required behaviour.");
206  0 } else if (argument instanceof String && isLongClassPathArgument((String) argument)) {
207    // it is assumed that starting from -cp or -classpath the arguments
208    // are: -classpath/-cp %classpath mainClass
209    // the arguments are replaced with: -jar $TMP/maven-exec.jar
210    // NOTE: the jar will contain the classpath and the main class
211  0 commandArguments.add("-jar");
212  0 File tmpFile = createJar(computeClasspath((Classpath) arguments.get(i + 1)),
213    (String) arguments.get(i + 2));
214  0 commandArguments.add(tmpFile.getAbsolutePath());
215  0 i += 2;
216  0 } else if (argument instanceof Classpath) {
217  0 Classpath specifiedClasspath = (Classpath) argument;
218   
219  0 arg = computeClasspathString(specifiedClasspath);
220  0 commandArguments.add(arg);
221    } else {
222  0 arg = argument.toString();
223  0 commandArguments.add(arg);
224    }
225    }
226    }
227    }
228   
229  0 Map enviro = new HashMap();
230  0 try {
231  0 Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
232  0 enviro.putAll(systemEnvVars);
233    } catch (IOException x) {
234  0 getLog().error("Could not assign default system enviroment variables.", x);
235    }
236   
237  0 if (environmentVariables != null) {
238  0 Iterator iter = environmentVariables.keySet().iterator();
239  0 while (iter.hasNext()) {
240  0 String key = (String) iter.next();
241  0 String value = (String) environmentVariables.get(key);
242  0 enviro.put(key, value);
243    }
244    }
245   
246  0 if (workingDirectory == null) {
247  0 workingDirectory = basedir;
248    }
249   
250  0 if (!workingDirectory.exists()) {
251  0 getLog().debug("Making working directory '" + workingDirectory.getAbsolutePath() + "'.");
252  0 if (!workingDirectory.mkdirs()) {
253  0 throw new MojoExecutionException("Could not make working directory: '"
254    + workingDirectory.getAbsolutePath() + "'");
255    }
256    }
257   
258  0 CommandLine commandLine = getExecutablePath(enviro, workingDirectory);
259   
260  0 String[] args = new String[commandArguments.size()];
261  0 for (int i = 0; i < commandArguments.size(); i++) {
262  0 args[i] = (String) commandArguments.get(i);
263    }
264   
265  0 commandLine.addArguments(args, false);
266   
267  0 Executor exec = getExecutor();
268   
269  0 exec.setWorkingDirectory(workingDirectory);
270   
271    // this code ensures the output gets logged vai maven logging, but at the same time prevents
272    // partial line output, like input prompts.
273    // final Log outputLog = getExecOutputLog();
274    // LogOutputStream stdout = new LogOutputStream()
275    // {
276    // protected void processLine( String line, int level )
277    // {
278    // outputLog.info( line );
279    // }
280    // };
281    //
282    // LogOutputStream stderr = new LogOutputStream()
283    // {
284    // protected void processLine( String line, int level )
285    // {
286    // outputLog.info( line );
287    // }
288    // };
289  0 OutputStream stdout = System.out;
290  0 OutputStream stderr = System.err;
291   
292  0 try {
293  0 getLog().debug("Executing command line: " + commandLine);
294   
295  0 int resultCode = executeCommandLine(exec, commandLine, enviro, stdout, stderr);
296   
297  0 if (isResultCodeAFailure(resultCode)) {
298  0 throw new MojoExecutionException("Result of " + commandLine + " execution is: '" + resultCode
299    + "'.");
300    }
301    } catch (ExecuteException e) {
302  0 throw new MojoExecutionException("Command execution failed.", e);
303   
304    } catch (IOException e) {
305  0 throw new MojoExecutionException("Command execution failed.", e);
306    }
307   
308  0 registerSourceRoots();
309    } catch (IOException e) {
310  0 throw new MojoExecutionException("I/O Error", e);
311    }
312    }
313   
 
314  0 toggle protected Executor getExecutor() {
315  0 DefaultExecutor exec = new DefaultExecutor();
316   
317  0 if (successCodes != null) {
318  0 int size = successCodes.size();
319  0 int[] exitValues = new int[size];
320  0 for (int i = 0; i < size; i++) {
321  0 exitValues[i] = new Integer(successCodes.get(i) + "");
322    }
323  0 exec.setExitValues(exitValues);
324    }
325  0 return exec;
326    }
327   
 
328  0 toggle boolean isResultCodeAFailure(int result) {
329  0 if (successCodes == null || successCodes.size() == 0) {
330  0 return result != 0;
331    }
332  0 for (Iterator it = successCodes.iterator(); it.hasNext();) {
333  0 int code = Integer.parseInt((String) it.next());
334  0 if (code == result) {
335  0 return false;
336    }
337    }
338  0 return true;
339    }
340   
 
341  0 toggle private boolean isLongClassPathArgument(String arg) {
342  0 return longClasspath && ("-classpath".equals(arg) || "-cp".equals(arg));
343    }
344   
 
345  0 toggle private Log getExecOutputLog() {
346  0 Log log = getLog();
347  0 if (outputFile != null) {
348  0 try {
349  0 if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
350  0 getLog().warn("Could not create non existing parent directories for log file: " + outputFile);
351    }
352  0 PrintStream stream = new PrintStream(new FileOutputStream(outputFile));
353   
354  0 log = new StreamLog(stream);
355    } catch (Exception e) {
356  0 getLog().warn("Could not open " + outputFile + ". Using default log", e);
357    }
358    }
359   
360  0 return log;
361    }
362   
363    /**
364    * Compute the classpath from the specified Classpath. The computed classpath is based on the classpathScope. The
365    * plugin cannot know from maven the phase it is executed in. So we have to depend on the user to tell us he wants
366    * the scope in which the plugin is expected to be executed.
367    *
368    * @param specifiedClasspath
369    * Non null when the user restricted the dependenceis, null otherwise (the default classpath will be
370    * used)
371    * @return a platform specific String representation of the classpath
372    */
 
373  0 toggle private String computeClasspathString(Classpath specifiedClasspath) {
374  0 List resultList = computeClasspath(specifiedClasspath);
375  0 StringBuffer theClasspath = new StringBuffer();
376   
377  0 for (Iterator it = resultList.iterator(); it.hasNext();) {
378  0 String str = (String) it.next();
379  0 addToClasspath(theClasspath, str);
380    }
381   
382  0 return theClasspath.toString();
383    }
384   
385    /**
386    * Compute the classpath from the specified Classpath. The computed classpath is based on the classpathScope. The
387    * plugin cannot know from maven the phase it is executed in. So we have to depend on the user to tell us he wants
388    * the scope in which the plugin is expected to be executed.
389    *
390    * @param specifiedClasspath
391    * Non null when the user restricted the dependenceis, null otherwise (the default classpath will be
392    * used)
393    * @return a list of class path elements
394    */
 
395  0 toggle private List computeClasspath(Classpath specifiedClasspath) {
396  0 List artifacts = new ArrayList();
397  0 List theClasspathFiles = new ArrayList();
398  0 List resultList = new ArrayList();
399   
400  0 collectProjectArtifactsAndClasspath(artifacts, theClasspathFiles);
401   
402  0 if ((specifiedClasspath != null) && (specifiedClasspath.getDependencies() != null)) {
403  0 artifacts = filterArtifacts(artifacts, specifiedClasspath.getDependencies());
404    }
405   
406  0 for (Iterator it = theClasspathFiles.iterator(); it.hasNext();) {
407  0 File f = (File) it.next();
408  0 resultList.add(f.getAbsolutePath());
409    }
410   
411  0 for (Iterator it = artifacts.iterator(); it.hasNext();) {
412  0 Artifact artifact = (Artifact) it.next();
413  0 getLog().debug("dealing with " + artifact);
414  0 resultList.add(artifact.getFile().getAbsolutePath());
415    }
416   
417  0 return resultList;
418    }
419   
 
420  0 toggle private static void addToClasspath(StringBuffer theClasspath, String toAdd) {
421  0 if (theClasspath.length() > 0) {
422  0 theClasspath.append(File.pathSeparator);
423    }
424  0 theClasspath.append(toAdd);
425    }
426   
 
427  0 toggle private List filterArtifacts(List artifacts, Collection dependencies) {
428  0 AndArtifactFilter filter = new AndArtifactFilter();
429   
430  0 filter.add(new IncludesArtifactFilter(new ArrayList(dependencies))); // gosh
431   
432  0 List filteredArtifacts = new ArrayList();
433  0 for (Iterator it = artifacts.iterator(); it.hasNext();) {
434  0 Artifact artifact = (Artifact) it.next();
435  0 if (filter.include(artifact)) {
436  0 getLog().debug("filtering in " + artifact);
437  0 filteredArtifacts.add(artifact);
438    }
439    }
440  0 return filteredArtifacts;
441    }
442   
 
443  0 toggle CommandLine getExecutablePath(Map enviro, File dir) {
444  0 File execFile = new File(executable);
445  0 String exec = null;
446  0 if (execFile.exists()) {
447  0 getLog().debug("Toolchains are ignored, 'executable' parameter is set to " + executable);
448  0 exec = execFile.getAbsolutePath();
449    } else {
450  0 Toolchain tc = getToolchain();
451   
452    // if the file doesn't exist & toolchain is null, the exec is probably in the PATH...
453    // we should probably also test for isFile and canExecute, but the second one is only
454    // available in SDK 6.
455  0 if (tc != null) {
456  0 getLog().info("Toolchain in exec-maven-plugin: " + tc);
457  0 exec = tc.findTool(executable);
458    } else {
459  0 if (OS.isFamilyWindows()) {
460  0 String ex = executable.indexOf(".") < 0 ? executable + ".bat" : executable;
461  0 File f = new File(dir, ex);
462  0 if (f.exists()) {
463  0 exec = ex;
464    } else {
465    // now try to figure the path from PATH, PATHEXT env vars
466    // if bat file, wrap in cmd /c
467  0 String path = (String) enviro.get("PATH");
468  0 if (path != null) {
469  0 String[] elems = StringUtils.split(path, File.pathSeparator);
470  0 for (int i = 0; i < elems.length; i++) {
471  0 f = new File(new File(elems[i]), ex);
472  0 if (f.exists()) {
473  0 exec = ex;
474  0 break;
475    }
476    }
477    }
478    }
479    }
480    }
481    }
482   
483  0 if (exec == null) {
484  0 exec = executable;
485    }
486   
487  0 CommandLine toRet;
488  0 if (OS.isFamilyWindows() && exec.toLowerCase(Locale.getDefault()).endsWith(".bat")) {
489  0 toRet = new CommandLine("cmd");
490  0 toRet.addArgument("/c");
491  0 toRet.addArgument(exec);
492    } else {
493  0 toRet = new CommandLine(exec);
494    }
495   
496  0 return toRet;
497    }
498   
499    // private String[] DEFAULT_PATH_EXT = new String[] {
500    // .COM; .EXE; .BAT; .CMD; .VBS; .VBE; .JS; .JSE; .WSF; .WSH
501    // ".COM", ".EXE", ".BAT", ".CMD"
502    // };
503   
 
504  0 toggle private static boolean isEmpty(String string) {
505  0 return string == null || string.length() == 0;
506    }
507   
508    //
509    // methods used for tests purposes - allow mocking and simulate automatic setters
510    //
511   
 
512  0 toggle protected int executeCommandLine(Executor exec, CommandLine commandLine, Map enviro, OutputStream out,
513    OutputStream err) throws ExecuteException, IOException {
514  0 exec.setStreamHandler(new PumpStreamHandler(out, err, System.in));
515  0 return exec.execute(commandLine, enviro);
516    }
517   
 
518  0 toggle void setExecutable(String executable) {
519  0 this.executable = executable;
520    }
521   
 
522  0 toggle String getExecutable() {
523  0 return executable;
524    }
525   
 
526  0 toggle void setWorkingDirectory(String workingDir) {
527  0 setWorkingDirectory(new File(workingDir));
528    }
529   
 
530  0 toggle void setWorkingDirectory(File workingDir) {
531  0 this.workingDirectory = workingDir;
532    }
533   
 
534  0 toggle void setArguments(List arguments) {
535  0 this.arguments = arguments;
536    }
537   
 
538  0 toggle void setBasedir(File basedir) {
539  0 this.basedir = basedir;
540    }
541   
 
542  0 toggle void setProject(MavenProject project) {
543  0 this.project = project;
544    }
545   
 
546  0 toggle protected String getSystemProperty(String key) {
547  0 return System.getProperty(key);
548    }
549   
 
550  0 toggle public void setSuccessCodes(List list) {
551  0 this.successCodes = list;
552    }
553   
 
554  0 toggle public List getSuccessCodes() {
555  0 return successCodes;
556    }
557   
 
558  0 toggle private Toolchain getToolchain() {
559  0 Toolchain tc = null;
560   
561  0 try {
562  0 if (session != null) // session is null in tests..
563    {
564  0 ToolchainManager toolchainManager = (ToolchainManager) session.getContainer().lookup(
565    ToolchainManager.ROLE);
566   
567  0 if (toolchainManager != null) {
568  0 tc = toolchainManager.getToolchainFromBuildContext("jdk", session);
569    }
570    }
571    } catch (ComponentLookupException componentLookupException) {
572    // just ignore, could happen in pre-2.0.9 builds..
573    }
574  0 return tc;
575    }
576   
577    /**
578    * Create a jar with just a manifest containing a Main-Class entry for SurefireBooter and a Class-Path entry for all
579    * classpath elements. Copied from surefire (ForkConfiguration#createJar())
580    *
581    * @param classPath
582    * List&lt;String> of all classpath elements.
583    * @return
584    * @throws IOException
585    */
 
586  0 toggle private File createJar(List classPath, String mainClass) throws IOException {
587  0 File file = File.createTempFile("maven-exec", ".jar");
588  0 file.deleteOnExit();
589  0 FileOutputStream fos = new FileOutputStream(file);
590  0 JarOutputStream jos = new JarOutputStream(fos);
591  0 jos.setLevel(JarOutputStream.STORED);
592  0 JarEntry je = new JarEntry("META-INF/MANIFEST.MF");
593  0 jos.putNextEntry(je);
594   
595  0 Manifest man = new Manifest();
596   
597    // we can't use StringUtils.join here since we need to add a '/' to
598    // the end of directory entries - otherwise the jvm will ignore them.
599  0 String cp = "";
600  0 for (Iterator it = classPath.iterator(); it.hasNext();) {
601  0 String el = (String) it.next();
602    // NOTE: if File points to a directory, this entry MUST end in '/'.
603  0 cp += UrlUtils.getURL(new File(el)).toExternalForm() + " ";
604    }
605   
606  0 man.getMainAttributes().putValue("Manifest-Version", "1.0");
607  0 man.getMainAttributes().putValue("Class-Path", cp.trim());
608  0 man.getMainAttributes().putValue("Main-Class", mainClass);
609   
610  0 man.write(jos);
611  0 jos.close();
612   
613  0 return file;
614    }
615    }