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