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.cling.invoker;
20  
21  import java.io.PrintWriter;
22  import java.io.StringWriter;
23  import java.util.LinkedHashSet;
24  import java.util.Map;
25  import java.util.Optional;
26  import java.util.Set;
27  import java.util.function.Consumer;
28  
29  import org.apache.commons.cli.CommandLine;
30  import org.apache.commons.cli.DefaultParser;
31  import org.apache.commons.cli.DeprecatedAttributes;
32  import org.apache.commons.cli.HelpFormatter;
33  import org.apache.commons.cli.Option;
34  import org.apache.commons.cli.ParseException;
35  import org.apache.maven.api.cli.Options;
36  import org.apache.maven.api.cli.ParserRequest;
37  import org.apache.maven.jline.MessageUtils;
38  
39  import static java.util.Objects.requireNonNull;
40  import static org.apache.maven.cling.invoker.Utils.toMap;
41  
42  public abstract class CommonsCliOptions implements Options {
43  
44      protected final String source;
45      protected final CLIManager cliManager;
46      protected final CommandLine commandLine;
47  
48      protected CommonsCliOptions(String source, CLIManager cliManager, CommandLine commandLine) {
49          this.source = requireNonNull(source);
50          this.cliManager = requireNonNull(cliManager);
51          this.commandLine = requireNonNull(commandLine);
52      }
53  
54      @Override
55      public String source() {
56          return source;
57      }
58  
59      @Override
60      public Optional<Map<String, String>> userProperties() {
61          if (commandLine.hasOption(CLIManager.USER_PROPERTY)) {
62              return Optional.of(toMap(commandLine.getOptionProperties(CLIManager.USER_PROPERTY)));
63          }
64          return Optional.empty();
65      }
66  
67      @Override
68      public Optional<Boolean> showVersionAndExit() {
69          if (commandLine.hasOption(CLIManager.SHOW_VERSION_AND_EXIT)) {
70              return Optional.of(Boolean.TRUE);
71          }
72          return Optional.empty();
73      }
74  
75      @Override
76      public Optional<Boolean> showVersion() {
77          if (commandLine.hasOption(CLIManager.SHOW_VERSION)) {
78              return Optional.of(Boolean.TRUE);
79          }
80          return Optional.empty();
81      }
82  
83      @Override
84      public Optional<Boolean> quiet() {
85          if (commandLine.hasOption(CLIManager.QUIET)) {
86              return Optional.of(Boolean.TRUE);
87          }
88          return Optional.empty();
89      }
90  
91      @Override
92      public Optional<Boolean> verbose() {
93          if (commandLine.hasOption(CLIManager.VERBOSE)) {
94              return Optional.of(Boolean.TRUE);
95          }
96          return Optional.empty();
97      }
98  
99      @Override
100     public Optional<Boolean> showErrors() {
101         if (commandLine.hasOption(CLIManager.SHOW_ERRORS) || verbose().orElse(false)) {
102             return Optional.of(Boolean.TRUE);
103         }
104         return Optional.empty();
105     }
106 
107     @Override
108     public Optional<String> failOnSeverity() {
109         if (commandLine.hasOption(CLIManager.FAIL_ON_SEVERITY)) {
110             return Optional.of(commandLine.getOptionValue(CLIManager.FAIL_ON_SEVERITY));
111         }
112         return Optional.empty();
113     }
114 
115     @Override
116     public Optional<Boolean> nonInteractive() {
117         if (commandLine.hasOption(CLIManager.NON_INTERACTIVE) || commandLine.hasOption(CLIManager.BATCH_MODE)) {
118             return Optional.of(Boolean.TRUE);
119         }
120         return Optional.empty();
121     }
122 
123     @Override
124     public Optional<Boolean> forceInteractive() {
125         if (commandLine.hasOption(CLIManager.FORCE_INTERACTIVE)) {
126             return Optional.of(Boolean.TRUE);
127         }
128         return Optional.empty();
129     }
130 
131     @Override
132     public Optional<String> altUserSettings() {
133         if (commandLine.hasOption(CLIManager.ALTERNATE_USER_SETTINGS)) {
134             return Optional.of(commandLine.getOptionValue(CLIManager.ALTERNATE_USER_SETTINGS));
135         }
136         return Optional.empty();
137     }
138 
139     @Override
140     public Optional<String> altProjectSettings() {
141         if (commandLine.hasOption(CLIManager.ALTERNATE_PROJECT_SETTINGS)) {
142             return Optional.of(commandLine.getOptionValue(CLIManager.ALTERNATE_PROJECT_SETTINGS));
143         }
144         return Optional.empty();
145     }
146 
147     @Override
148     public Optional<String> altInstallationSettings() {
149         if (commandLine.hasOption(CLIManager.ALTERNATE_INSTALLATION_SETTINGS)) {
150             return Optional.of(commandLine.getOptionValue(CLIManager.ALTERNATE_INSTALLATION_SETTINGS));
151         }
152         if (commandLine.hasOption(CLIManager.ALTERNATE_GLOBAL_SETTINGS)) {
153             return Optional.of(commandLine.getOptionValue(CLIManager.ALTERNATE_GLOBAL_SETTINGS));
154         }
155         return Optional.empty();
156     }
157 
158     @Override
159     public Optional<String> altUserToolchains() {
160         if (commandLine.hasOption(CLIManager.ALTERNATE_USER_TOOLCHAINS)) {
161             return Optional.of(commandLine.getOptionValue(CLIManager.ALTERNATE_USER_TOOLCHAINS));
162         }
163         return Optional.empty();
164     }
165 
166     @Override
167     public Optional<String> altInstallationToolchains() {
168         if (commandLine.hasOption(CLIManager.ALTERNATE_INSTALLATION_TOOLCHAINS)) {
169             return Optional.of(commandLine.getOptionValue(CLIManager.ALTERNATE_INSTALLATION_TOOLCHAINS));
170         }
171         if (commandLine.hasOption(CLIManager.ALTERNATE_GLOBAL_TOOLCHAINS)) {
172             return Optional.of(commandLine.getOptionValue(CLIManager.ALTERNATE_GLOBAL_TOOLCHAINS));
173         }
174         return Optional.empty();
175     }
176 
177     @Override
178     public Optional<String> logFile() {
179         if (commandLine.hasOption(CLIManager.LOG_FILE)) {
180             return Optional.of(commandLine.getOptionValue(CLIManager.LOG_FILE));
181         }
182         return Optional.empty();
183     }
184 
185     @Override
186     public Optional<Boolean> rawStreams() {
187         if (commandLine.hasOption(CLIManager.RAW_STREAMS)) {
188             return Optional.of(Boolean.TRUE);
189         }
190         return Optional.empty();
191     }
192 
193     @Override
194     public Optional<String> color() {
195         if (commandLine.hasOption(CLIManager.COLOR)) {
196             if (commandLine.getOptionValue(CLIManager.COLOR) != null) {
197                 return Optional.of(commandLine.getOptionValue(CLIManager.COLOR));
198             } else {
199                 return Optional.of("auto");
200             }
201         }
202         return Optional.empty();
203     }
204 
205     @Override
206     public Optional<Boolean> offline() {
207         if (commandLine.hasOption(CLIManager.OFFLINE)) {
208             return Optional.of(Boolean.TRUE);
209         }
210         return Optional.empty();
211     }
212 
213     @Override
214     public Optional<Boolean> help() {
215         if (commandLine.hasOption(CLIManager.HELP)) {
216             return Optional.of(Boolean.TRUE);
217         }
218         return Optional.empty();
219     }
220 
221     @Override
222     public void warnAboutDeprecatedOptions(ParserRequest request, Consumer<String> printWriter) {
223         if (cliManager.getUsedDeprecatedOptions().isEmpty()) {
224             return;
225         }
226         printWriter.accept("Detected deprecated option use in " + source);
227         for (Option option : cliManager.getUsedDeprecatedOptions()) {
228             StringBuilder sb = new StringBuilder();
229             sb.append("The option ");
230             if (option.getOpt() != null) {
231                 sb.append("-").append(option.getOpt());
232             }
233             if (option.getLongOpt() != null) {
234                 if (option.getOpt() != null) {
235                     sb.append(",");
236                 }
237                 sb.append("--").append(option.getLongOpt());
238             }
239             sb.append(" is deprecated ");
240             if (option.getDeprecated().isForRemoval()) {
241                 sb.append("and will be removed in a future version");
242             }
243             if (option.getDeprecated().getSince() != null) {
244                 sb.append(" since ")
245                         .append(request.commandName())
246                         .append(" ")
247                         .append(option.getDeprecated().getSince());
248             }
249             printWriter.accept(sb.toString());
250         }
251     }
252 
253     @Override
254     public void displayHelp(ParserRequest request, Consumer<String> printStream) {
255         cliManager.displayHelp(request.command(), printStream);
256     }
257 
258     protected static class CLIManager {
259         public static final String USER_PROPERTY = "D";
260         public static final String SHOW_VERSION_AND_EXIT = "v";
261         public static final String SHOW_VERSION = "V";
262         public static final String QUIET = "q";
263         public static final String VERBOSE = "X";
264 
265         public static final String SHOW_ERRORS = "e";
266 
267         public static final String FAIL_ON_SEVERITY = "fos";
268         public static final String NON_INTERACTIVE = "non-interactive";
269         public static final String BATCH_MODE = "B";
270         public static final String FORCE_INTERACTIVE = "force-interactive";
271         public static final String ALTERNATE_USER_SETTINGS = "s";
272         public static final String ALTERNATE_PROJECT_SETTINGS = "ps";
273         public static final String ALTERNATE_INSTALLATION_SETTINGS = "is";
274         public static final String ALTERNATE_USER_TOOLCHAINS = "t";
275         public static final String ALTERNATE_INSTALLATION_TOOLCHAINS = "it";
276         public static final String LOG_FILE = "l";
277         public static final String RAW_STREAMS = "raw-streams";
278         public static final String COLOR = "color";
279         public static final String OFFLINE = "o";
280         public static final String HELP = "h";
281 
282         // Not an Option: used only for early detection, when CLI args may not be even parsed
283         public static final String SHOW_ERRORS_CLI_ARG = "-" + SHOW_ERRORS;
284 
285         // parameters handled by script
286         public static final String DEBUG = "debug";
287         public static final String ENC = "enc";
288         public static final String SHELL = "shell";
289         public static final String YJP = "yjp";
290 
291         // deprecated ones
292         @Deprecated
293         public static final String ALTERNATE_GLOBAL_SETTINGS = "gs";
294 
295         @Deprecated
296         public static final String ALTERNATE_GLOBAL_TOOLCHAINS = "gt";
297 
298         protected org.apache.commons.cli.Options options;
299         protected final LinkedHashSet<Option> usedDeprecatedOptions = new LinkedHashSet<>();
300 
301         @SuppressWarnings("checkstyle:MethodLength")
302         protected CLIManager() {
303             options = new org.apache.commons.cli.Options();
304             prepareOptions(options);
305         }
306 
307         protected void prepareOptions(org.apache.commons.cli.Options options) {
308             options.addOption(Option.builder(HELP)
309                     .longOpt("help")
310                     .desc("Display help information")
311                     .build());
312             options.addOption(Option.builder(USER_PROPERTY)
313                     .numberOfArgs(2)
314                     .valueSeparator('=')
315                     .desc("Define a user property")
316                     .build());
317             options.addOption(Option.builder(SHOW_VERSION_AND_EXIT)
318                     .longOpt("version")
319                     .desc("Display version information")
320                     .build());
321             options.addOption(Option.builder(QUIET)
322                     .longOpt("quiet")
323                     .desc("Quiet output - only show errors")
324                     .build());
325             options.addOption(Option.builder(VERBOSE)
326                     .longOpt("verbose")
327                     .desc("Produce execution verbose output")
328                     .build());
329             options.addOption(Option.builder(SHOW_ERRORS)
330                     .longOpt("errors")
331                     .desc("Produce execution error messages")
332                     .build());
333             options.addOption(Option.builder(BATCH_MODE)
334                     .longOpt("batch-mode")
335                     .desc("Run in non-interactive mode. Alias for --non-interactive (kept for backwards compatability)")
336                     .build());
337             options.addOption(Option.builder()
338                     .longOpt(NON_INTERACTIVE)
339                     .desc("Run in non-interactive mode. Alias for --batch-mode")
340                     .build());
341             options.addOption(Option.builder()
342                     .longOpt(FORCE_INTERACTIVE)
343                     .desc(
344                             "Run in interactive mode. Overrides, if applicable, the CI environment variable and --non-interactive/--batch-mode options")
345                     .build());
346             options.addOption(Option.builder(ALTERNATE_USER_SETTINGS)
347                     .longOpt("settings")
348                     .desc("Alternate path for the user settings file")
349                     .hasArg()
350                     .build());
351             options.addOption(Option.builder(ALTERNATE_PROJECT_SETTINGS)
352                     .longOpt("project-settings")
353                     .desc("Alternate path for the project settings file")
354                     .hasArg()
355                     .build());
356             options.addOption(Option.builder(ALTERNATE_INSTALLATION_SETTINGS)
357                     .longOpt("install-settings")
358                     .desc("Alternate path for the installation settings file")
359                     .hasArg()
360                     .build());
361             options.addOption(Option.builder(ALTERNATE_USER_TOOLCHAINS)
362                     .longOpt("toolchains")
363                     .desc("Alternate path for the user toolchains file")
364                     .hasArg()
365                     .build());
366             options.addOption(Option.builder(ALTERNATE_INSTALLATION_TOOLCHAINS)
367                     .longOpt("install-toolchains")
368                     .desc("Alternate path for the installation toolchains file")
369                     .hasArg()
370                     .build());
371             options.addOption(Option.builder(FAIL_ON_SEVERITY)
372                     .longOpt("fail-on-severity")
373                     .desc("Configure which severity of logging should cause the build to fail")
374                     .hasArg()
375                     .build());
376             options.addOption(Option.builder(LOG_FILE)
377                     .longOpt("log-file")
378                     .hasArg()
379                     .desc("Log file where all build output will go (disables output color)")
380                     .build());
381             options.addOption(Option.builder()
382                     .longOpt(RAW_STREAMS)
383                     .desc("Do not decorate standard output and error streams")
384                     .build());
385             options.addOption(Option.builder(SHOW_VERSION)
386                     .longOpt("show-version")
387                     .desc("Display version information WITHOUT stopping build")
388                     .build());
389             options.addOption(Option.builder()
390                     .longOpt(COLOR)
391                     .hasArg()
392                     .optionalArg(true)
393                     .desc("Defines the color mode of the output. Supported are 'auto', 'always', 'never'.")
394                     .build());
395             options.addOption(Option.builder(OFFLINE)
396                     .longOpt("offline")
397                     .desc("Work offline")
398                     .build());
399 
400             // Parameters handled by script
401             options.addOption(Option.builder()
402                     .longOpt(DEBUG)
403                     .desc("Launch the JVM in debug mode (script option).")
404                     .build());
405             options.addOption(Option.builder()
406                     .longOpt(ENC)
407                     .desc("Launch the Maven Encryption tool (script option).")
408                     .build());
409             options.addOption(Option.builder()
410                     .longOpt(SHELL)
411                     .desc("Launch the Maven Shell tool (script option).")
412                     .build());
413             options.addOption(Option.builder()
414                     .longOpt(YJP)
415                     .desc("Launch the JVM with Yourkit profiler (script option).")
416                     .build());
417 
418             // Deprecated
419             options.addOption(Option.builder(ALTERNATE_GLOBAL_SETTINGS)
420                     .longOpt("global-settings")
421                     .desc("<deprecated> Alternate path for the global settings file.")
422                     .hasArg()
423                     .deprecated(DeprecatedAttributes.builder()
424                             .setForRemoval(true)
425                             .setSince("4.0.0")
426                             .setDescription("Use -is,--install-settings instead.")
427                             .get())
428                     .build());
429             options.addOption(Option.builder(ALTERNATE_GLOBAL_TOOLCHAINS)
430                     .longOpt("global-toolchains")
431                     .desc("<deprecated> Alternate path for the global toolchains file.")
432                     .hasArg()
433                     .deprecated(DeprecatedAttributes.builder()
434                             .setForRemoval(true)
435                             .setSince("4.0.0")
436                             .setDescription("Use -it,--install-toolchains instead.")
437                             .get())
438                     .build());
439         }
440 
441         public CommandLine parse(String[] args) throws ParseException {
442             // We need to eat any quotes surrounding arguments...
443             String[] cleanArgs = CleanArgument.cleanArgs(args);
444             DefaultParser parser = DefaultParser.builder()
445                     .setDeprecatedHandler(this::addDeprecatedOption)
446                     .build();
447             CommandLine commandLine = parser.parse(options, cleanArgs);
448             // to trigger deprecation handler, so we can report deprecation BEFORE we actually use options
449             options.getOptions().forEach(commandLine::hasOption);
450             return commandLine;
451         }
452 
453         protected void addDeprecatedOption(Option option) {
454             usedDeprecatedOptions.add(option);
455         }
456 
457         public org.apache.commons.cli.Options getOptions() {
458             return options;
459         }
460 
461         public Set<Option> getUsedDeprecatedOptions() {
462             return usedDeprecatedOptions;
463         }
464 
465         public void displayHelp(String command, Consumer<String> pw) {
466             HelpFormatter formatter = new HelpFormatter();
467 
468             int width = MessageUtils.getTerminalWidth();
469             if (width <= 0) {
470                 width = HelpFormatter.DEFAULT_WIDTH;
471             }
472 
473             pw.accept("");
474 
475             StringWriter sw = new StringWriter();
476             PrintWriter pw2 = new PrintWriter(sw);
477             formatter.printHelp(
478                     pw2,
479                     width,
480                     commandLineSyntax(command),
481                     System.lineSeparator() + "Options:",
482                     options,
483                     HelpFormatter.DEFAULT_LEFT_PAD,
484                     HelpFormatter.DEFAULT_DESC_PAD,
485                     System.lineSeparator(),
486                     false);
487             pw2.flush();
488             for (String s : sw.toString().split(System.lineSeparator())) {
489                 pw.accept(s);
490             }
491         }
492 
493         protected String commandLineSyntax(String command) {
494             return command + " [options] [goals]";
495         }
496     }
497 }