View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.shared.invoker;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  import java.util.Properties;
27  
28  import org.apache.maven.shared.invoker.InvocationRequest.CheckSumPolicy;
29  import org.apache.maven.shared.invoker.InvocationRequest.ReactorFailureBehavior;
30  import org.apache.maven.shared.utils.Os;
31  import org.apache.maven.shared.utils.StringUtils;
32  import org.apache.maven.shared.utils.cli.CommandLineException;
33  import org.apache.maven.shared.utils.cli.Commandline;
34  
35  /**
36   * <p>MavenCommandLineBuilder class.</p>
37   */
38  public class MavenCommandLineBuilder {
39  
40      private static final InvokerLogger DEFAULT_LOGGER = new SystemOutLogger();
41  
42      private InvokerLogger logger = DEFAULT_LOGGER;
43  
44      private File baseDirectory;
45  
46      private File localRepositoryDirectory;
47  
48      private File mavenHome;
49  
50      private File mavenExecutable;
51  
52      /**
53       * <p>build.</p>
54       *
55       * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
56       * @return a {@link org.apache.maven.shared.utils.cli.Commandline} object.
57       * @throws org.apache.maven.shared.invoker.CommandLineConfigurationException if any.
58       */
59      public Commandline build(InvocationRequest request) throws CommandLineConfigurationException {
60  
61          Commandline cli = new Commandline();
62  
63          setupMavenHome(request);
64  
65          // discover value for working directory
66          setupBaseDirectory(request);
67          cli.setWorkingDirectory(baseDirectory);
68  
69          checkRequiredState();
70  
71          setupMavenExecutable(request);
72          cli.setExecutable(mavenExecutable.getAbsolutePath());
73  
74          // handling for OS-level envars
75          setShellEnvironment(request, cli);
76  
77          // interactive, offline, update-snapshots,
78          // debug/show-errors, checksum policy
79          setFlags(request, cli);
80  
81          // failure behavior and [eventually] forced-reactor
82          // includes/excludes, etc.
83          setReactorBehavior(request, cli);
84  
85          // local repository location
86          setLocalRepository(request, cli);
87  
88          // pom-file handling
89          setPomLocation(request, cli);
90  
91          setSettingsLocation(request, cli);
92  
93          setToolchainsLocation(request, cli);
94  
95          setProperties(request, cli);
96  
97          setProfiles(request, cli);
98  
99          setGoals(request, cli);
100 
101         setThreads(request, cli);
102 
103         setArgs(request, cli);
104 
105         return cli;
106     }
107 
108     /**
109      * <p>checkRequiredState.</p>
110      */
111     protected void checkRequiredState() {
112         if (logger == null) {
113             throw new IllegalStateException("A logger instance is required.");
114         }
115     }
116 
117     /**
118      * <p>setSettingsLocation.</p>
119      *
120      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
121      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
122      */
123     protected void setSettingsLocation(InvocationRequest request, Commandline cli) {
124         File userSettingsFile = request.getUserSettingsFile();
125 
126         if (userSettingsFile != null) {
127             try {
128                 userSettingsFile = userSettingsFile.getCanonicalFile();
129             } catch (IOException e) {
130                 logger.debug(
131                         "Failed to canonicalize user settings path: " + userSettingsFile.getAbsolutePath()
132                                 + ". Using as-is.",
133                         e);
134             }
135 
136             cli.createArg().setValue("-s");
137             cli.createArg().setValue(userSettingsFile.getPath());
138         }
139 
140         File globalSettingsFile = request.getGlobalSettingsFile();
141 
142         if (globalSettingsFile != null) {
143             try {
144                 globalSettingsFile = globalSettingsFile.getCanonicalFile();
145             } catch (IOException e) {
146                 logger.debug(
147                         "Failed to canonicalize global settings path: " + globalSettingsFile.getAbsolutePath()
148                                 + ". Using as-is.",
149                         e);
150             }
151 
152             cli.createArg().setValue("-gs");
153             cli.createArg().setValue(globalSettingsFile.getPath());
154         }
155     }
156 
157     /**
158      * <p>setToolchainsLocation.</p>
159      *
160      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
161      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
162      */
163     protected void setToolchainsLocation(InvocationRequest request, Commandline cli) {
164         File toolchainsFile = request.getToolchainsFile();
165 
166         if (toolchainsFile != null) {
167             try {
168                 toolchainsFile = toolchainsFile.getCanonicalFile();
169             } catch (IOException e) {
170                 logger.debug(
171                         "Failed to canonicalize toolchains path: " + toolchainsFile.getAbsolutePath()
172                                 + ". Using as-is.",
173                         e);
174             }
175 
176             cli.createArg().setValue("-t");
177             cli.createArg().setValue(toolchainsFile.getPath());
178         }
179     }
180 
181     /**
182      * <p>setShellEnvironment.</p>
183      *
184      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
185      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
186      */
187     protected void setShellEnvironment(InvocationRequest request, Commandline cli) {
188         cli.setShellEnvironmentInherited(request.isShellEnvironmentInherited());
189 
190         if (request.getJavaHome() != null) {
191             cli.addEnvironment("JAVA_HOME", request.getJavaHome().getAbsolutePath());
192         }
193 
194         if (request.getMavenOpts() != null) {
195             cli.addEnvironment("MAVEN_OPTS", request.getMavenOpts());
196         }
197 
198         for (Map.Entry<String, String> entry : request.getShellEnvironments().entrySet()) {
199             cli.addEnvironment(entry.getKey(), entry.getValue());
200         }
201     }
202 
203     /**
204      * <p>setProfiles.</p>
205      *
206      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
207      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
208      */
209     protected void setProfiles(InvocationRequest request, Commandline cli) {
210         List<String> profiles = request.getProfiles();
211 
212         if ((profiles != null) && !profiles.isEmpty()) {
213             cli.createArg().setValue("-P");
214             cli.createArg().setValue(String.join(",", profiles));
215         }
216     }
217 
218     /**
219      * <p>setGoals.</p>
220      *
221      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
222      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
223      * @throws org.apache.maven.shared.invoker.CommandLineConfigurationException if any.
224      */
225     protected void setGoals(InvocationRequest request, Commandline cli) throws CommandLineConfigurationException {
226         List<String> goals = request.getGoals();
227 
228         if ((goals != null) && !goals.isEmpty()) {
229             try {
230                 cli.createArg().setLine(String.join(" ", goals));
231             } catch (CommandLineException e) {
232                 throw new CommandLineConfigurationException("Problem setting goals", e);
233             }
234         }
235     }
236 
237     /**
238      * <p>setProperties.</p>
239      *
240      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
241      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
242      */
243     protected void setProperties(InvocationRequest request, Commandline cli) {
244         Properties properties = request.getProperties();
245 
246         if (properties != null) {
247             for (Entry<Object, Object> entry : properties.entrySet()) {
248                 String key = (String) entry.getKey();
249                 String value = (String) entry.getValue();
250 
251                 cli.createArg().setValue("-D");
252                 cli.createArg().setValue(key + '=' + value);
253             }
254         }
255     }
256 
257     /**
258      * <p>setPomLocation.</p>
259      *
260      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
261      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
262      */
263     protected void setPomLocation(InvocationRequest request, Commandline cli) {
264         File pom = request.getPomFile();
265         String pomFilename = request.getPomFileName();
266 
267         if (pom == null) {
268             if (pomFilename != null) {
269                 pom = new File(baseDirectory, pomFilename);
270             } else {
271                 pom = new File(baseDirectory, "pom.xml");
272             }
273         }
274 
275         try {
276             pom = pom.getCanonicalFile();
277         } catch (IOException e) {
278             logger.debug("Failed to canonicalize the POM path: " + pom + ". Using as-is.", e);
279         }
280 
281         if (pom.getParentFile().equals(baseDirectory)) {
282             // pom in project workspace
283             if (!"pom.xml".equals(pom.getName())) {
284                 logger.debug("Specified POM file is not named 'pom.xml'. "
285                         + "Using the '-f' command-line option to accommodate non-standard filename...");
286 
287                 cli.createArg().setValue("-f");
288                 cli.createArg().setValue(pom.getName());
289             }
290         } else {
291             cli.createArg().setValue("-f");
292             cli.createArg().setValue(pom.getPath());
293         }
294     }
295 
296     void setupBaseDirectory(InvocationRequest request) {
297         File baseDirectoryFromRequest = null;
298         if (request.getBaseDirectory() != null) {
299             baseDirectoryFromRequest = request.getBaseDirectory();
300         } else {
301             File pomFile = request.getPomFile();
302             if (pomFile != null) {
303                 baseDirectoryFromRequest = pomFile.getParentFile();
304             }
305         }
306 
307         if (baseDirectoryFromRequest != null) {
308             baseDirectory = baseDirectoryFromRequest;
309         }
310 
311         if (baseDirectory == null) {
312             baseDirectory = new File(System.getProperty("user.dir"));
313         } else if (baseDirectory.isFile()) {
314             logger.warn(
315                     "Specified base directory (" + baseDirectory + ") is a file." + " Using its parent directory...");
316 
317             baseDirectory = baseDirectory.getParentFile();
318         }
319 
320         try {
321             baseDirectory = baseDirectory.getCanonicalFile();
322         } catch (IOException e) {
323             logger.debug("Failed to canonicalize base directory: " + baseDirectory + ". Using as-is.", e);
324         }
325     }
326 
327     /**
328      * <p>setLocalRepository.</p>
329      *
330      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
331      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
332      */
333     protected void setLocalRepository(InvocationRequest request, Commandline cli) {
334         File localRepositoryDirectory = request.getLocalRepositoryDirectory(this.localRepositoryDirectory);
335 
336         if (localRepositoryDirectory != null) {
337             try {
338                 localRepositoryDirectory = localRepositoryDirectory.getCanonicalFile();
339             } catch (IOException e) {
340                 logger.debug(
341                         "Failed to canonicalize local repository directory: " + localRepositoryDirectory
342                                 + ". Using as-is.",
343                         e);
344             }
345 
346             if (!localRepositoryDirectory.isDirectory()) {
347                 throw new IllegalArgumentException(
348                         "Local repository location: '" + localRepositoryDirectory + "' is NOT a directory.");
349             }
350 
351             cli.createArg().setValue("-D");
352             cli.createArg().setValue("maven.repo.local=" + localRepositoryDirectory.getPath());
353         }
354     }
355 
356     /**
357      * <p>setReactorBehavior.</p>
358      *
359      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
360      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
361      */
362     protected void setReactorBehavior(InvocationRequest request, Commandline cli) {
363         // NOTE: The default is "fail-fast"
364         ReactorFailureBehavior failureBehavior = request.getReactorFailureBehavior();
365 
366         if (failureBehavior != null) {
367             if (ReactorFailureBehavior.FailAtEnd.equals(failureBehavior)) {
368                 cli.createArg().setValue("-" + ReactorFailureBehavior.FailAtEnd.getShortOption());
369             } else if (ReactorFailureBehavior.FailNever.equals(failureBehavior)) {
370                 cli.createArg().setValue("-" + ReactorFailureBehavior.FailNever.getShortOption());
371             }
372         }
373 
374         if (StringUtils.isNotEmpty(request.getResumeFrom())) {
375             cli.createArg().setValue("-rf");
376             cli.createArg().setValue(request.getResumeFrom());
377         }
378 
379         List<String> projectList = request.getProjects();
380         if (projectList != null) {
381             cli.createArg().setValue("-pl");
382             cli.createArg().setValue(String.join(",", projectList));
383 
384             if (request.isAlsoMake()) {
385                 cli.createArg().setValue("-am");
386             }
387 
388             if (request.isAlsoMakeDependents()) {
389                 cli.createArg().setValue("-amd");
390             }
391         }
392     }
393 
394     /**
395      * <p>setFlags.</p>
396      *
397      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
398      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
399      */
400     protected void setFlags(InvocationRequest request, Commandline cli) {
401         if (request.isBatchMode()) {
402             cli.createArg().setValue("-B");
403         }
404 
405         if (request.isOffline()) {
406             cli.createArg().setValue("-o");
407         }
408 
409         if (request.getUpdateSnapshotsPolicy() == UpdateSnapshotsPolicy.ALWAYS) {
410             cli.createArg().setValue("-U");
411         }
412 
413         if (request.getUpdateSnapshotsPolicy() == UpdateSnapshotsPolicy.NEVER) {
414             cli.createArg().setValue("-nsu");
415         }
416 
417         if (!request.isRecursive()) {
418             cli.createArg().setValue("-N");
419         }
420 
421         if (request.isDebug()) {
422             cli.createArg().setValue("-X");
423         }
424         // this is superseded by -X, if it exists.
425         else if (request.isShowErrors()) {
426             cli.createArg().setValue("-e");
427         }
428 
429         CheckSumPolicy checksumPolicy = request.getGlobalChecksumPolicy();
430         if (CheckSumPolicy.Fail.equals(checksumPolicy)) {
431             cli.createArg().setValue("-C");
432         } else if (CheckSumPolicy.Warn.equals(checksumPolicy)) {
433             cli.createArg().setValue("-c");
434         }
435 
436         if (request.isNonPluginUpdates()) {
437             cli.createArg().setValue("-npu");
438         }
439 
440         if (request.isShowVersion()) {
441             cli.createArg().setValue("-V");
442         }
443 
444         if (request.getBuilder() != null) {
445             cli.createArg().setValue("-b");
446             cli.createArg().setValue(request.getBuilder());
447         }
448 
449         if (request.isQuiet()) {
450             cli.createArg().setValue("-q");
451         }
452 
453         if (request.isNoTransferProgress()) {
454             cli.createArg().setValue("-ntp");
455         }
456     }
457 
458     /**
459      * <p>setThreads.</p>
460      *
461      * @param request a {@link org.apache.maven.shared.invoker.InvocationRequest} object.
462      * @param cli a {@link org.apache.maven.shared.utils.cli.Commandline} object.
463      */
464     protected void setThreads(InvocationRequest request, Commandline cli) {
465         String threads = request.getThreads();
466         if (threads != null && !threads.isEmpty()) {
467             cli.createArg().setValue("-T");
468             cli.createArg().setValue(threads);
469         }
470     }
471 
472     protected void setArgs(InvocationRequest request, Commandline cli) {
473         for (String arg : request.getArgs()) {
474             cli.createArg().setValue(arg);
475         }
476     }
477 
478     private void setupMavenHome(InvocationRequest request) {
479         if (request.getMavenHome() != null) {
480             mavenHome = request.getMavenHome();
481         } else if (System.getProperty("maven.home") != null) {
482             mavenHome = new File(System.getProperty("maven.home"));
483         }
484 
485         if (mavenHome != null && !mavenHome.isDirectory()) {
486             File binDir = mavenHome.getParentFile();
487             if (binDir != null && "bin".equals(binDir.getName())) {
488                 // ah, they specified the mvn
489                 // executable instead...
490                 mavenHome = binDir.getParentFile();
491             }
492         }
493 
494         if (mavenHome != null && !mavenHome.isDirectory()) {
495             throw new IllegalStateException("Maven home is set to: '" + mavenHome + "' which is not a directory");
496         }
497 
498         logger.debug("Using maven.home of: '" + mavenHome + "'.");
499     }
500 
501     /**
502      * <p>setupMavenExecutable.</p>
503      *
504      * @param request a Invoker request
505      * @throws org.apache.maven.shared.invoker.CommandLineConfigurationException if any.
506      */
507     protected void setupMavenExecutable(InvocationRequest request) throws CommandLineConfigurationException {
508         if (request.getMavenExecutable() != null) {
509             mavenExecutable = request.getMavenExecutable();
510         }
511 
512         if (mavenExecutable == null || !mavenExecutable.isAbsolute()) {
513             String executable;
514             if (mavenExecutable != null) {
515                 executable = mavenExecutable.getPath();
516             } else {
517                 executable = "mvn";
518             }
519 
520             // firs look in project directory
521             mavenExecutable = detectMavenExecutablePerOs(baseDirectory, executable);
522             if (mavenExecutable == null) {
523                 // next maven home
524                 mavenExecutable = detectMavenExecutablePerOs(mavenHome, "/bin/" + executable);
525             }
526 
527             if (mavenExecutable != null) {
528                 try {
529                     mavenExecutable = mavenExecutable.getCanonicalFile();
530                 } catch (IOException e) {
531                     logger.debug("Failed to canonicalize maven executable: '" + mavenExecutable + "'. Using as-is.", e);
532                 }
533             } else {
534                 throw new CommandLineConfigurationException("Maven executable: '" + executable + "'"
535                         + " not found at project dir: '" + baseDirectory + "' nor maven home: '" + mavenHome + "'");
536             }
537         }
538     }
539 
540     private File detectMavenExecutablePerOs(File baseDirectory, String executable) {
541         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
542             File executableFile = new File(baseDirectory, executable + ".ps1");
543             if (executableFile.isFile()) {
544                 return executableFile;
545             }
546 
547             executableFile = new File(baseDirectory, executable + ".cmd");
548             if (executableFile.isFile()) {
549                 return executableFile;
550             }
551 
552             executableFile = new File(baseDirectory, executable + ".bat");
553             if (executableFile.isFile()) {
554                 return executableFile;
555             }
556         }
557 
558         File executableFile = new File(baseDirectory, executable);
559         if (executableFile.isFile()) {
560             return executableFile;
561         }
562         return null;
563     }
564 
565     /**
566      * <p>Getter for the field <code>localRepositoryDirectory</code>.</p>
567      *
568      * @return a {@link java.io.File} object.
569      */
570     public File getLocalRepositoryDirectory() {
571         return localRepositoryDirectory;
572     }
573 
574     /**
575      * <p>Setter for the field <code>localRepositoryDirectory</code>.</p>
576      *
577      * @param localRepositoryDirectory a {@link java.io.File} object.
578      */
579     public void setLocalRepositoryDirectory(File localRepositoryDirectory) {
580         this.localRepositoryDirectory = localRepositoryDirectory;
581     }
582 
583     /**
584      * <p>Getter for the field <code>logger</code>.</p>
585      *
586      * @return a {@link org.apache.maven.shared.invoker.InvokerLogger} object.
587      */
588     public InvokerLogger getLogger() {
589         return logger;
590     }
591 
592     /**
593      * <p>Setter for the field <code>logger</code>.</p>
594      *
595      * @param logger a {@link org.apache.maven.shared.invoker.InvokerLogger} object.
596      */
597     public void setLogger(InvokerLogger logger) {
598         this.logger = logger;
599     }
600 
601     /**
602      * <p>Getter for the field <code>mavenHome</code>.</p>
603      *
604      * @return a {@link java.io.File} object.
605      */
606     public File getMavenHome() {
607         return mavenHome;
608     }
609 
610     /**
611      * <p>Setter for the field <code>mavenHome</code>.</p>
612      *
613      * @param mavenHome a {@link java.io.File} object.
614      */
615     public void setMavenHome(File mavenHome) {
616         this.mavenHome = mavenHome;
617     }
618 
619     /**
620      * <p>Getter for the field <code>baseDirectory</code>.</p>
621      *
622      * @return a {@link java.io.File} object.
623      */
624     public File getBaseDirectory() {
625         return baseDirectory;
626     }
627 
628     /**
629      * <p>Setter for the field <code>baseDirectory</code>.</p>
630      *
631      * @param baseDirectory a {@link java.io.File} object.
632      */
633     public void setBaseDirectory(File baseDirectory) {
634         this.baseDirectory = baseDirectory;
635     }
636 
637     /**
638      * {@code mavenExecutable} can either be relative to ${maven.home}/bin/ or absolute
639      *
640      * @param mavenExecutable the executable
641      */
642     public void setMavenExecutable(File mavenExecutable) {
643         this.mavenExecutable = mavenExecutable;
644     }
645 
646     /**
647      * <p>Getter for the field <code>mavenExecutable</code>.</p>
648      *
649      * @return a {@link java.io.File} object.
650      */
651     public File getMavenExecutable() {
652         return mavenExecutable;
653     }
654 }