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.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 consumer for STD out of the Maven. If given, this consumer will get all output from the std out of
122      * Maven. Note: whether consumer gets to consume anything depends on invocation arguments passed in
123      * {@link #arguments()}, as if log file is set, not much will go to stdout.
124      *
125      * @return an Optional containing the stdout consumer, or empty if not specified.
126      */
127     Optional<OutputStream> stdoutConsumer();
128 
129     /**
130      * Optional consumer for STD err of the Maven. If given, this consumer will get all output from the std err 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 stderr.
133      *
134      * @return an Optional containing the stderr consumer, or empty if not specified.
135      */
136     Optional<OutputStream> stderrConsumer();
137 
138     /**
139      * Returns {@link Builder} created from this instance.
140      */
141     @Nonnull
142     default Builder toBuilder() {
143         return new Builder(
144                 command(),
145                 arguments(),
146                 cwd(),
147                 installationDirectory(),
148                 userHomeDirectory(),
149                 jvmSystemProperties().orElse(null),
150                 environmentVariables().orElse(null),
151                 jvmArguments().orElse(null),
152                 stdoutConsumer().orElse(null),
153                 stderrConsumer().orElse(null));
154     }
155 
156     /**
157      * Returns new builder pre-set to run Maven. The discovery of maven home is attempted, user cwd and home are
158      * also discovered by standard means.
159      */
160     @Nonnull
161     static Builder mavenBuilder(@Nullable Path installationDirectory) {
162         return new Builder(
163                 MVN,
164                 null,
165                 getCanonicalPath(Paths.get(System.getProperty("user.dir"))),
166                 installationDirectory != null ? getCanonicalPath(installationDirectory) : discoverMavenHome(),
167                 getCanonicalPath(Paths.get(System.getProperty("user.home"))),
168                 null,
169                 null,
170                 null,
171                 null,
172                 null);
173     }
174 
175     class Builder {
176         private String command;
177         private List<String> arguments;
178         private Path cwd;
179         private Path installationDirectory;
180         private Path userHomeDirectory;
181         private Map<String, String> jvmSystemProperties;
182         private Map<String, String> environmentVariables;
183         private List<String> jvmArguments;
184         private OutputStream stdoutConsumer;
185         private OutputStream stderrConsumer;
186 
187         private Builder() {}
188 
189         @SuppressWarnings("ParameterNumber")
190         private Builder(
191                 String command,
192                 List<String> arguments,
193                 Path cwd,
194                 Path installationDirectory,
195                 Path userHomeDirectory,
196                 Map<String, String> jvmSystemProperties,
197                 Map<String, String> environmentVariables,
198                 List<String> jvmArguments,
199                 OutputStream stdoutConsumer,
200                 OutputStream stderrConsumer) {
201             this.command = command;
202             this.arguments = arguments;
203             this.cwd = cwd;
204             this.installationDirectory = installationDirectory;
205             this.userHomeDirectory = userHomeDirectory;
206             this.jvmSystemProperties = jvmSystemProperties;
207             this.environmentVariables = environmentVariables;
208             this.jvmArguments = jvmArguments;
209             this.stdoutConsumer = stdoutConsumer;
210             this.stderrConsumer = stderrConsumer;
211         }
212 
213         @Nonnull
214         public Builder command(String command) {
215             this.command = requireNonNull(command, "command");
216             return this;
217         }
218 
219         @Nonnull
220         public Builder arguments(List<String> arguments) {
221             this.arguments = requireNonNull(arguments, "arguments");
222             return this;
223         }
224 
225         @Nonnull
226         public Builder argument(String argument) {
227             if (arguments == null) {
228                 arguments = new ArrayList<>();
229             }
230             this.arguments.add(requireNonNull(argument, "argument"));
231             return this;
232         }
233 
234         @Nonnull
235         public Builder cwd(Path cwd) {
236             this.cwd = getCanonicalPath(requireNonNull(cwd, "cwd"));
237             return this;
238         }
239 
240         @Nonnull
241         public Builder installationDirectory(Path installationDirectory) {
242             this.installationDirectory =
243                     getCanonicalPath(requireNonNull(installationDirectory, "installationDirectory"));
244             return this;
245         }
246 
247         @Nonnull
248         public Builder userHomeDirectory(Path userHomeDirectory) {
249             this.userHomeDirectory = getCanonicalPath(requireNonNull(userHomeDirectory, "userHomeDirectory"));
250             return this;
251         }
252 
253         @Nonnull
254         public Builder jvmSystemProperties(Map<String, String> jvmSystemProperties) {
255             this.jvmSystemProperties = jvmSystemProperties;
256             return this;
257         }
258 
259         @Nonnull
260         public Builder jvmSystemProperty(String key, String value) {
261             requireNonNull(key, "env key");
262             requireNonNull(value, "env value");
263             if (jvmSystemProperties == null) {
264                 this.jvmSystemProperties = new HashMap<>();
265             }
266             this.jvmSystemProperties.put(key, value);
267             return this;
268         }
269 
270         @Nonnull
271         public Builder environmentVariables(Map<String, String> environmentVariables) {
272             this.environmentVariables = environmentVariables;
273             return this;
274         }
275 
276         @Nonnull
277         public Builder environmentVariable(String key, String value) {
278             requireNonNull(key, "env key");
279             requireNonNull(value, "env value");
280             if (environmentVariables == null) {
281                 this.environmentVariables = new HashMap<>();
282             }
283             this.environmentVariables.put(key, value);
284             return this;
285         }
286 
287         @Nonnull
288         public Builder jvmArguments(List<String> jvmArguments) {
289             this.jvmArguments = jvmArguments;
290             return this;
291         }
292 
293         @Nonnull
294         public Builder jvmArgument(String jvmArgument) {
295             if (jvmArguments == null) {
296                 jvmArguments = new ArrayList<>();
297             }
298             this.jvmArguments.add(requireNonNull(jvmArgument, "jvmArgument"));
299             return this;
300         }
301 
302         @Nonnull
303         public Builder stdoutConsumer(OutputStream stdoutConsumer) {
304             this.stdoutConsumer = stdoutConsumer;
305             return this;
306         }
307 
308         @Nonnull
309         public Builder stderrConsumer(OutputStream stderrConsumer) {
310             this.stderrConsumer = stderrConsumer;
311             return this;
312         }
313 
314         @Nonnull
315         public ExecutorRequest build() {
316             return new Impl(
317                     command,
318                     arguments,
319                     cwd,
320                     installationDirectory,
321                     userHomeDirectory,
322                     jvmSystemProperties,
323                     environmentVariables,
324                     jvmArguments,
325                     stdoutConsumer,
326                     stderrConsumer);
327         }
328 
329         private static class Impl implements ExecutorRequest {
330             private final String command;
331             private final List<String> arguments;
332             private final Path cwd;
333             private final Path installationDirectory;
334             private final Path userHomeDirectory;
335             private final Map<String, String> jvmSystemProperties;
336             private final Map<String, String> environmentVariables;
337             private final List<String> jvmArguments;
338             private final OutputStream stdoutConsumer;
339             private final OutputStream stderrConsumer;
340 
341             @SuppressWarnings("ParameterNumber")
342             private Impl(
343                     String command,
344                     List<String> arguments,
345                     Path cwd,
346                     Path installationDirectory,
347                     Path userHomeDirectory,
348                     Map<String, String> jvmSystemProperties,
349                     Map<String, String> environmentVariables,
350                     List<String> jvmArguments,
351                     OutputStream stdoutConsumer,
352                     OutputStream stderrConsumer) {
353                 this.command = requireNonNull(command);
354                 this.arguments = arguments == null ? List.of() : List.copyOf(arguments);
355                 this.cwd = getCanonicalPath(requireNonNull(cwd));
356                 this.installationDirectory = getCanonicalPath(requireNonNull(installationDirectory));
357                 this.userHomeDirectory = getCanonicalPath(requireNonNull(userHomeDirectory));
358                 this.jvmSystemProperties = jvmSystemProperties != null ? Map.copyOf(jvmSystemProperties) : null;
359                 this.environmentVariables = environmentVariables != null ? Map.copyOf(environmentVariables) : null;
360                 this.jvmArguments = jvmArguments != null ? List.copyOf(jvmArguments) : null;
361                 this.stdoutConsumer = stdoutConsumer;
362                 this.stderrConsumer = stderrConsumer;
363             }
364 
365             @Override
366             public String command() {
367                 return command;
368             }
369 
370             @Override
371             public List<String> arguments() {
372                 return arguments;
373             }
374 
375             @Override
376             public Path cwd() {
377                 return cwd;
378             }
379 
380             @Override
381             public Path installationDirectory() {
382                 return installationDirectory;
383             }
384 
385             @Override
386             public Path userHomeDirectory() {
387                 return userHomeDirectory;
388             }
389 
390             @Override
391             public Optional<Map<String, String>> jvmSystemProperties() {
392                 return Optional.ofNullable(jvmSystemProperties);
393             }
394 
395             @Override
396             public Optional<Map<String, String>> environmentVariables() {
397                 return Optional.ofNullable(environmentVariables);
398             }
399 
400             @Override
401             public Optional<List<String>> jvmArguments() {
402                 return Optional.ofNullable(jvmArguments);
403             }
404 
405             @Override
406             public Optional<OutputStream> stdoutConsumer() {
407                 return Optional.ofNullable(stdoutConsumer);
408             }
409 
410             @Override
411             public Optional<OutputStream> stderrConsumer() {
412                 return Optional.ofNullable(stderrConsumer);
413             }
414 
415             @Override
416             public String toString() {
417                 return "Impl{" + "command='"
418                         + command + '\'' + ", arguments="
419                         + arguments + ", cwd="
420                         + cwd + ", installationDirectory="
421                         + installationDirectory + ", userHomeDirectory="
422                         + userHomeDirectory + ", jvmSystemProperties="
423                         + jvmSystemProperties + ", environmentVariables="
424                         + environmentVariables + ", jvmArguments="
425                         + jvmArguments + ", stdoutConsumer="
426                         + stdoutConsumer + ", stderrConsumer="
427                         + stderrConsumer + '}';
428             }
429         }
430     }
431 
432     @Nonnull
433     static Path discoverMavenHome() {
434         String mavenHome = System.getProperty("maven.home");
435         if (mavenHome == null) {
436             throw new ExecutorException("requires maven.home Java System Property set");
437         }
438         return getCanonicalPath(Paths.get(mavenHome));
439     }
440 
441     @Nonnull
442     static Path getCanonicalPath(Path path) {
443         requireNonNull(path, "path");
444         try {
445             return path.toRealPath();
446         } catch (IOException e) {
447             return getCanonicalPath(path.getParent()).resolve(path.getFileName());
448         }
449     }
450 }