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