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> help() {
207         if (commandLine.hasOption(CLIManager.HELP)) {
208             return Optional.of(Boolean.TRUE);
209         }
210         return Optional.empty();
211     }
212 
213     @Override
214     public void warnAboutDeprecatedOptions(ParserRequest request, Consumer<String> printWriter) {
215         if (cliManager.getUsedDeprecatedOptions().isEmpty()) {
216             return;
217         }
218         printWriter.accept("Detected deprecated option use in " + source);
219         for (Option option : cliManager.getUsedDeprecatedOptions()) {
220             StringBuilder sb = new StringBuilder();
221             sb.append("The option ");
222             if (option.getOpt() != null) {
223                 sb.append("-").append(option.getOpt());
224             }
225             if (option.getLongOpt() != null) {
226                 if (option.getOpt() != null) {
227                     sb.append(",");
228                 }
229                 sb.append("--").append(option.getLongOpt());
230             }
231             sb.append(" is deprecated ");
232             if (option.getDeprecated().isForRemoval()) {
233                 sb.append("and will be removed in a future version");
234             }
235             if (option.getDeprecated().getSince() != null) {
236                 sb.append(" since ")
237                         .append(request.commandName())
238                         .append(" ")
239                         .append(option.getDeprecated().getSince());
240             }
241             printWriter.accept(sb.toString());
242         }
243     }
244 
245     @Override
246     public void displayHelp(ParserRequest request, Consumer<String> printStream) {
247         cliManager.displayHelp(request.command(), printStream);
248     }
249 
250     protected static class CLIManager {
251         public static final String USER_PROPERTY = "D";
252         public static final String SHOW_VERSION_AND_EXIT = "v";
253         public static final String SHOW_VERSION = "V";
254         public static final String QUIET = "q";
255         public static final String VERBOSE = "X";
256 
257         public static final String SHOW_ERRORS = "e";
258         public static final String FAIL_ON_SEVERITY = "fos";
259         public static final String NON_INTERACTIVE = "non-interactive";
260         public static final String BATCH_MODE = "B";
261         public static final String FORCE_INTERACTIVE = "force-interactive";
262         public static final String ALTERNATE_USER_SETTINGS = "s";
263         public static final String ALTERNATE_PROJECT_SETTINGS = "ps";
264         public static final String ALTERNATE_INSTALLATION_SETTINGS = "is";
265         public static final String ALTERNATE_USER_TOOLCHAINS = "t";
266         public static final String ALTERNATE_INSTALLATION_TOOLCHAINS = "it";
267         public static final String LOG_FILE = "l";
268         public static final String RAW_STREAMS = "raw-streams";
269         public static final String COLOR = "color";
270         public static final String HELP = "h";
271 
272         // parameters handled by script
273         public static final String DEBUG = "debug";
274         public static final String ENC = "enc";
275         public static final String YJP = "yjp";
276 
277         // deprecated ones
278         @Deprecated
279         public static final String ALTERNATE_GLOBAL_SETTINGS = "gs";
280 
281         @Deprecated
282         public static final String ALTERNATE_GLOBAL_TOOLCHAINS = "gt";
283 
284         protected org.apache.commons.cli.Options options;
285         protected final LinkedHashSet<Option> usedDeprecatedOptions = new LinkedHashSet<>();
286 
287         @SuppressWarnings("checkstyle:MethodLength")
288         protected CLIManager() {
289             options = new org.apache.commons.cli.Options();
290             prepareOptions(options);
291         }
292 
293         protected void prepareOptions(org.apache.commons.cli.Options options) {
294             options.addOption(Option.builder(HELP)
295                     .longOpt("help")
296                     .desc("Display help information")
297                     .build());
298             options.addOption(Option.builder(USER_PROPERTY)
299                     .numberOfArgs(2)
300                     .valueSeparator('=')
301                     .desc("Define a user property")
302                     .build());
303             options.addOption(Option.builder(SHOW_VERSION_AND_EXIT)
304                     .longOpt("version")
305                     .desc("Display version information")
306                     .build());
307             options.addOption(Option.builder(QUIET)
308                     .longOpt("quiet")
309                     .desc("Quiet output - only show errors")
310                     .build());
311             options.addOption(Option.builder(VERBOSE)
312                     .longOpt("verbose")
313                     .desc("Produce execution verbose output")
314                     .build());
315             options.addOption(Option.builder(SHOW_ERRORS)
316                     .longOpt("errors")
317                     .desc("Produce execution error messages")
318                     .build());
319             options.addOption(Option.builder(BATCH_MODE)
320                     .longOpt("batch-mode")
321                     .desc("Run in non-interactive mode. Alias for --non-interactive (kept for backwards compatability)")
322                     .build());
323             options.addOption(Option.builder()
324                     .longOpt(NON_INTERACTIVE)
325                     .desc("Run in non-interactive mode. Alias for --batch-mode")
326                     .build());
327             options.addOption(Option.builder()
328                     .longOpt(FORCE_INTERACTIVE)
329                     .desc(
330                             "Run in interactive mode. Overrides, if applicable, the CI environment variable and --non-interactive/--batch-mode options")
331                     .build());
332             options.addOption(Option.builder(ALTERNATE_USER_SETTINGS)
333                     .longOpt("settings")
334                     .desc("Alternate path for the user settings file")
335                     .hasArg()
336                     .build());
337             options.addOption(Option.builder(ALTERNATE_PROJECT_SETTINGS)
338                     .longOpt("project-settings")
339                     .desc("Alternate path for the project settings file")
340                     .hasArg()
341                     .build());
342             options.addOption(Option.builder(ALTERNATE_INSTALLATION_SETTINGS)
343                     .longOpt("install-settings")
344                     .desc("Alternate path for the installation settings file")
345                     .hasArg()
346                     .build());
347             options.addOption(Option.builder(ALTERNATE_USER_TOOLCHAINS)
348                     .longOpt("toolchains")
349                     .desc("Alternate path for the user toolchains file")
350                     .hasArg()
351                     .build());
352             options.addOption(Option.builder(ALTERNATE_INSTALLATION_TOOLCHAINS)
353                     .longOpt("install-toolchains")
354                     .desc("Alternate path for the installation toolchains file")
355                     .hasArg()
356                     .build());
357             options.addOption(Option.builder(FAIL_ON_SEVERITY)
358                     .longOpt("fail-on-severity")
359                     .desc("Configure which severity of logging should cause the build to fail")
360                     .hasArg()
361                     .build());
362             options.addOption(Option.builder(LOG_FILE)
363                     .longOpt("log-file")
364                     .hasArg()
365                     .desc("Log file where all build output will go (disables output color)")
366                     .build());
367             options.addOption(Option.builder()
368                     .longOpt(RAW_STREAMS)
369                     .desc("Do not decorate standard output and error streams")
370                     .build());
371             options.addOption(Option.builder(SHOW_VERSION)
372                     .longOpt("show-version")
373                     .desc("Display version information WITHOUT stopping build")
374                     .build());
375             options.addOption(Option.builder()
376                     .longOpt(COLOR)
377                     .hasArg()
378                     .optionalArg(true)
379                     .desc("Defines the color mode of the output. Supported are 'auto', 'always', 'never'.")
380                     .build());
381 
382             // Parameters handled by script
383             options.addOption(Option.builder()
384                     .longOpt(DEBUG)
385                     .desc("Launch the JVM in debug mode (script option).")
386                     .build());
387             options.addOption(Option.builder()
388                     .longOpt(ENC)
389                     .desc("Launch the Maven Encryption tool (script option).")
390                     .build());
391             options.addOption(Option.builder()
392                     .longOpt(YJP)
393                     .desc("Launch the JVM with Yourkit profiler (script option).")
394                     .build());
395 
396             // Deprecated
397             options.addOption(Option.builder(ALTERNATE_GLOBAL_SETTINGS)
398                     .longOpt("global-settings")
399                     .desc("<deprecated> Alternate path for the global settings file.")
400                     .hasArg()
401                     .deprecated(DeprecatedAttributes.builder()
402                             .setForRemoval(true)
403                             .setSince("4.0.0")
404                             .setDescription("Use -is,--install-settings instead.")
405                             .get())
406                     .build());
407             options.addOption(Option.builder(ALTERNATE_GLOBAL_TOOLCHAINS)
408                     .longOpt("global-toolchains")
409                     .desc("<deprecated> Alternate path for the global toolchains file.")
410                     .hasArg()
411                     .deprecated(DeprecatedAttributes.builder()
412                             .setForRemoval(true)
413                             .setSince("4.0.0")
414                             .setDescription("Use -it,--install-toolchains instead.")
415                             .get())
416                     .build());
417         }
418 
419         public CommandLine parse(String[] args) throws ParseException {
420             // We need to eat any quotes surrounding arguments...
421             String[] cleanArgs = CleanArgument.cleanArgs(args);
422             DefaultParser parser = DefaultParser.builder()
423                     .setDeprecatedHandler(this::addDeprecatedOption)
424                     .build();
425             CommandLine commandLine = parser.parse(options, cleanArgs);
426             // to trigger deprecation handler, so we can report deprecation BEFORE we actually use options
427             options.getOptions().forEach(commandLine::hasOption);
428             return commandLine;
429         }
430 
431         protected void addDeprecatedOption(Option option) {
432             usedDeprecatedOptions.add(option);
433         }
434 
435         public org.apache.commons.cli.Options getOptions() {
436             return options;
437         }
438 
439         public Set<Option> getUsedDeprecatedOptions() {
440             return usedDeprecatedOptions;
441         }
442 
443         public void displayHelp(String command, Consumer<String> pw) {
444             HelpFormatter formatter = new HelpFormatter();
445 
446             int width = MessageUtils.getTerminalWidth();
447             if (width <= 0) {
448                 width = HelpFormatter.DEFAULT_WIDTH;
449             }
450 
451             pw.accept("");
452 
453             StringWriter sw = new StringWriter();
454             PrintWriter pw2 = new PrintWriter(sw);
455             formatter.printHelp(
456                     pw2,
457                     width,
458                     commandLineSyntax(command),
459                     System.lineSeparator() + "Options:",
460                     options,
461                     HelpFormatter.DEFAULT_LEFT_PAD,
462                     HelpFormatter.DEFAULT_DESC_PAD,
463                     System.lineSeparator(),
464                     false);
465             pw2.flush();
466             for (String s : sw.toString().split(System.lineSeparator())) {
467                 pw.accept(s);
468             }
469         }
470 
471         protected String commandLineSyntax(String command) {
472             return command + " [options] [goals]";
473         }
474     }
475 }