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.api.cli;
20  
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.nio.file.Path;
24  import java.nio.file.Paths;
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Optional;
30  
31  import org.apache.maven.api.annotations.Experimental;
32  import org.apache.maven.api.annotations.Immutable;
33  import org.apache.maven.api.annotations.Nonnull;
34  import org.apache.maven.api.annotations.Nullable;
35  
36  import static java.util.Objects.requireNonNull;
37  
38  /**
39   * Represents a request to execute Maven with command-line arguments.
40   * This interface encapsulates all the necessary information needed to execute
41   * Maven command with arguments. The arguments are not parsed, they are just passed over
42   * to executed tool.
43   *
44   * @since 4.0.0
45   */
46  @Immutable
47  @Experimental
48  public interface ExecutorRequest {
49      /**
50       * The Maven command.
51       */
52      String MVN = "mvn";
53  
54      /**
55       * The command to execute, ie "mvn".
56       */
57      @Nonnull
58      String command();
59  
60      /**
61       * The immutable list of arguments to pass to the command.
62       */
63      @Nonnull
64      List<String> arguments();
65  
66      /**
67       * Returns the current working directory for the Maven execution.
68       * This is typically the directory from which Maven was invoked.
69       *
70       * @return the current working directory path
71       */
72      @Nonnull
73      Path cwd();
74  
75      /**
76       * Returns the Maven installation directory.
77       * This is usually set by the Maven launcher script using the "maven.home" system property.
78       *
79       * @return the Maven installation directory path
80       */
81      @Nonnull
82      Path installationDirectory();
83  
84      /**
85       * Returns the user's home directory.
86       * This is typically obtained from the "user.home" system property.
87       *
88       * @return the user's home directory path
89       */
90      @Nonnull
91      Path userHomeDirectory();
92  
93      /**
94       * Returns the map of Java System Properties to set before executing process.
95       *
96       * @return an Optional containing the map of Java System Properties, or empty if not specified
97       */
98      @Nonnull
99      Optional<Map<String, String>> jvmSystemProperties();
100 
101     /**
102      * Returns the map of environment variables to set before executing process.
103      * This property is used ONLY by executors that spawn a new JVM.
104      *
105      * @return an Optional containing the map of environment variables, or empty if not specified
106      */
107     @Nonnull
108     Optional<Map<String, String>> environmentVariables();
109 
110     /**
111      * Returns the list of extra JVM arguments to be passed to the forked process.
112      * These arguments allow for customization of the JVM environment in which tool will run.
113      * This property is used ONLY by executors that spawn a new JVM.
114      *
115      * @return an Optional containing the list of extra JVM arguments, or empty if not specified
116      */
117     @Nonnull
118     Optional<List<String>> jvmArguments();
119 
120     /**
121      * Optional provider for STD in of the Maven. If given, this provider will be piped into std input of
122      * Maven.
123      *
124      * @return an Optional containing the stdin provider, or empty if not specified.
125      */
126     Optional<InputStream> stdIn();
127 
128     /**
129      * Optional consumer for STD out of the Maven. If given, this consumer will get all output from the std out of
130      * Maven. Note: whether consumer gets to consume anything depends on invocation arguments passed in
131      * {@link #arguments()}, as if log file is set, not much will go to stdout.
132      *
133      * @return an Optional containing the stdout consumer, or empty if not specified.
134      */
135     Optional<OutputStream> stdOut();
136 
137     /**
138      * Optional consumer for STD err of the Maven. If given, this consumer will get all output from the std err of
139      * Maven. Note: whether consumer gets to consume anything depends on invocation arguments passed in
140      * {@link #arguments()}, as if log file is set, not much will go to stderr.
141      *
142      * @return an Optional containing the stderr consumer, or empty if not specified.
143      */
144     Optional<OutputStream> stdErr();
145 
146     /**
147      * Indicate if {@code ~/.mavenrc} should be skipped during execution.
148      * <p>
149      * Affected only for forked executor by adding MAVEN_SKIP_RC environment variable
150      */
151     boolean skipMavenRc();
152 
153     /**
154      * Returns {@link Builder} created from this instance.
155      */
156     @Nonnull
157     default Builder toBuilder() {
158         return new Builder(
159                 command(),
160                 arguments(),
161                 cwd(),
162                 installationDirectory(),
163                 userHomeDirectory(),
164                 jvmSystemProperties().orElse(null),
165                 environmentVariables().orElse(null),
166                 jvmArguments().orElse(null),
167                 stdIn().orElse(null),
168                 stdOut().orElse(null),
169                 stdErr().orElse(null),
170                 skipMavenRc());
171     }
172 
173     /**
174      * Returns new builder pre-set to run Maven. The discovery of maven home is attempted, user cwd and home are
175      * also discovered by standard means.
176      */
177     @Nonnull
178     static Builder mavenBuilder(@Nullable Path installationDirectory) {
179         return new Builder(
180                 MVN,
181                 null,
182                 getCanonicalPath(Paths.get(System.getProperty("user.dir"))),
183                 installationDirectory != null
184                         ? getCanonicalPath(installationDirectory)
185                         : discoverInstallationDirectory(),
186                 getCanonicalPath(Paths.get(System.getProperty("user.home"))),
187                 null,
188                 null,
189                 null,
190                 null,
191                 null,
192                 null,
193                 false);
194     }
195 
196     class Builder {
197         private String command;
198         private List<String> arguments;
199         private Path cwd;
200         private Path installationDirectory;
201         private Path userHomeDirectory;
202         private Map<String, String> jvmSystemProperties;
203         private Map<String, String> environmentVariables;
204         private List<String> jvmArguments;
205         private InputStream stdIn;
206         private OutputStream stdOut;
207         private OutputStream stdErr;
208         private boolean skipMavenRc;
209 
210         private Builder() {}
211 
212         @SuppressWarnings("ParameterNumber")
213         private Builder(
214                 String command,
215                 List<String> arguments,
216                 Path cwd,
217                 Path installationDirectory,
218                 Path userHomeDirectory,
219                 Map<String, String> jvmSystemProperties,
220                 Map<String, String> environmentVariables,
221                 List<String> jvmArguments,
222                 InputStream stdIn,
223                 OutputStream stdOut,
224                 OutputStream stdErr,
225                 boolean skipMavenRc) {
226             this.command = command;
227             this.arguments = arguments;
228             this.cwd = cwd;
229             this.installationDirectory = installationDirectory;
230             this.userHomeDirectory = userHomeDirectory;
231             this.jvmSystemProperties = jvmSystemProperties;
232             this.environmentVariables = environmentVariables;
233             this.jvmArguments = jvmArguments;
234             this.stdIn = stdIn;
235             this.stdOut = stdOut;
236             this.stdErr = stdErr;
237             this.skipMavenRc = skipMavenRc;
238         }
239 
240         @Nonnull
241         public Builder command(String command) {
242             this.command = requireNonNull(command, "command");
243             return this;
244         }
245 
246         @Nonnull
247         public Builder arguments(List<String> arguments) {
248             this.arguments = requireNonNull(arguments, "arguments");
249             return this;
250         }
251 
252         @Nonnull
253         public Builder argument(String argument) {
254             if (arguments == null) {
255                 arguments = new ArrayList<>();
256             }
257             this.arguments.add(requireNonNull(argument, "argument"));
258             return this;
259         }
260 
261         @Nonnull
262         public Builder cwd(Path cwd) {
263             this.cwd = getCanonicalPath(requireNonNull(cwd, "cwd"));
264             return this;
265         }
266 
267         @Nonnull
268         public Builder installationDirectory(Path installationDirectory) {
269             this.installationDirectory =
270                     getCanonicalPath(requireNonNull(installationDirectory, "installationDirectory"));
271             return this;
272         }
273 
274         @Nonnull
275         public Builder userHomeDirectory(Path userHomeDirectory) {
276             this.userHomeDirectory = getCanonicalPath(requireNonNull(userHomeDirectory, "userHomeDirectory"));
277             return this;
278         }
279 
280         @Nonnull
281         public Builder jvmSystemProperties(Map<String, String> jvmSystemProperties) {
282             this.jvmSystemProperties = jvmSystemProperties;
283             return this;
284         }
285 
286         @Nonnull
287         public Builder jvmSystemProperty(String key, String value) {
288             requireNonNull(key, "env key");
289             requireNonNull(value, "env value");
290             if (jvmSystemProperties == null) {
291                 this.jvmSystemProperties = new HashMap<>();
292             }
293             this.jvmSystemProperties.put(key, value);
294             return this;
295         }
296 
297         @Nonnull
298         public Builder environmentVariables(Map<String, String> environmentVariables) {
299             this.environmentVariables = environmentVariables;
300             return this;
301         }
302 
303         @Nonnull
304         public Builder environmentVariable(String key, String value) {
305             requireNonNull(key, "env key");
306             requireNonNull(value, "env value");
307             if (environmentVariables == null) {
308                 this.environmentVariables = new HashMap<>();
309             }
310             this.environmentVariables.put(key, value);
311             return this;
312         }
313 
314         @Nonnull
315         public Builder jvmArguments(List<String> jvmArguments) {
316             this.jvmArguments = jvmArguments;
317             return this;
318         }
319 
320         @Nonnull
321         public Builder jvmArgument(String jvmArgument) {
322             if (jvmArguments == null) {
323                 jvmArguments = new ArrayList<>();
324             }
325             this.jvmArguments.add(requireNonNull(jvmArgument, "jvmArgument"));
326             return this;
327         }
328 
329         @Nonnull
330         public Builder stdIn(InputStream stdIn) {
331             this.stdIn = stdIn;
332             return this;
333         }
334 
335         @Nonnull
336         public Builder stdOut(OutputStream stdOut) {
337             this.stdOut = stdOut;
338             return this;
339         }
340 
341         @Nonnull
342         public Builder stdErr(OutputStream stdErr) {
343             this.stdErr = stdErr;
344             return this;
345         }
346 
347         @Nonnull
348         public Builder skipMavenRc(boolean skipMavenRc) {
349             this.skipMavenRc = skipMavenRc;
350             return this;
351         }
352 
353         @Nonnull
354         public ExecutorRequest build() {
355             return new Impl(
356                     command,
357                     arguments,
358                     cwd,
359                     installationDirectory,
360                     userHomeDirectory,
361                     jvmSystemProperties,
362                     environmentVariables,
363                     jvmArguments,
364                     stdIn,
365                     stdOut,
366                     stdErr,
367                     skipMavenRc);
368         }
369 
370         private static class Impl implements ExecutorRequest {
371             private final String command;
372             private final List<String> arguments;
373             private final Path cwd;
374             private final Path installationDirectory;
375             private final Path userHomeDirectory;
376             private final Map<String, String> jvmSystemProperties;
377             private final Map<String, String> environmentVariables;
378             private final List<String> jvmArguments;
379             private final InputStream stdIn;
380             private final OutputStream stdOut;
381             private final OutputStream stdErr;
382             private final boolean skipMavenRc;
383 
384             @SuppressWarnings("ParameterNumber")
385             private Impl(
386                     String command,
387                     List<String> arguments,
388                     Path cwd,
389                     Path installationDirectory,
390                     Path userHomeDirectory,
391                     Map<String, String> jvmSystemProperties,
392                     Map<String, String> environmentVariables,
393                     List<String> jvmArguments,
394                     InputStream stdIn,
395                     OutputStream stdOut,
396                     OutputStream stdErr,
397                     boolean skipMavenRc) {
398                 this.command = requireNonNull(command);
399                 this.arguments = arguments == null ? List.of() : List.copyOf(arguments);
400                 this.cwd = getCanonicalPath(requireNonNull(cwd));
401                 this.installationDirectory = getCanonicalPath(requireNonNull(installationDirectory));
402                 this.userHomeDirectory = getCanonicalPath(requireNonNull(userHomeDirectory));
403                 this.jvmSystemProperties = jvmSystemProperties != null && !jvmSystemProperties.isEmpty()
404                         ? Map.copyOf(jvmSystemProperties)
405                         : null;
406                 this.environmentVariables = environmentVariables != null && !environmentVariables.isEmpty()
407                         ? Map.copyOf(environmentVariables)
408                         : null;
409                 this.jvmArguments = jvmArguments != null && !jvmArguments.isEmpty() ? List.copyOf(jvmArguments) : null;
410                 this.stdIn = stdIn;
411                 this.stdOut = stdOut;
412                 this.stdErr = stdErr;
413                 this.skipMavenRc = skipMavenRc;
414             }
415 
416             @Override
417             public String command() {
418                 return command;
419             }
420 
421             @Override
422             public List<String> arguments() {
423                 return arguments;
424             }
425 
426             @Override
427             public Path cwd() {
428                 return cwd;
429             }
430 
431             @Override
432             public Path installationDirectory() {
433                 return installationDirectory;
434             }
435 
436             @Override
437             public Path userHomeDirectory() {
438                 return userHomeDirectory;
439             }
440 
441             @Override
442             public Optional<Map<String, String>> jvmSystemProperties() {
443                 return Optional.ofNullable(jvmSystemProperties);
444             }
445 
446             @Override
447             public Optional<Map<String, String>> environmentVariables() {
448                 return Optional.ofNullable(environmentVariables);
449             }
450 
451             @Override
452             public Optional<List<String>> jvmArguments() {
453                 return Optional.ofNullable(jvmArguments);
454             }
455 
456             @Override
457             public Optional<InputStream> stdIn() {
458                 return Optional.ofNullable(stdIn);
459             }
460 
461             @Override
462             public Optional<OutputStream> stdOut() {
463                 return Optional.ofNullable(stdOut);
464             }
465 
466             @Override
467             public Optional<OutputStream> stdErr() {
468                 return Optional.ofNullable(stdErr);
469             }
470 
471             @Override
472             public boolean skipMavenRc() {
473                 return skipMavenRc;
474             }
475 
476             @Override
477             public String toString() {
478                 return getClass().getSimpleName() + "{" + "command='"
479                         + command + '\'' + ", arguments="
480                         + arguments + ", cwd="
481                         + cwd + ", installationDirectory="
482                         + installationDirectory + ", userHomeDirectory="
483                         + userHomeDirectory + ", jvmSystemProperties="
484                         + jvmSystemProperties + ", environmentVariables="
485                         + environmentVariables + ", jvmArguments="
486                         + jvmArguments + ", stdinProvider="
487                         + stdIn + ", stdoutConsumer="
488                         + stdOut + ", stderrConsumer="
489                         + stdErr + ", skipMavenRc="
490                         + skipMavenRc + "}";
491             }
492         }
493     }
494 
495     @Nonnull
496     static Path discoverInstallationDirectory() {
497         String mavenHome = System.getProperty("maven.home");
498         if (mavenHome == null) {
499             throw new ExecutorException("requires maven.home Java System Property set");
500         }
501         return getCanonicalPath(Paths.get(mavenHome));
502     }
503 
504     @Nonnull
505     static Path discoverUserHomeDirectory() {
506         String userHome = System.getProperty("user.home");
507         if (userHome == null) {
508             throw new ExecutorException("requires user.home Java System Property set");
509         }
510         return getCanonicalPath(Paths.get(userHome));
511     }
512 
513     @Nonnull
514     static Path getCanonicalPath(Path path) {
515         requireNonNull(path, "path");
516         return path.toAbsolutePath().normalize();
517     }
518 }