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