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.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.PrintWriter;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.util.ArrayList;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Locale;
30  import java.util.Map;
31  import java.util.Properties;
32  import java.util.function.Consumer;
33  import java.util.function.Function;
34  
35  import org.apache.maven.api.Constants;
36  import org.apache.maven.api.ProtoSession;
37  import org.apache.maven.api.cli.Invoker;
38  import org.apache.maven.api.cli.InvokerException;
39  import org.apache.maven.api.cli.InvokerRequest;
40  import org.apache.maven.api.cli.Logger;
41  import org.apache.maven.api.cli.Options;
42  import org.apache.maven.api.services.BuilderProblem;
43  import org.apache.maven.api.services.Interpolator;
44  import org.apache.maven.api.services.Lookup;
45  import org.apache.maven.api.services.MavenException;
46  import org.apache.maven.api.services.MessageBuilder;
47  import org.apache.maven.api.services.SettingsBuilder;
48  import org.apache.maven.api.services.SettingsBuilderRequest;
49  import org.apache.maven.api.services.SettingsBuilderResult;
50  import org.apache.maven.api.services.Source;
51  import org.apache.maven.api.settings.Mirror;
52  import org.apache.maven.api.settings.Profile;
53  import org.apache.maven.api.settings.Proxy;
54  import org.apache.maven.api.settings.Repository;
55  import org.apache.maven.api.settings.Server;
56  import org.apache.maven.api.settings.Settings;
57  import org.apache.maven.api.spi.PropertyContributor;
58  import org.apache.maven.artifact.InvalidRepositoryException;
59  import org.apache.maven.artifact.repository.ArtifactRepository;
60  import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
61  import org.apache.maven.artifact.repository.MavenArtifactRepository;
62  import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
63  import org.apache.maven.bridge.MavenRepositorySystem;
64  import org.apache.maven.cling.invoker.spi.PropertyContributorsHolder;
65  import org.apache.maven.cling.logging.Slf4jConfiguration;
66  import org.apache.maven.cling.logging.Slf4jConfigurationFactory;
67  import org.apache.maven.cling.utils.CLIReportingUtils;
68  import org.apache.maven.execution.MavenExecutionRequest;
69  import org.apache.maven.internal.impl.SettingsUtilsV4;
70  import org.apache.maven.jline.FastTerminal;
71  import org.apache.maven.jline.MessageUtils;
72  import org.apache.maven.logging.BuildEventListener;
73  import org.apache.maven.logging.LoggingOutputStream;
74  import org.apache.maven.logging.ProjectBuildLogAppender;
75  import org.apache.maven.logging.SimpleBuildEventListener;
76  import org.apache.maven.logging.api.LogLevelRecorder;
77  import org.apache.maven.slf4j.MavenSimpleLogger;
78  import org.jline.terminal.Terminal;
79  import org.jline.terminal.TerminalBuilder;
80  import org.jline.terminal.impl.AbstractPosixTerminal;
81  import org.jline.terminal.spi.TerminalExt;
82  import org.slf4j.LoggerFactory;
83  import org.slf4j.spi.LocationAwareLogger;
84  
85  import static java.util.Objects.requireNonNull;
86  import static org.apache.maven.cling.invoker.Utils.toMavenExecutionRequestLoggingLevel;
87  import static org.apache.maven.cling.invoker.Utils.toProperties;
88  
89  /**
90   * Lookup invoker implementation, that boots up DI container.
91   *
92   * @param <C> The context type.
93   */
94  public abstract class LookupInvoker<C extends LookupContext> implements Invoker {
95      protected final ProtoLookup protoLookup;
96  
97      public LookupInvoker(ProtoLookup protoLookup) {
98          this.protoLookup = requireNonNull(protoLookup);
99      }
100 
101     @Override
102     public int invoke(InvokerRequest invokerRequest) throws InvokerException {
103         requireNonNull(invokerRequest);
104 
105         Properties oldProps = (Properties) System.getProperties().clone();
106         ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
107         try (C context = createContext(invokerRequest)) {
108             try {
109                 if (context.containerCapsule != null
110                         && context.containerCapsule.currentThreadClassLoader().isPresent()) {
111                     Thread.currentThread()
112                             .setContextClassLoader(context.containerCapsule
113                                     .currentThreadClassLoader()
114                                     .get());
115                 }
116                 return doInvoke(context);
117             } catch (InvokerException.ExitException e) {
118                 return e.getExitCode();
119             } catch (Exception e) {
120                 throw handleException(context, e);
121             }
122         } finally {
123             Thread.currentThread().setContextClassLoader(oldCL);
124             System.setProperties(oldProps);
125         }
126     }
127 
128     protected int doInvoke(C context) throws Exception {
129         pushCoreProperties(context);
130         pushUserProperties(context);
131         validate(context);
132         prepare(context);
133         configureLogging(context);
134         createTerminal(context);
135         activateLogging(context);
136         helpOrVersionAndMayExit(context);
137         preCommands(context);
138         container(context);
139         postContainer(context);
140         pushUserProperties(context); // after PropertyContributor SPI
141         lookup(context);
142         init(context);
143         postCommands(context);
144         settings(context);
145         return execute(context);
146     }
147 
148     protected InvokerException handleException(C context, Exception e) throws InvokerException {
149         boolean showStackTrace = context.invokerRequest.options().showErrors().orElse(false);
150         if (showStackTrace) {
151             context.logger.error(
152                     "Error executing " + context.invokerRequest.parserRequest().commandName() + ".", e);
153         } else {
154             context.logger.error(
155                     "Error executing " + context.invokerRequest.parserRequest().commandName() + ".");
156             context.logger.error(e.getMessage());
157             for (Throwable cause = e.getCause(); cause != null && cause != cause.getCause(); cause = cause.getCause()) {
158                 context.logger.error("Caused by: " + cause.getMessage());
159             }
160         }
161         return new InvokerException(e.getMessage(), e);
162     }
163 
164     protected abstract C createContext(InvokerRequest invokerRequest) throws InvokerException;
165 
166     protected void pushCoreProperties(C context) throws Exception {
167         System.setProperty(
168                 Constants.MAVEN_HOME,
169                 context.invokerRequest.installationDirectory().toString());
170     }
171 
172     /**
173      * Note: this method is called twice from {@link #doInvoke(LookupContext)} and modifies context. First invocation
174      * when {@link LookupContext#pushedUserProperties} is null will push user properties IF key does not already
175      * exist among Java System Properties, and collects all they key it pushes. Second invocation happens AFTER
176      * {@link PropertyContributor} SPI invocation, and "refreshes" already pushed user properties by re-writing them
177      * as SPI may have modified them.
178      */
179     protected void pushUserProperties(C context) throws Exception {
180         ProtoSession protoSession = context.protoSession;
181         HashSet<String> sys = new HashSet<>(protoSession.getSystemProperties().keySet());
182         if (context.pushedUserProperties == null) {
183             context.pushedUserProperties = new HashSet<>();
184             protoSession.getUserProperties().entrySet().stream()
185                     .filter(k -> !sys.contains(k.getKey()))
186                     .peek(k -> context.pushedUserProperties.add(k.getKey()))
187                     .forEach(k -> System.setProperty(k.getKey(), k.getValue()));
188         } else {
189             protoSession.getUserProperties().entrySet().stream()
190                     .filter(k -> context.pushedUserProperties.contains(k.getKey()) || !sys.contains(k.getKey()))
191                     .forEach(k -> System.setProperty(k.getKey(), k.getValue()));
192         }
193     }
194 
195     protected void validate(C context) throws Exception {}
196 
197     protected void prepare(C context) throws Exception {}
198 
199     protected void configureLogging(C context) throws Exception {
200         // LOG COLOR
201         Options mavenOptions = context.invokerRequest.options();
202         Map<String, String> userProperties = context.protoSession.getUserProperties();
203         String styleColor = mavenOptions
204                 .color()
205                 .orElse(userProperties.getOrDefault(
206                         Constants.MAVEN_STYLE_COLOR_PROPERTY, userProperties.getOrDefault("style.color", "auto")));
207         if ("always".equals(styleColor) || "yes".equals(styleColor) || "force".equals(styleColor)) {
208             context.coloredOutput = true;
209         } else if ("never".equals(styleColor) || "no".equals(styleColor) || "none".equals(styleColor)) {
210             context.coloredOutput = false;
211         } else if (!"auto".equals(styleColor) && !"tty".equals(styleColor) && !"if-tty".equals(styleColor)) {
212             throw new IllegalArgumentException(
213                     "Invalid color configuration value '" + styleColor + "'. Supported are 'auto', 'always', 'never'.");
214         } else {
215             boolean isBatchMode = !mavenOptions.forceInteractive().orElse(false)
216                     && mavenOptions.nonInteractive().orElse(false);
217             if (isBatchMode || mavenOptions.logFile().isPresent()) {
218                 context.coloredOutput = false;
219             }
220         }
221 
222         context.loggerFactory = LoggerFactory.getILoggerFactory();
223         context.slf4jConfiguration = Slf4jConfigurationFactory.getConfiguration(context.loggerFactory);
224 
225         context.loggerLevel = Slf4jConfiguration.Level.INFO;
226         if (mavenOptions.verbose().orElse(false)) {
227             context.loggerLevel = Slf4jConfiguration.Level.DEBUG;
228         } else if (mavenOptions.quiet().orElse(false)) {
229             context.loggerLevel = Slf4jConfiguration.Level.ERROR;
230         }
231         context.slf4jConfiguration.setRootLoggerLevel(context.loggerLevel);
232         // else fall back to default log level specified in conf
233         // see https://issues.apache.org/jira/browse/MNG-2570
234 
235         // Create the build log appender; also sets MavenSimpleLogger sink
236         ProjectBuildLogAppender projectBuildLogAppender =
237                 new ProjectBuildLogAppender(determineBuildEventListener(context));
238         context.closeables.add(projectBuildLogAppender);
239     }
240 
241     protected BuildEventListener determineBuildEventListener(C context) {
242         if (context.buildEventListener == null) {
243             context.buildEventListener = doDetermineBuildEventListener(context);
244         }
245         return context.buildEventListener;
246     }
247 
248     protected BuildEventListener doDetermineBuildEventListener(C context) {
249         Consumer<String> writer = determineWriter(context);
250         return new SimpleBuildEventListener(writer);
251     }
252 
253     protected void createTerminal(C context) {
254         MessageUtils.systemInstall(
255                 builder -> {
256                     builder.streams(
257                             context.invokerRequest.in().orElse(null),
258                             context.invokerRequest.out().orElse(null));
259                     builder.systemOutput(TerminalBuilder.SystemOutput.ForcedSysOut);
260                     // The exec builder suffers from https://github.com/jline/jline3/issues/1098
261                     // We could re-enable it when fixed to provide support for non-standard architectures,
262                     // for which JLine does not provide any native library.
263                     builder.exec(false);
264                     if (context.coloredOutput != null) {
265                         builder.color(context.coloredOutput);
266                     }
267                 },
268                 terminal -> doConfigureWithTerminal(context, terminal));
269 
270         context.terminal = MessageUtils.getTerminal();
271         // JLine is quite slow to start due to the native library unpacking and loading
272         // so boot it asynchronously
273         context.closeables.add(MessageUtils::systemUninstall);
274         MessageUtils.registerShutdownHook(); // safety belt
275         if (context.coloredOutput != null) {
276             MessageUtils.setColorEnabled(context.coloredOutput);
277         }
278     }
279 
280     protected void doConfigureWithTerminal(C context, Terminal terminal) {
281         Options options = context.invokerRequest.options();
282         if (options.rawStreams().isEmpty() || !options.rawStreams().get()) {
283             MavenSimpleLogger stdout = (MavenSimpleLogger) context.loggerFactory.getLogger("stdout");
284             MavenSimpleLogger stderr = (MavenSimpleLogger) context.loggerFactory.getLogger("stderr");
285             stdout.setLogLevel(LocationAwareLogger.INFO_INT);
286             stderr.setLogLevel(LocationAwareLogger.INFO_INT);
287             System.setOut(new LoggingOutputStream(s -> stdout.info("[stdout] " + s)).printStream());
288             System.setErr(new LoggingOutputStream(s -> stderr.warn("[stderr] " + s)).printStream());
289             // no need to set them back, this is already handled by MessageUtils.systemUninstall() above
290         }
291     }
292 
293     protected Consumer<String> determineWriter(C context) {
294         if (context.writer == null) {
295             context.writer = doDetermineWriter(context);
296         }
297         return context.writer;
298     }
299 
300     protected Consumer<String> doDetermineWriter(C context) {
301         Options options = context.invokerRequest.options();
302         if (options.logFile().isPresent()) {
303             Path logFile = context.cwdResolver.apply(options.logFile().get());
304             try {
305                 PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(logFile), true);
306                 context.closeables.add(printWriter);
307                 return printWriter::println;
308             } catch (IOException e) {
309                 throw new MavenException("Unable to redirect logging to " + logFile, e);
310             }
311         } else {
312             // Given the terminal creation has been offloaded to a different thread,
313             // do not pass directly the terminal writer
314             return msg -> {
315                 PrintWriter pw = context.terminal.writer();
316                 pw.println(msg);
317                 pw.flush();
318             };
319         }
320     }
321 
322     protected void activateLogging(C context) throws Exception {
323         InvokerRequest invokerRequest = context.invokerRequest;
324         Options mavenOptions = invokerRequest.options();
325 
326         context.slf4jConfiguration.activate();
327         org.slf4j.Logger l = context.loggerFactory.getLogger(this.getClass().getName());
328         context.logger = (level, message, error) -> l.atLevel(org.slf4j.event.Level.valueOf(level.name()))
329                 .setCause(error)
330                 .log(message);
331 
332         if (mavenOptions.failOnSeverity().isPresent()) {
333             String logLevelThreshold = mavenOptions.failOnSeverity().get();
334             if (context.loggerFactory instanceof LogLevelRecorder recorder) {
335                 LogLevelRecorder.Level level =
336                         switch (logLevelThreshold.toLowerCase(Locale.ENGLISH)) {
337                             case "warn", "warning" -> LogLevelRecorder.Level.WARN;
338                             case "error" -> LogLevelRecorder.Level.ERROR;
339                             default -> throw new IllegalArgumentException(
340                                     logLevelThreshold
341                                             + " is not a valid log severity threshold. Valid severities are WARN/WARNING and ERROR.");
342                         };
343                 recorder.setMaxLevelAllowed(level);
344                 context.logger.info("Enabled to break the build on log level " + logLevelThreshold + ".");
345             } else {
346                 context.logger.warn("Expected LoggerFactory to be of type '" + LogLevelRecorder.class.getName()
347                         + "', but found '"
348                         + context.loggerFactory.getClass().getName() + "' instead. "
349                         + "The --fail-on-severity flag will not take effect.");
350             }
351         }
352     }
353 
354     protected void helpOrVersionAndMayExit(C context) throws Exception {
355         InvokerRequest invokerRequest = context.invokerRequest;
356         if (invokerRequest.options().help().isPresent()) {
357             Consumer<String> writer = determineWriter(context);
358             invokerRequest.options().displayHelp(context.invokerRequest.parserRequest(), writer);
359             throw new InvokerException.ExitException(0);
360         }
361         if (invokerRequest.options().showVersionAndExit().isPresent()) {
362             showVersion(context);
363             throw new InvokerException.ExitException(0);
364         }
365     }
366 
367     protected void showVersion(C context) {
368         Consumer<String> writer = determineWriter(context);
369         InvokerRequest invokerRequest = context.invokerRequest;
370         if (invokerRequest.options().quiet().orElse(false)) {
371             writer.accept(CLIReportingUtils.showVersionMinimal());
372         } else if (invokerRequest.options().verbose().orElse(false)) {
373             writer.accept(CLIReportingUtils.showVersion(
374                     ProcessHandle.current().info().commandLine().orElse(null), describe(context.terminal)));
375 
376         } else {
377             writer.accept(CLIReportingUtils.showVersion());
378         }
379     }
380 
381     protected String describe(Terminal terminal) {
382         if (terminal == null) {
383             return null;
384         }
385         if (terminal instanceof FastTerminal ft) {
386             terminal = ft.getTerminal();
387         }
388         List<String> subs = new ArrayList<>();
389         subs.add("type=" + terminal.getType());
390         if (terminal instanceof TerminalExt te) {
391             subs.add("provider=" + te.getProvider().name());
392         }
393         if (terminal instanceof AbstractPosixTerminal pt) {
394             subs.add("pty=" + pt.getPty().getClass().getName());
395         }
396         return terminal.getClass().getSimpleName() + " (" + String.join(", ", subs) + ")";
397     }
398 
399     protected void preCommands(C context) throws Exception {
400         Options mavenOptions = context.invokerRequest.options();
401         boolean verbose = mavenOptions.verbose().orElse(false);
402         boolean version = mavenOptions.showVersion().orElse(false);
403         if (verbose || version) {
404             showVersion(context);
405         }
406     }
407 
408     protected void container(C context) throws Exception {
409         context.containerCapsule = createContainerCapsuleFactory().createContainerCapsule(this, context);
410         context.closeables.add(context::closeContainer);
411         context.lookup = context.containerCapsule.getLookup();
412 
413         // refresh logger in case container got customized by spy
414         org.slf4j.Logger l = context.loggerFactory.getLogger(this.getClass().getName());
415         context.logger = (level, message, error) -> l.atLevel(org.slf4j.event.Level.valueOf(level.name()))
416                 .setCause(error)
417                 .log(message);
418     }
419 
420     protected ContainerCapsuleFactory<C> createContainerCapsuleFactory() {
421         return new PlexusContainerCapsuleFactory<>();
422     }
423 
424     protected void postContainer(C context) throws Exception {
425         ProtoSession protoSession = context.protoSession;
426         for (PropertyContributor propertyContributor : context.lookup
427                 .lookup(PropertyContributorsHolder.class)
428                 .getPropertyContributors()
429                 .values()) {
430             protoSession = protoSession.toBuilder()
431                     .withUserProperties(propertyContributor.contribute(protoSession))
432                     .build();
433         }
434         context.protoSession = protoSession;
435     }
436 
437     protected void lookup(C context) throws Exception {}
438 
439     protected void init(C context) throws Exception {}
440 
441     protected void postCommands(C context) throws Exception {
442         InvokerRequest invokerRequest = context.invokerRequest;
443         Logger logger = context.logger;
444         if (invokerRequest.options().showErrors().orElse(false)) {
445             logger.info("Error stacktraces are turned on.");
446         }
447         if (context.invokerRequest.options().verbose().orElse(false)) {
448             logger.debug("Message scheme: " + (MessageUtils.isColorEnabled() ? "color" : "plain"));
449             if (MessageUtils.isColorEnabled()) {
450                 MessageBuilder buff = MessageUtils.builder();
451                 buff.a("Message styles: ");
452                 buff.trace("trace").a(' ');
453                 buff.debug("debug").a(' ');
454                 buff.info("info").a(' ');
455                 buff.warning("warning").a(' ');
456                 buff.error("error").a(' ');
457                 buff.success("success").a(' ');
458                 buff.failure("failure").a(' ');
459                 buff.strong("strong").a(' ');
460                 buff.mojo("mojo").a(' ');
461                 buff.project("project");
462                 logger.debug(buff.toString());
463             }
464         }
465     }
466 
467     protected void settings(C context) throws Exception {
468         settings(context, true, context.lookup.lookup(SettingsBuilder.class));
469     }
470 
471     /**
472      * This method is invoked twice during "normal" LookupInvoker level startup: once when (if present) extensions
473      * are loaded up during Plexus DI creation, and once afterward as "normal" boot procedure.
474      * <p>
475      * If there are Maven3 passwords presents in settings, this results in doubled warnings emitted. So Plexus DI
476      * creation call keeps "emitSettingsWarnings" false. If there are fatal issues, it will anyway "die" at that
477      * spot before warnings would be emitted.
478      */
479     protected void settings(C context, boolean emitSettingsWarnings, SettingsBuilder settingsBuilder) throws Exception {
480         Options mavenOptions = context.invokerRequest.options();
481 
482         Path userSettingsFile = null;
483         if (mavenOptions.altUserSettings().isPresent()) {
484             userSettingsFile =
485                     context.cwdResolver.apply(mavenOptions.altUserSettings().get());
486 
487             if (!Files.isRegularFile(userSettingsFile)) {
488                 throw new FileNotFoundException("The specified user settings file does not exist: " + userSettingsFile);
489             }
490         } else {
491             String userSettingsFileStr =
492                     context.protoSession.getUserProperties().get(Constants.MAVEN_USER_SETTINGS);
493             if (userSettingsFileStr != null) {
494                 userSettingsFile = context.userResolver.apply(userSettingsFileStr);
495             }
496         }
497 
498         Path projectSettingsFile = null;
499         if (mavenOptions.altProjectSettings().isPresent()) {
500             projectSettingsFile =
501                     context.cwdResolver.apply(mavenOptions.altProjectSettings().get());
502 
503             if (!Files.isRegularFile(projectSettingsFile)) {
504                 throw new FileNotFoundException(
505                         "The specified project settings file does not exist: " + projectSettingsFile);
506             }
507         } else {
508             String projectSettingsFileStr =
509                     context.protoSession.getUserProperties().get(Constants.MAVEN_PROJECT_SETTINGS);
510             if (projectSettingsFileStr != null) {
511                 projectSettingsFile = context.cwdResolver.apply(projectSettingsFileStr);
512             }
513         }
514 
515         Path installationSettingsFile = null;
516         if (mavenOptions.altInstallationSettings().isPresent()) {
517             installationSettingsFile = context.cwdResolver.apply(
518                     mavenOptions.altInstallationSettings().get());
519 
520             if (!Files.isRegularFile(installationSettingsFile)) {
521                 throw new FileNotFoundException(
522                         "The specified installation settings file does not exist: " + installationSettingsFile);
523             }
524         } else {
525             String installationSettingsFileStr =
526                     context.protoSession.getUserProperties().get(Constants.MAVEN_INSTALLATION_SETTINGS);
527             if (installationSettingsFileStr != null) {
528                 installationSettingsFile = context.installationResolver.apply(installationSettingsFileStr);
529             }
530         }
531 
532         context.installationSettingsPath = installationSettingsFile;
533         context.projectSettingsPath = projectSettingsFile;
534         context.userSettingsPath = userSettingsFile;
535 
536         Function<String, String> interpolationSource = Interpolator.chain(
537                 context.protoSession.getUserProperties()::get, context.protoSession.getSystemProperties()::get);
538         SettingsBuilderRequest settingsRequest = SettingsBuilderRequest.builder()
539                 .session(context.protoSession)
540                 .installationSettingsSource(
541                         installationSettingsFile != null && Files.exists(installationSettingsFile)
542                                 ? Source.fromPath(installationSettingsFile)
543                                 : null)
544                 .projectSettingsSource(
545                         projectSettingsFile != null && Files.exists(projectSettingsFile)
546                                 ? Source.fromPath(projectSettingsFile)
547                                 : null)
548                 .userSettingsSource(
549                         userSettingsFile != null && Files.exists(userSettingsFile)
550                                 ? Source.fromPath(userSettingsFile)
551                                 : null)
552                 .interpolationSource(interpolationSource)
553                 .build();
554 
555         customizeSettingsRequest(context, settingsRequest);
556 
557         context.logger.debug("Reading installation settings from '" + installationSettingsFile + "'");
558         context.logger.debug("Reading project settings from '" + projectSettingsFile + "'");
559         context.logger.debug("Reading user settings from '" + userSettingsFile + "'");
560 
561         SettingsBuilderResult settingsResult = settingsBuilder.build(settingsRequest);
562         customizeSettingsResult(context, settingsResult);
563 
564         context.effectiveSettings = settingsResult.getEffectiveSettings();
565         context.interactive = mayDisableInteractiveMode(context, context.effectiveSettings.isInteractiveMode());
566         context.localRepositoryPath = localRepositoryPath(context);
567 
568         if (emitSettingsWarnings && !settingsResult.getProblems().isEmpty()) {
569             context.logger.info("");
570             context.logger.info(
571                     "Some problems were encountered while building the effective settings (use -X to see details)");
572 
573             if (context.invokerRequest.options().verbose().orElse(false)) {
574                 for (BuilderProblem problem : settingsResult.getProblems()) {
575                     context.logger.warn(problem.getMessage() + " @ " + problem.getLocation());
576                 }
577             }
578             context.logger.info("");
579         }
580     }
581 
582     protected void customizeSettingsRequest(C context, SettingsBuilderRequest settingsBuilderRequest)
583             throws Exception {}
584 
585     protected void customizeSettingsResult(C context, SettingsBuilderResult settingsBuilderResult) throws Exception {}
586 
587     protected boolean mayDisableInteractiveMode(C context, boolean proposedInteractive) {
588         if (!context.invokerRequest.options().forceInteractive().orElse(false)) {
589             if (context.invokerRequest.options().nonInteractive().orElse(false)) {
590                 return false;
591             } else {
592                 boolean runningOnCI = isRunningOnCI(context);
593                 if (runningOnCI) {
594                     context.logger.info(
595                             "Making this build non-interactive, because the environment variable CI equals \"true\"."
596                                     + " Disable this detection by removing that variable or adding --force-interactive.");
597                     return false;
598                 }
599             }
600         }
601         return proposedInteractive;
602     }
603 
604     protected Path localRepositoryPath(C context) {
605         // user override
606         String userDefinedLocalRepo = context.protoSession.getUserProperties().get(Constants.MAVEN_REPO_LOCAL);
607         if (userDefinedLocalRepo == null) {
608             userDefinedLocalRepo = context.protoSession.getUserProperties().get(Constants.MAVEN_REPO_LOCAL);
609             if (userDefinedLocalRepo != null) {
610                 context.logger.warn("The property '" + Constants.MAVEN_REPO_LOCAL
611                         + "' has been set using a JVM system property which is deprecated. "
612                         + "The property can be passed as a Maven argument or in the Maven project configuration file,"
613                         + "usually located at ${session.rootDirectory}/.mvn/maven.properties.");
614             }
615         }
616         if (userDefinedLocalRepo != null) {
617             return context.cwdResolver.apply(userDefinedLocalRepo);
618         }
619         // settings
620         userDefinedLocalRepo = context.effectiveSettings.getLocalRepository();
621         if (userDefinedLocalRepo != null && !userDefinedLocalRepo.isEmpty()) {
622             return context.userResolver.apply(userDefinedLocalRepo);
623         }
624         // defaults
625         return context.userResolver
626                 .apply(context.protoSession.getUserProperties().get(Constants.MAVEN_USER_CONF))
627                 .resolve("repository");
628     }
629 
630     protected void populateRequest(C context, Lookup lookup, MavenExecutionRequest request) throws Exception {
631         populateRequestFromSettings(request, context.effectiveSettings);
632 
633         Options options = context.invokerRequest.options();
634         request.setLoggingLevel(toMavenExecutionRequestLoggingLevel(context.loggerLevel));
635         request.setLocalRepositoryPath(context.localRepositoryPath.toFile());
636         request.setLocalRepository(createLocalArtifactRepository(context.localRepositoryPath));
637 
638         request.setInteractiveMode(context.interactive);
639         request.setShowErrors(options.showErrors().orElse(false));
640         request.setBaseDirectory(context.invokerRequest.topDirectory().toFile());
641         request.setSystemProperties(toProperties(context.protoSession.getSystemProperties()));
642         request.setUserProperties(toProperties(context.protoSession.getUserProperties()));
643 
644         request.setInstallationSettingsFile(
645                 context.installationSettingsPath != null ? context.installationSettingsPath.toFile() : null);
646         request.setProjectSettingsFile(
647                 context.projectSettingsPath != null ? context.projectSettingsPath.toFile() : null);
648         request.setUserSettingsFile(context.userSettingsPath != null ? context.userSettingsPath.toFile() : null);
649 
650         request.setTopDirectory(context.invokerRequest.topDirectory());
651         if (context.invokerRequest.rootDirectory().isPresent()) {
652             request.setMultiModuleProjectDirectory(
653                     context.invokerRequest.rootDirectory().get().toFile());
654             request.setRootDirectory(context.invokerRequest.rootDirectory().get());
655         }
656 
657         request.addPluginGroup("org.apache.maven.plugins");
658         request.addPluginGroup("org.codehaus.mojo");
659     }
660 
661     /**
662      * TODO: get rid of this!!!
663      */
664     @Deprecated
665     private ArtifactRepository createLocalArtifactRepository(Path baseDirectory) {
666         DefaultRepositoryLayout layout = new DefaultRepositoryLayout();
667         ArtifactRepositoryPolicy blah = new ArtifactRepositoryPolicy(
668                 true, ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS, ArtifactRepositoryPolicy.CHECKSUM_POLICY_IGNORE);
669         return new MavenArtifactRepository(
670                 "local", "file://" + baseDirectory.toUri().getRawPath(), layout, blah, blah);
671     }
672 
673     protected void populateRequestFromSettings(MavenExecutionRequest request, Settings settings) throws Exception {
674         if (settings == null) {
675             return;
676         }
677         request.setOffline(settings.isOffline());
678         request.setInteractiveMode(settings.isInteractiveMode());
679         request.setPluginGroups(settings.getPluginGroups());
680         request.setLocalRepositoryPath(settings.getLocalRepository());
681         for (Server server : settings.getServers()) {
682             request.addServer(new org.apache.maven.settings.Server(server));
683         }
684 
685         //  <proxies>
686         //    <proxy>
687         //      <active>true</active>
688         //      <protocol>http</protocol>
689         //      <host>proxy.somewhere.com</host>
690         //      <port>8080</port>
691         //      <username>proxyuser</username>
692         //      <password>somepassword</password>
693         //      <nonProxyHosts>www.google.com|*.somewhere.com</nonProxyHosts>
694         //    </proxy>
695         //  </proxies>
696 
697         for (Proxy proxy : settings.getProxies()) {
698             if (!proxy.isActive()) {
699                 continue;
700             }
701             request.addProxy(new org.apache.maven.settings.Proxy(proxy));
702         }
703 
704         // <mirrors>
705         //   <mirror>
706         //     <id>nexus</id>
707         //     <mirrorOf>*</mirrorOf>
708         //     <url>http://repository.sonatype.org/content/groups/public</url>
709         //   </mirror>
710         // </mirrors>
711 
712         for (Mirror mirror : settings.getMirrors()) {
713             request.addMirror(new org.apache.maven.settings.Mirror(mirror));
714         }
715 
716         for (Repository remoteRepository : settings.getRepositories()) {
717             try {
718                 request.addRemoteRepository(MavenRepositorySystem.buildArtifactRepository(
719                         new org.apache.maven.settings.Repository(remoteRepository)));
720             } catch (InvalidRepositoryException e) {
721                 // do nothing for now
722             }
723         }
724 
725         for (Repository pluginRepository : settings.getPluginRepositories()) {
726             try {
727                 request.addPluginArtifactRepository(MavenRepositorySystem.buildArtifactRepository(
728                         new org.apache.maven.settings.Repository(pluginRepository)));
729             } catch (InvalidRepositoryException e) {
730                 // do nothing for now
731             }
732         }
733 
734         request.setActiveProfiles(settings.getActiveProfiles());
735         for (Profile rawProfile : settings.getProfiles()) {
736             request.addProfile(
737                     new org.apache.maven.model.Profile(SettingsUtilsV4.convertFromSettingsProfile(rawProfile)));
738 
739             if (settings.getActiveProfiles().contains(rawProfile.getId())) {
740                 List<Repository> remoteRepositories = rawProfile.getRepositories();
741                 for (Repository remoteRepository : remoteRepositories) {
742                     try {
743                         request.addRemoteRepository(MavenRepositorySystem.buildArtifactRepository(
744                                 new org.apache.maven.settings.Repository(remoteRepository)));
745                     } catch (InvalidRepositoryException e) {
746                         // do nothing for now
747                     }
748                 }
749 
750                 List<Repository> pluginRepositories = rawProfile.getPluginRepositories();
751                 for (Repository pluginRepository : pluginRepositories) {
752                     try {
753                         request.addPluginArtifactRepository(MavenRepositorySystem.buildArtifactRepository(
754                                 new org.apache.maven.settings.Repository(pluginRepository)));
755                     } catch (InvalidRepositoryException e) {
756                         // do nothing for now
757                     }
758                 }
759             }
760         }
761     }
762 
763     protected int calculateDegreeOfConcurrency(String threadConfiguration) {
764         try {
765             if (threadConfiguration.endsWith("C")) {
766                 String str = threadConfiguration.substring(0, threadConfiguration.length() - 1);
767                 float coreMultiplier = Float.parseFloat(str);
768 
769                 if (coreMultiplier <= 0.0f) {
770                     throw new IllegalArgumentException("Invalid threads core multiplier value: '" + threadConfiguration
771                             + "'. Value must be positive.");
772                 }
773 
774                 int procs = Runtime.getRuntime().availableProcessors();
775                 int threads = (int) (coreMultiplier * procs);
776                 return threads == 0 ? 1 : threads;
777             } else {
778                 int threads = Integer.parseInt(threadConfiguration);
779                 if (threads <= 0) {
780                     throw new IllegalArgumentException(
781                             "Invalid threads value: '" + threadConfiguration + "'. Value must be positive.");
782                 }
783                 return threads;
784             }
785         } catch (NumberFormatException e) {
786             throw new IllegalArgumentException("Invalid threads value: '" + threadConfiguration
787                     + "'. Supported are int and float values ending with C.");
788         }
789     }
790 
791     protected boolean isRunningOnCI(C context) {
792         String ciEnv = context.protoSession.getSystemProperties().get("env.CI");
793         return ciEnv != null && !"false".equals(ciEnv);
794     }
795 
796     protected abstract int execute(C context) throws Exception;
797 }