| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.maven.cli; |
| |
| import javax.xml.stream.XMLStreamException; |
| |
| import java.io.Console; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.PrintStream; |
| import java.nio.charset.Charset; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.*; |
| import java.util.Map.Entry; |
| import java.util.function.Consumer; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.stream.Stream; |
| |
| import com.google.inject.AbstractModule; |
| import org.apache.commons.cli.CommandLine; |
| import org.apache.commons.cli.Option; |
| import org.apache.commons.cli.ParseException; |
| import org.apache.commons.cli.UnrecognizedOptionException; |
| import org.apache.maven.BuildAbort; |
| import org.apache.maven.InternalErrorException; |
| import org.apache.maven.Maven; |
| import org.apache.maven.api.services.MessageBuilder; |
| import org.apache.maven.api.services.MessageBuilderFactory; |
| import org.apache.maven.building.FileSource; |
| import org.apache.maven.building.Problem; |
| import org.apache.maven.building.Source; |
| import org.apache.maven.cli.configuration.ConfigurationProcessor; |
| import org.apache.maven.cli.configuration.SettingsXmlConfigurationProcessor; |
| import org.apache.maven.cli.event.DefaultEventSpyContext; |
| import org.apache.maven.cli.event.ExecutionEventLogger; |
| import org.apache.maven.cli.internal.BootstrapCoreExtensionManager; |
| import org.apache.maven.cli.internal.extension.io.CoreExtensionsStaxReader; |
| import org.apache.maven.cli.internal.extension.model.CoreExtension; |
| import org.apache.maven.cli.logging.Slf4jConfiguration; |
| import org.apache.maven.cli.logging.Slf4jConfigurationFactory; |
| import org.apache.maven.cli.logging.Slf4jLoggerManager; |
| import org.apache.maven.cli.logging.Slf4jStdoutLogger; |
| import org.apache.maven.cli.transfer.*; |
| import org.apache.maven.eventspy.internal.EventSpyDispatcher; |
| import org.apache.maven.exception.DefaultExceptionHandler; |
| import org.apache.maven.exception.ExceptionHandler; |
| import org.apache.maven.exception.ExceptionSummary; |
| import org.apache.maven.execution.DefaultMavenExecutionRequest; |
| import org.apache.maven.execution.ExecutionListener; |
| import org.apache.maven.execution.MavenExecutionRequest; |
| import org.apache.maven.execution.MavenExecutionRequestPopulationException; |
| import org.apache.maven.execution.MavenExecutionRequestPopulator; |
| import org.apache.maven.execution.MavenExecutionResult; |
| import org.apache.maven.execution.ProfileActivation; |
| import org.apache.maven.execution.ProjectActivation; |
| import org.apache.maven.execution.scope.internal.MojoExecutionScope; |
| import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule; |
| import org.apache.maven.extension.internal.CoreExports; |
| import org.apache.maven.extension.internal.CoreExtensionEntry; |
| import org.apache.maven.jline.JLineMessageBuilderFactory; |
| import org.apache.maven.jline.MessageUtils; |
| import org.apache.maven.lifecycle.LifecycleExecutionException; |
| import org.apache.maven.logwrapper.LogLevelRecorder; |
| import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory; |
| import org.apache.maven.model.building.ModelProcessor; |
| import org.apache.maven.model.root.RootLocator; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.properties.internal.EnvironmentUtils; |
| import org.apache.maven.properties.internal.SystemProperties; |
| import org.apache.maven.session.scope.internal.SessionScope; |
| import org.apache.maven.session.scope.internal.SessionScopeModule; |
| import org.apache.maven.toolchain.building.DefaultToolchainsBuildingRequest; |
| import org.apache.maven.toolchain.building.ToolchainsBuilder; |
| import org.apache.maven.toolchain.building.ToolchainsBuildingResult; |
| import org.codehaus.plexus.ContainerConfiguration; |
| import org.codehaus.plexus.DefaultContainerConfiguration; |
| import org.codehaus.plexus.DefaultPlexusContainer; |
| import org.codehaus.plexus.PlexusConstants; |
| import org.codehaus.plexus.PlexusContainer; |
| import org.codehaus.plexus.classworlds.ClassWorld; |
| import org.codehaus.plexus.classworlds.realm.ClassRealm; |
| import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; |
| import org.codehaus.plexus.component.repository.exception.ComponentLookupException; |
| import org.codehaus.plexus.interpolation.AbstractValueSource; |
| import org.codehaus.plexus.interpolation.BasicInterpolator; |
| import org.codehaus.plexus.interpolation.StringSearchInterpolator; |
| import org.codehaus.plexus.logging.LoggerManager; |
| import org.eclipse.aether.DefaultRepositoryCache; |
| import org.eclipse.aether.transfer.TransferListener; |
| import org.slf4j.ILoggerFactory; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.sonatype.plexus.components.cipher.DefaultPlexusCipher; |
| import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher; |
| import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher; |
| import org.sonatype.plexus.components.sec.dispatcher.SecUtil; |
| import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity; |
| |
| import static java.util.Comparator.comparing; |
| import static org.apache.maven.cli.CLIManager.BATCH_MODE; |
| import static org.apache.maven.cli.CLIManager.COLOR; |
| import static org.apache.maven.cli.CLIManager.FORCE_INTERACTIVE; |
| import static org.apache.maven.cli.CLIManager.NON_INTERACTIVE; |
| import static org.apache.maven.cli.ResolveFile.resolveFile; |
| |
| // TODO push all common bits back to plexus cli and prepare for transition to Guice. We don't need 50 ways to make CLIs |
| |
| /** |
| */ |
| public class MavenCli { |
| public static final String LOCAL_REPO_PROPERTY = "maven.repo.local"; |
| |
| public static final String MULTIMODULE_PROJECT_DIRECTORY = "maven.multiModuleProjectDirectory"; |
| |
| public static final String USER_HOME = System.getProperty("user.home"); |
| |
| public static final File USER_MAVEN_CONFIGURATION_HOME = new File(USER_HOME, ".m2"); |
| |
| public static final File DEFAULT_USER_TOOLCHAINS_FILE = new File(USER_MAVEN_CONFIGURATION_HOME, "toolchains.xml"); |
| |
| public static final File DEFAULT_GLOBAL_TOOLCHAINS_FILE = |
| new File(System.getProperty("maven.conf"), "toolchains.xml"); |
| |
| private static final String EXT_CLASS_PATH = "maven.ext.class.path"; |
| |
| private static final String EXTENSIONS_FILENAME = "extensions.xml"; |
| |
| private static final String MVN_EXTENSIONS_FILENAME = ".mvn/" + EXTENSIONS_FILENAME; |
| |
| private static final String MVN_MAVEN_CONFIG = ".mvn/maven.config"; |
| |
| public static final String STYLE_COLOR_PROPERTY = "style.color"; |
| |
| private ClassWorld classWorld; |
| |
| private LoggerManager plexusLoggerManager; |
| |
| private ILoggerFactory slf4jLoggerFactory; |
| |
| private Logger slf4jLogger; |
| |
| private EventSpyDispatcher eventSpyDispatcher; |
| |
| private ModelProcessor modelProcessor; |
| |
| private Maven maven; |
| |
| private MavenExecutionRequestPopulator executionRequestPopulator; |
| |
| private ToolchainsBuilder toolchainsBuilder; |
| |
| private DefaultSecDispatcher dispatcher; |
| |
| private Map<String, ConfigurationProcessor> configurationProcessors; |
| |
| private CLIManager cliManager; |
| |
| private MessageBuilderFactory messageBuilderFactory; |
| |
| private static final Pattern NEXT_LINE = Pattern.compile("\r?\n"); |
| |
| public MavenCli() { |
| this(null); |
| } |
| |
| // This supports painless invocation by the Verifier during embedded execution of the core ITs |
| public MavenCli(ClassWorld classWorld) { |
| this.classWorld = classWorld; |
| this.messageBuilderFactory = new JLineMessageBuilderFactory(); |
| } |
| |
| public static void main(String[] args) { |
| int result = main(args, null); |
| |
| System.exit(result); |
| } |
| |
| public static int main(String[] args, ClassWorld classWorld) { |
| MavenCli cli = new MavenCli(); |
| |
| MessageUtils.systemInstall(); |
| MessageUtils.registerShutdownHook(); |
| int result = cli.doMain(new CliRequest(args, classWorld)); |
| MessageUtils.systemUninstall(); |
| |
| return result; |
| } |
| |
| // TODO need to externalize CliRequest |
| public static int doMain(String[] args, ClassWorld classWorld) { |
| MavenCli cli = new MavenCli(); |
| return cli.doMain(new CliRequest(args, classWorld)); |
| } |
| |
| /** |
| * This supports painless invocation by the Verifier during embedded execution of the core ITs. |
| * See <a href="http://maven.apache.org/shared/maven-verifier/xref/org/apache/maven/it/Embedded3xLauncher.html"> |
| * <code>Embedded3xLauncher</code> in <code>maven-verifier</code></a> |
| * |
| * @param args CLI args |
| * @param workingDirectory working directory |
| * @param stdout stdout |
| * @param stderr stderr |
| * @return return code |
| */ |
| public int doMain(String[] args, String workingDirectory, PrintStream stdout, PrintStream stderr) { |
| PrintStream oldout = System.out; |
| PrintStream olderr = System.err; |
| |
| final Set<String> realms; |
| if (classWorld != null) { |
| realms = new HashSet<>(); |
| for (ClassRealm realm : classWorld.getRealms()) { |
| realms.add(realm.getId()); |
| } |
| } else { |
| realms = Collections.emptySet(); |
| } |
| |
| try { |
| if (stdout != null) { |
| System.setOut(stdout); |
| } |
| if (stderr != null) { |
| System.setErr(stderr); |
| } |
| |
| CliRequest cliRequest = new CliRequest(args, classWorld); |
| cliRequest.workingDirectory = workingDirectory; |
| |
| return doMain(cliRequest); |
| } finally { |
| if (classWorld != null) { |
| for (ClassRealm realm : new ArrayList<>(classWorld.getRealms())) { |
| String realmId = realm.getId(); |
| if (!realms.contains(realmId)) { |
| try { |
| classWorld.disposeRealm(realmId); |
| } catch (NoSuchRealmException ignored) { |
| // can't happen |
| } |
| } |
| } |
| } |
| System.setOut(oldout); |
| System.setErr(olderr); |
| } |
| } |
| |
| // TODO need to externalize CliRequest |
| public int doMain(CliRequest cliRequest) { |
| PlexusContainer localContainer = null; |
| try { |
| initialize(cliRequest); |
| cli(cliRequest); |
| properties(cliRequest); |
| logging(cliRequest); |
| informativeCommands(cliRequest); |
| version(cliRequest); |
| localContainer = container(cliRequest); |
| commands(cliRequest); |
| configure(cliRequest); |
| toolchains(cliRequest); |
| populateRequest(cliRequest); |
| encryption(cliRequest); |
| return execute(cliRequest); |
| } catch (ExitException e) { |
| return e.exitCode; |
| } catch (UnrecognizedOptionException e) { |
| // pure user error, suppress stack trace |
| return 1; |
| } catch (BuildAbort e) { |
| CLIReportingUtils.showError(slf4jLogger, "ABORTED", e, cliRequest.showErrors); |
| |
| return 2; |
| } catch (Exception e) { |
| CLIReportingUtils.showError(slf4jLogger, "Error executing Maven.", e, cliRequest.showErrors); |
| |
| return 1; |
| } finally { |
| if (localContainer != null) { |
| localContainer.dispose(); |
| } |
| } |
| } |
| |
| void initialize(CliRequest cliRequest) throws ExitException { |
| if (cliRequest.workingDirectory == null) { |
| cliRequest.workingDirectory = System.getProperty("user.dir"); |
| } |
| |
| if (cliRequest.multiModuleProjectDirectory == null) { |
| String basedirProperty = System.getProperty(MULTIMODULE_PROJECT_DIRECTORY); |
| if (basedirProperty == null) { |
| System.err.format("-D%s system property is not set.", MULTIMODULE_PROJECT_DIRECTORY); |
| throw new ExitException(1); |
| } |
| File basedir = new File(basedirProperty); |
| try { |
| cliRequest.multiModuleProjectDirectory = basedir.getCanonicalFile(); |
| } catch (IOException e) { |
| cliRequest.multiModuleProjectDirectory = basedir.getAbsoluteFile(); |
| } |
| } |
| |
| // We need to locate the top level project which may be pointed at using |
| // the -f/--file option. However, the command line isn't parsed yet, so |
| // we need to iterate through the args to find it and act upon it. |
| Path topDirectory = Paths.get(cliRequest.workingDirectory); |
| boolean isAltFile = false; |
| for (String arg : cliRequest.args) { |
| if (isAltFile) { |
| // this is the argument following -f/--file |
| Path path = topDirectory.resolve(stripLeadingAndTrailingQuotes(arg)); |
| if (Files.isDirectory(path)) { |
| topDirectory = path; |
| } else if (Files.isRegularFile(path)) { |
| topDirectory = path.getParent(); |
| if (!Files.isDirectory(topDirectory)) { |
| System.err.println("Directory " + topDirectory |
| + " extracted from the -f/--file command-line argument " + arg + " does not exist"); |
| throw new ExitException(1); |
| } |
| } else { |
| System.err.println( |
| "POM file " + arg + " specified with the -f/--file command line argument does not exist"); |
| throw new ExitException(1); |
| } |
| break; |
| } else { |
| // Check if this is the -f/--file option |
| isAltFile = arg.equals("-f") || arg.equals("--file"); |
| } |
| } |
| topDirectory = getCanonicalPath(topDirectory); |
| cliRequest.topDirectory = topDirectory; |
| // We're very early in the process, and we don't have the container set up yet, |
| // so we rely on the JDK services to eventually look up a custom RootLocator. |
| // This is used to compute {@code session.rootDirectory} but all {@code project.rootDirectory} |
| // properties will be computed through the RootLocator found in the container. |
| RootLocator rootLocator = |
| ServiceLoader.load(RootLocator.class).iterator().next(); |
| cliRequest.rootDirectory = rootLocator.findRoot(topDirectory); |
| |
| // |
| // Make sure the Maven home directory is an absolute path to save us from confusion with say drive-relative |
| // Windows paths. |
| // |
| String mavenHome = System.getProperty("maven.home"); |
| |
| if (mavenHome != null) { |
| System.setProperty("maven.home", new File(mavenHome).getAbsolutePath()); |
| } |
| } |
| |
| void cli(CliRequest cliRequest) throws Exception { |
| // |
| // Parsing errors can happen during the processing of the arguments and we prefer not having to check if |
| // the logger is null and construct this so we can use an SLF4J logger everywhere. |
| // |
| slf4jLogger = new Slf4jStdoutLogger(); |
| |
| cliManager = new CLIManager(); |
| |
| CommandLine mavenConfig = null; |
| try { |
| File configFile = new File(cliRequest.multiModuleProjectDirectory, MVN_MAVEN_CONFIG); |
| |
| if (configFile.isFile()) { |
| try (Stream<String> lines = Files.lines(configFile.toPath(), Charset.defaultCharset())) { |
| String[] args = lines.filter(arg -> !arg.isEmpty() && !arg.startsWith("#")) |
| .toArray(String[]::new); |
| mavenConfig = cliManager.parse(args); |
| List<?> unrecognized = mavenConfig.getArgList(); |
| if (!unrecognized.isEmpty()) { |
| // This file can only contain options, not args (goals or phases) |
| throw new ParseException("Unrecognized maven.config file entries: " + unrecognized); |
| } |
| } |
| } |
| } catch (ParseException e) { |
| System.err.println("Unable to parse maven.config file options: " + e.getMessage()); |
| cliManager.displayHelp(System.out); |
| throw e; |
| } |
| |
| try { |
| CommandLine mavenCli = cliManager.parse(cliRequest.args); |
| if (mavenConfig == null) { |
| cliRequest.commandLine = mavenCli; |
| } else { |
| cliRequest.commandLine = cliMerge(mavenConfig, mavenCli); |
| } |
| } catch (ParseException e) { |
| System.err.println("Unable to parse command line options: " + e.getMessage()); |
| cliManager.displayHelp(System.out); |
| throw e; |
| } |
| |
| // check for presence of unsupported command line options |
| try { |
| if (cliRequest.commandLine.hasOption("llr")) { |
| throw new UnrecognizedOptionException("Option '-llr' is not supported starting with Maven 3.9.1"); |
| } |
| } catch (ParseException e) { |
| System.err.println("Unsupported options: " + e.getMessage()); |
| cliManager.displayHelp(System.out); |
| throw e; |
| } |
| } |
| |
| private void informativeCommands(CliRequest cliRequest) throws ExitException { |
| if (cliRequest.commandLine.hasOption(CLIManager.HELP)) { |
| cliManager.displayHelp(System.out); |
| throw new ExitException(0); |
| } |
| |
| if (cliRequest.commandLine.hasOption(CLIManager.VERSION)) { |
| if (cliRequest.commandLine.hasOption(CLIManager.QUIET)) { |
| System.out.println(CLIReportingUtils.showVersionMinimal()); |
| } else { |
| System.out.println(CLIReportingUtils.showVersion()); |
| } |
| throw new ExitException(0); |
| } |
| |
| if (cliRequest.rootDirectory == null) { |
| slf4jLogger.info(RootLocator.UNABLE_TO_FIND_ROOT_PROJECT_MESSAGE); |
| } |
| } |
| |
| private CommandLine cliMerge(CommandLine mavenConfig, CommandLine mavenCli) { |
| CommandLine.Builder commandLineBuilder = new CommandLine.Builder(); |
| |
| // the args are easy, CLI only since maven.config file can only contain options |
| for (String arg : mavenCli.getArgs()) { |
| commandLineBuilder.addArg(arg); |
| } |
| |
| /* Although this looks wrong in terms of order Commons CLI stores the value of options in |
| * an array and when a value is potentionally overriden it is added to the array. The single |
| * arg option value is retrieved and instead of returning values[values.length-1] it returns |
| * values[0] which means that the original value instead of the overridden one is returned |
| * (first wins). With properties values are truely overriden since at the end a map is used |
| * to merge which means last wins. |
| * |
| * TODO Report this behavioral bug with Commons CLI |
| */ |
| // now add all options, except for user properties with CLI first then maven.config file |
| List<Option> setPropertyOptions = new ArrayList<>(); |
| for (Option opt : mavenCli.getOptions()) { |
| if (String.valueOf(CLIManager.SET_USER_PROPERTY).equals(opt.getOpt())) { |
| setPropertyOptions.add(opt); |
| } else { |
| commandLineBuilder.addOption(opt); |
| } |
| } |
| for (Option opt : mavenConfig.getOptions()) { |
| commandLineBuilder.addOption(opt); |
| } |
| // finally add the CLI user properties |
| for (Option opt : setPropertyOptions) { |
| commandLineBuilder.addOption(opt); |
| } |
| return commandLineBuilder.build(); |
| } |
| |
| /** |
| * configure logging |
| */ |
| void logging(CliRequest cliRequest) { |
| // LOG LEVEL |
| CommandLine commandLine = cliRequest.commandLine; |
| cliRequest.verbose = commandLine.hasOption(CLIManager.VERBOSE) || commandLine.hasOption(CLIManager.DEBUG); |
| cliRequest.quiet = !cliRequest.verbose && commandLine.hasOption(CLIManager.QUIET); |
| cliRequest.showErrors = cliRequest.verbose || commandLine.hasOption(CLIManager.ERRORS); |
| |
| // LOG COLOR |
| String styleColor = cliRequest.getUserProperties().getProperty(STYLE_COLOR_PROPERTY, "auto"); |
| styleColor = commandLine.getOptionValue(COLOR, styleColor); |
| if ("always".equals(styleColor) || "yes".equals(styleColor) || "force".equals(styleColor)) { |
| MessageUtils.setColorEnabled(true); |
| } else if ("never".equals(styleColor) || "no".equals(styleColor) || "none".equals(styleColor)) { |
| MessageUtils.setColorEnabled(false); |
| } else if (!"auto".equals(styleColor) && !"tty".equals(styleColor) && !"if-tty".equals(styleColor)) { |
| throw new IllegalArgumentException( |
| "Invalid color configuration value '" + styleColor + "'. Supported are 'auto', 'always', 'never'."); |
| } else { |
| boolean isBatchMode = !commandLine.hasOption(FORCE_INTERACTIVE) |
| && (commandLine.hasOption(BATCH_MODE) || commandLine.hasOption(NON_INTERACTIVE)); |
| if (isBatchMode || commandLine.hasOption(CLIManager.LOG_FILE)) { |
| MessageUtils.setColorEnabled(false); |
| } |
| } |
| |
| slf4jLoggerFactory = LoggerFactory.getILoggerFactory(); |
| Slf4jConfiguration slf4jConfiguration = Slf4jConfigurationFactory.getConfiguration(slf4jLoggerFactory); |
| |
| if (cliRequest.verbose) { |
| cliRequest.request.setLoggingLevel(MavenExecutionRequest.LOGGING_LEVEL_DEBUG); |
| slf4jConfiguration.setRootLoggerLevel(Slf4jConfiguration.Level.DEBUG); |
| } else if (cliRequest.quiet) { |
| cliRequest.request.setLoggingLevel(MavenExecutionRequest.LOGGING_LEVEL_ERROR); |
| slf4jConfiguration.setRootLoggerLevel(Slf4jConfiguration.Level.ERROR); |
| } |
| // else fall back to default log level specified in conf |
| // see https://issues.apache.org/jira/browse/MNG-2570 |
| |
| // LOG STREAMS |
| if (commandLine.hasOption(CLIManager.LOG_FILE)) { |
| File logFile = new File(commandLine.getOptionValue(CLIManager.LOG_FILE)); |
| logFile = resolveFile(logFile, cliRequest.workingDirectory); |
| |
| // redirect stdout and stderr to file |
| try { |
| PrintStream ps = new PrintStream(new FileOutputStream(logFile)); |
| System.setOut(ps); |
| System.setErr(ps); |
| } catch (FileNotFoundException e) { |
| // |
| // Ignore |
| // |
| } |
| } |
| |
| slf4jConfiguration.activate(); |
| |
| plexusLoggerManager = new Slf4jLoggerManager(); |
| slf4jLogger = slf4jLoggerFactory.getLogger(this.getClass().getName()); |
| |
| if (commandLine.hasOption(CLIManager.FAIL_ON_SEVERITY)) { |
| String logLevelThreshold = commandLine.getOptionValue(CLIManager.FAIL_ON_SEVERITY); |
| |
| if (slf4jLoggerFactory instanceof MavenSlf4jWrapperFactory) { |
| LogLevelRecorder logLevelRecorder = new LogLevelRecorder(logLevelThreshold); |
| ((MavenSlf4jWrapperFactory) slf4jLoggerFactory).setLogLevelRecorder(logLevelRecorder); |
| slf4jLogger.info("Enabled to break the build on log level {}.", logLevelThreshold); |
| } else { |
| slf4jLogger.warn( |
| "Expected LoggerFactory to be of type '{}', but found '{}' instead. " |
| + "The --fail-on-severity flag will not take effect.", |
| MavenSlf4jWrapperFactory.class.getName(), |
| slf4jLoggerFactory.getClass().getName()); |
| } |
| } |
| |
| if (commandLine.hasOption(CLIManager.DEBUG)) { |
| slf4jLogger.warn("The option '--debug' is deprecated and may be repurposed as Java debug" |
| + " in a future version. Use -X/--verbose instead."); |
| } |
| } |
| |
| private void version(CliRequest cliRequest) { |
| if (cliRequest.verbose || cliRequest.commandLine.hasOption(CLIManager.SHOW_VERSION)) { |
| System.out.println(CLIReportingUtils.showVersion()); |
| } |
| } |
| |
| private void commands(CliRequest cliRequest) { |
| if (cliRequest.showErrors) { |
| slf4jLogger.info("Error stacktraces are turned on."); |
| } |
| |
| if (MavenExecutionRequest.CHECKSUM_POLICY_WARN.equals(cliRequest.request.getGlobalChecksumPolicy())) { |
| slf4jLogger.info("Disabling strict checksum verification on all artifact downloads."); |
| } else if (MavenExecutionRequest.CHECKSUM_POLICY_FAIL.equals(cliRequest.request.getGlobalChecksumPolicy())) { |
| slf4jLogger.info("Enabling strict checksum verification on all artifact downloads."); |
| } |
| |
| if (slf4jLogger.isDebugEnabled()) { |
| slf4jLogger.debug("Message scheme: {}", (MessageUtils.isColorEnabled() ? "color" : "plain")); |
| if (MessageUtils.isColorEnabled()) { |
| MessageBuilder buff = MessageUtils.builder(); |
| buff.a("Message styles: "); |
| buff.trace("trace").a(' '); |
| buff.debug("debug").a(' '); |
| buff.info("info").a(' '); |
| buff.warning("warning").a(' '); |
| buff.error("error").a(' '); |
| buff.success("success").a(' '); |
| buff.failure("failure").a(' '); |
| buff.strong("strong").a(' '); |
| buff.mojo("mojo").a(' '); |
| buff.project("project"); |
| slf4jLogger.debug(buff.toString()); |
| } |
| } |
| } |
| |
| // Needed to make this method package visible to make writing a unit test possible |
| // Maybe it's better to move some of those methods to separate class (SoC). |
| void properties(CliRequest cliRequest) throws Exception { |
| Properties paths = new Properties(); |
| if (cliRequest.topDirectory != null) { |
| paths.put("session.topDirectory", cliRequest.topDirectory.toString()); |
| } |
| if (cliRequest.rootDirectory != null) { |
| paths.put("session.rootDirectory", cliRequest.rootDirectory.toString()); |
| } |
| |
| populateProperties(cliRequest.commandLine, paths, cliRequest.systemProperties, cliRequest.userProperties); |
| |
| // now that we have properties, interpolate all arguments |
| BasicInterpolator interpolator = |
| createInterpolator(paths, cliRequest.systemProperties, cliRequest.userProperties); |
| CommandLine.Builder commandLineBuilder = new CommandLine.Builder(); |
| for (Option option : cliRequest.commandLine.getOptions()) { |
| if (!String.valueOf(CLIManager.SET_USER_PROPERTY).equals(option.getOpt())) { |
| List<String> values = option.getValuesList(); |
| for (ListIterator<String> it = values.listIterator(); it.hasNext(); ) { |
| it.set(interpolator.interpolate(it.next())); |
| } |
| } |
| commandLineBuilder.addOption(option); |
| } |
| for (String arg : cliRequest.commandLine.getArgList()) { |
| commandLineBuilder.addArg(interpolator.interpolate(arg)); |
| } |
| cliRequest.commandLine = commandLineBuilder.build(); |
| } |
| |
| PlexusContainer container(CliRequest cliRequest) throws Exception { |
| if (cliRequest.classWorld == null) { |
| cliRequest.classWorld = |
| new ClassWorld("plexus.core", Thread.currentThread().getContextClassLoader()); |
| } |
| |
| ClassRealm coreRealm = cliRequest.classWorld.getClassRealm("plexus.core"); |
| if (coreRealm == null) { |
| coreRealm = cliRequest.classWorld.getRealms().iterator().next(); |
| } |
| |
| List<File> extClassPath = parseExtClasspath(cliRequest); |
| |
| CoreExtensionEntry coreEntry = CoreExtensionEntry.discoverFrom(coreRealm); |
| List<CoreExtensionEntry> extensions = |
| loadCoreExtensions(cliRequest, coreRealm, coreEntry.getExportedArtifacts()); |
| |
| ClassRealm containerRealm = setupContainerRealm(cliRequest.classWorld, coreRealm, extClassPath, extensions); |
| |
| ContainerConfiguration cc = new DefaultContainerConfiguration() |
| .setClassWorld(cliRequest.classWorld) |
| .setRealm(containerRealm) |
| .setClassPathScanning(PlexusConstants.SCANNING_INDEX) |
| .setAutoWiring(true) |
| .setJSR250Lifecycle(true) |
| .setName("maven"); |
| |
| Set<String> exportedArtifacts = new HashSet<>(coreEntry.getExportedArtifacts()); |
| Set<String> exportedPackages = new HashSet<>(coreEntry.getExportedPackages()); |
| for (CoreExtensionEntry extension : extensions) { |
| exportedArtifacts.addAll(extension.getExportedArtifacts()); |
| exportedPackages.addAll(extension.getExportedPackages()); |
| } |
| |
| final CoreExports exports = new CoreExports(containerRealm, exportedArtifacts, exportedPackages); |
| |
| DefaultPlexusContainer container = new DefaultPlexusContainer(cc, new AbstractModule() { |
| @Override |
| protected void configure() { |
| bind(ILoggerFactory.class).toInstance(slf4jLoggerFactory); |
| bind(CoreExports.class).toInstance(exports); |
| bind(MessageBuilderFactory.class).toInstance(messageBuilderFactory); |
| } |
| }); |
| |
| // NOTE: To avoid inconsistencies, we'll use the TCCL exclusively for lookups |
| container.setLookupRealm(null); |
| Thread.currentThread().setContextClassLoader(container.getContainerRealm()); |
| |
| container.setLoggerManager(plexusLoggerManager); |
| |
| AbstractValueSource extensionSource = new AbstractValueSource(false) { |
| @Override |
| public Object getValue(String expression) { |
| Object value = cliRequest.userProperties.getProperty(expression); |
| if (value == null) { |
| value = cliRequest.systemProperties.getProperty(expression); |
| } |
| return value; |
| } |
| }; |
| for (CoreExtensionEntry extension : extensions) { |
| container.discoverComponents( |
| extension.getClassRealm(), |
| new SessionScopeModule(container.lookup(SessionScope.class)), |
| new MojoExecutionScopeModule(container.lookup(MojoExecutionScope.class)), |
| new ExtensionConfigurationModule(extension, extensionSource)); |
| } |
| |
| customizeContainer(container); |
| |
| container.getLoggerManager().setThresholds(cliRequest.request.getLoggingLevel()); |
| |
| eventSpyDispatcher = container.lookup(EventSpyDispatcher.class); |
| |
| DefaultEventSpyContext eventSpyContext = new DefaultEventSpyContext(); |
| Map<String, Object> data = eventSpyContext.getData(); |
| data.put("plexus", container); |
| data.put("workingDirectory", cliRequest.workingDirectory); |
| data.put("systemProperties", cliRequest.systemProperties); |
| data.put("userProperties", cliRequest.userProperties); |
| data.put("versionProperties", CLIReportingUtils.getBuildProperties()); |
| eventSpyDispatcher.init(eventSpyContext); |
| |
| // refresh logger in case container got customized by spy |
| slf4jLogger = slf4jLoggerFactory.getLogger(this.getClass().getName()); |
| |
| maven = container.lookup(Maven.class); |
| |
| executionRequestPopulator = container.lookup(MavenExecutionRequestPopulator.class); |
| |
| modelProcessor = createModelProcessor(container); |
| |
| configurationProcessors = container.lookupMap(ConfigurationProcessor.class); |
| |
| toolchainsBuilder = container.lookup(ToolchainsBuilder.class); |
| |
| dispatcher = (DefaultSecDispatcher) container.lookup(SecDispatcher.class, "maven"); |
| |
| return container; |
| } |
| |
| private List<CoreExtensionEntry> loadCoreExtensions( |
| CliRequest cliRequest, ClassRealm containerRealm, Set<String> providedArtifacts) throws Exception { |
| if (cliRequest.multiModuleProjectDirectory == null) { |
| return Collections.emptyList(); |
| } |
| |
| File extensionsFile = new File(cliRequest.multiModuleProjectDirectory, MVN_EXTENSIONS_FILENAME); |
| File userHomeExtensionsFile = new File(USER_MAVEN_CONFIGURATION_HOME, EXTENSIONS_FILENAME); |
| |
| List<CoreExtension> extensions = new ArrayList<>(); |
| if (extensionsFile.isFile()) { |
| extensions.addAll(readCoreExtensionsDescriptor(extensionsFile)); |
| } |
| if (userHomeExtensionsFile.isFile()) { |
| extensions.addAll(readCoreExtensionsDescriptor(userHomeExtensionsFile)); |
| } |
| |
| if (extensions.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| |
| ContainerConfiguration cc = new DefaultContainerConfiguration() // |
| .setClassWorld(cliRequest.classWorld) // |
| .setRealm(containerRealm) // |
| .setClassPathScanning(PlexusConstants.SCANNING_INDEX) // |
| .setAutoWiring(true) // |
| .setJSR250Lifecycle(true) // |
| .setName("maven"); |
| |
| DefaultPlexusContainer container = new DefaultPlexusContainer(cc, new AbstractModule() { |
| @Override |
| protected void configure() { |
| bind(ILoggerFactory.class).toInstance(slf4jLoggerFactory); |
| } |
| }); |
| |
| try { |
| container.setLookupRealm(null); |
| |
| container.setLoggerManager(plexusLoggerManager); |
| |
| container.getLoggerManager().setThresholds(cliRequest.request.getLoggingLevel()); |
| |
| Thread.currentThread().setContextClassLoader(container.getContainerRealm()); |
| |
| executionRequestPopulator = container.lookup(MavenExecutionRequestPopulator.class); |
| |
| configurationProcessors = container.lookupMap(ConfigurationProcessor.class); |
| |
| configure(cliRequest); |
| |
| MavenExecutionRequest request = DefaultMavenExecutionRequest.copy(cliRequest.request); |
| |
| populateRequest(cliRequest, request); |
| |
| request = executionRequestPopulator.populateDefaults(request); |
| |
| BootstrapCoreExtensionManager resolver = container.lookup(BootstrapCoreExtensionManager.class); |
| |
| return Collections.unmodifiableList(resolver.loadCoreExtensions(request, providedArtifacts, extensions)); |
| |
| } finally { |
| executionRequestPopulator = null; |
| container.dispose(); |
| } |
| } |
| |
| private List<CoreExtension> readCoreExtensionsDescriptor(File extensionsFile) |
| throws IOException, XMLStreamException { |
| CoreExtensionsStaxReader parser = new CoreExtensionsStaxReader(); |
| |
| try (InputStream is = Files.newInputStream(extensionsFile.toPath())) { |
| return parser.read(is, true).getExtensions(); |
| } |
| } |
| |
| private ClassRealm setupContainerRealm( |
| ClassWorld classWorld, ClassRealm coreRealm, List<File> extClassPath, List<CoreExtensionEntry> extensions) |
| throws Exception { |
| if (!extClassPath.isEmpty() || !extensions.isEmpty()) { |
| ClassRealm extRealm = classWorld.newRealm("maven.ext", null); |
| |
| extRealm.setParentRealm(coreRealm); |
| |
| slf4jLogger.debug("Populating class realm '{}'", extRealm.getId()); |
| |
| for (File file : extClassPath) { |
| slf4jLogger.debug(" included '{}'", file); |
| |
| extRealm.addURL(file.toURI().toURL()); |
| } |
| |
| for (CoreExtensionEntry entry : reverse(extensions)) { |
| Set<String> exportedPackages = entry.getExportedPackages(); |
| ClassRealm realm = entry.getClassRealm(); |
| for (String exportedPackage : exportedPackages) { |
| extRealm.importFrom(realm, exportedPackage); |
| } |
| if (exportedPackages.isEmpty()) { |
| // sisu uses realm imports to establish component visibility |
| extRealm.importFrom(realm, realm.getId()); |
| } |
| } |
| |
| return extRealm; |
| } |
| |
| return coreRealm; |
| } |
| |
| private static <T> List<T> reverse(List<T> list) { |
| List<T> copy = new ArrayList<>(list); |
| Collections.reverse(copy); |
| return copy; |
| } |
| |
| private List<File> parseExtClasspath(CliRequest cliRequest) { |
| String extClassPath = cliRequest.userProperties.getProperty(EXT_CLASS_PATH); |
| if (extClassPath == null) { |
| extClassPath = cliRequest.systemProperties.getProperty(EXT_CLASS_PATH); |
| } |
| |
| List<File> jars = new ArrayList<>(); |
| |
| if (extClassPath != null && !extClassPath.isEmpty()) { |
| for (String jar : extClassPath.split(File.pathSeparator)) { |
| File file = resolveFile(new File(jar), cliRequest.workingDirectory); |
| |
| slf4jLogger.debug(" included '{}'", file); |
| |
| jars.add(file); |
| } |
| } |
| |
| return jars; |
| } |
| |
| // |
| // This should probably be a separate tool and not be baked into Maven. |
| // |
| private void encryption(CliRequest cliRequest) throws Exception { |
| if (cliRequest.commandLine.hasOption(CLIManager.ENCRYPT_MASTER_PASSWORD)) { |
| String passwd = cliRequest.commandLine.getOptionValue(CLIManager.ENCRYPT_MASTER_PASSWORD); |
| |
| if (passwd == null) { |
| Console cons = System.console(); |
| char[] password = (cons == null) ? null : cons.readPassword("Master password: "); |
| if (password != null) { |
| // Cipher uses Strings |
| passwd = String.copyValueOf(password); |
| |
| // Sun/Oracle advises to empty the char array |
| java.util.Arrays.fill(password, ' '); |
| } |
| } |
| |
| DefaultPlexusCipher cipher = new DefaultPlexusCipher(); |
| |
| System.out.println(cipher.encryptAndDecorate(passwd, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION)); |
| |
| throw new ExitException(0); |
| } else if (cliRequest.commandLine.hasOption(CLIManager.ENCRYPT_PASSWORD)) { |
| String passwd = cliRequest.commandLine.getOptionValue(CLIManager.ENCRYPT_PASSWORD); |
| |
| if (passwd == null) { |
| Console cons = System.console(); |
| char[] password = (cons == null) ? null : cons.readPassword("Password: "); |
| if (password != null) { |
| // Cipher uses Strings |
| passwd = String.copyValueOf(password); |
| |
| // Sun/Oracle advises to empty the char array |
| java.util.Arrays.fill(password, ' '); |
| } |
| } |
| |
| String configurationFile = dispatcher.getConfigurationFile(); |
| |
| if (configurationFile.startsWith("~")) { |
| configurationFile = System.getProperty("user.home") + configurationFile.substring(1); |
| } |
| |
| String file = System.getProperty(DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION, configurationFile); |
| |
| String master = null; |
| |
| SettingsSecurity sec = SecUtil.read(file, true); |
| if (sec != null) { |
| master = sec.getMaster(); |
| } |
| |
| if (master == null) { |
| throw new IllegalStateException("Master password is not set in the setting security file: " + file); |
| } |
| |
| DefaultPlexusCipher cipher = new DefaultPlexusCipher(); |
| String masterPasswd = cipher.decryptDecorated(master, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION); |
| System.out.println(cipher.encryptAndDecorate(passwd, masterPasswd)); |
| |
| throw new ExitException(0); |
| } |
| } |
| |
| private int execute(CliRequest cliRequest) throws MavenExecutionRequestPopulationException { |
| MavenExecutionRequest request = executionRequestPopulator.populateDefaults(cliRequest.request); |
| |
| if (cliRequest.request.getRepositoryCache() == null) { |
| cliRequest.request.setRepositoryCache(new DefaultRepositoryCache()); |
| } |
| |
| eventSpyDispatcher.onEvent(request); |
| |
| MavenExecutionResult result = maven.execute(request); |
| |
| eventSpyDispatcher.onEvent(result); |
| |
| eventSpyDispatcher.close(); |
| |
| if (result.hasExceptions()) { |
| ExceptionHandler handler = new DefaultExceptionHandler(); |
| |
| Map<String, String> references = new LinkedHashMap<>(); |
| |
| List<MavenProject> failedProjects = new ArrayList<>(); |
| |
| for (Throwable exception : result.getExceptions()) { |
| ExceptionSummary summary = handler.handleException(exception); |
| |
| logSummary(summary, references, "", cliRequest.showErrors); |
| |
| if (exception instanceof LifecycleExecutionException) { |
| failedProjects.add(((LifecycleExecutionException) exception).getProject()); |
| } |
| } |
| |
| slf4jLogger.error(""); |
| |
| if (!cliRequest.showErrors) { |
| slf4jLogger.error( |
| "To see the full stack trace of the errors, re-run Maven with the '{}' switch", |
| MessageUtils.builder().strong("-e")); |
| } |
| if (!slf4jLogger.isDebugEnabled()) { |
| slf4jLogger.error( |
| "Re-run Maven using the '{}' switch to enable verbose output", |
| MessageUtils.builder().strong("-X")); |
| } |
| |
| if (!references.isEmpty()) { |
| slf4jLogger.error(""); |
| slf4jLogger.error("For more information about the errors and possible solutions" |
| + ", please read the following articles:"); |
| |
| for (Map.Entry<String, String> entry : references.entrySet()) { |
| slf4jLogger.error("{} {}", MessageUtils.builder().strong(entry.getValue()), entry.getKey()); |
| } |
| } |
| |
| if (result.canResume()) { |
| logBuildResumeHint("mvn [args] -r"); |
| } else if (!failedProjects.isEmpty()) { |
| List<MavenProject> sortedProjects = result.getTopologicallySortedProjects(); |
| |
| // Sort the failedProjects list in the topologically sorted order. |
| failedProjects.sort(comparing(sortedProjects::indexOf)); |
| |
| MavenProject firstFailedProject = failedProjects.get(0); |
| if (!firstFailedProject.equals(sortedProjects.get(0))) { |
| String resumeFromSelector = getResumeFromSelector(sortedProjects, firstFailedProject); |
| logBuildResumeHint("mvn [args] -rf " + resumeFromSelector); |
| } |
| } |
| |
| if (MavenExecutionRequest.REACTOR_FAIL_NEVER.equals(cliRequest.request.getReactorFailureBehavior())) { |
| slf4jLogger.info("Build failures were ignored."); |
| |
| return 0; |
| } else { |
| return 1; |
| } |
| } else { |
| return 0; |
| } |
| } |
| |
| private void logBuildResumeHint(String resumeBuildHint) { |
| slf4jLogger.error(""); |
| slf4jLogger.error("After correcting the problems, you can resume the build with the command"); |
| slf4jLogger.error(MessageUtils.builder().a(" ").strong(resumeBuildHint).toString()); |
| } |
| |
| /** |
| * A helper method to determine the value to resume the build with {@code -rf} taking into account the edge case |
| * where multiple modules in the reactor have the same artifactId. |
| * <p> |
| * {@code -rf :artifactId} will pick up the first module which matches, but when multiple modules in the reactor |
| * have the same artifactId, effective failed module might be later in build reactor. |
| * This means that developer will either have to type groupId or wait for build execution of all modules which |
| * were fine, but they are still before one which reported errors. |
| * <p>Then the returned value is {@code groupId:artifactId} when there is a name clash and |
| * {@code :artifactId} if there is no conflict. |
| * This method is made package-private for testing purposes. |
| * |
| * @param mavenProjects Maven projects which are part of build execution. |
| * @param firstFailedProject The first project which has failed. |
| * @return Value for -rf flag to resume build exactly from place where it failed ({@code :artifactId} in general |
| * and {@code groupId:artifactId} when there is a name clash). |
| */ |
| String getResumeFromSelector(List<MavenProject> mavenProjects, MavenProject firstFailedProject) { |
| boolean hasOverlappingArtifactId = mavenProjects.stream() |
| .filter(project -> firstFailedProject.getArtifactId().equals(project.getArtifactId())) |
| .count() |
| > 1; |
| |
| if (hasOverlappingArtifactId) { |
| return firstFailedProject.getGroupId() + ":" + firstFailedProject.getArtifactId(); |
| } |
| |
| return ":" + firstFailedProject.getArtifactId(); |
| } |
| |
| private void logSummary( |
| ExceptionSummary summary, Map<String, String> references, String indent, boolean showErrors) { |
| String referenceKey = ""; |
| |
| if (summary.getReference() != null && !summary.getReference().isEmpty()) { |
| referenceKey = |
| references.computeIfAbsent(summary.getReference(), k -> "[Help " + (references.size() + 1) + "]"); |
| } |
| |
| String msg = summary.getMessage(); |
| |
| if (referenceKey != null && !referenceKey.isEmpty()) { |
| if (msg.indexOf('\n') < 0) { |
| msg += " -> " + MessageUtils.builder().strong(referenceKey); |
| } else { |
| msg += "\n-> " + MessageUtils.builder().strong(referenceKey); |
| } |
| } |
| |
| String[] lines = NEXT_LINE.split(msg); |
| String currentColor = ""; |
| |
| for (int i = 0; i < lines.length; i++) { |
| // add eventual current color inherited from previous line |
| String line = currentColor + lines[i]; |
| |
| // look for last ANSI escape sequence to check if nextColor |
| Matcher matcher = LAST_ANSI_SEQUENCE.matcher(line); |
| String nextColor = ""; |
| if (matcher.find()) { |
| nextColor = matcher.group(1); |
| if (ANSI_RESET.equals(nextColor)) { |
| // last ANSI escape code is reset: no next color |
| nextColor = ""; |
| } |
| } |
| |
| // effective line, with indent and reset if end is colored |
| line = indent + line + ("".equals(nextColor) ? "" : ANSI_RESET); |
| |
| if ((i == lines.length - 1) && (showErrors || (summary.getException() instanceof InternalErrorException))) { |
| slf4jLogger.error(line, summary.getException()); |
| } else { |
| slf4jLogger.error(line); |
| } |
| |
| currentColor = nextColor; |
| } |
| |
| indent += " "; |
| |
| for (ExceptionSummary child : summary.getChildren()) { |
| logSummary(child, references, indent, showErrors); |
| } |
| } |
| |
| private static final Pattern LAST_ANSI_SEQUENCE = Pattern.compile("(\u001B\\[[;\\d]*[ -/]*[@-~])[^\u001B]*$"); |
| |
| private static final String ANSI_RESET = "\u001B\u005Bm"; |
| |
| private void configure(CliRequest cliRequest) throws Exception { |
| // |
| // This is not ideal but there are events specifically for configuration from the CLI which I don't |
| // believe are really valid but there are ITs which assert the right events are published so this |
| // needs to be supported so the EventSpyDispatcher needs to be put in the CliRequest so that |
| // it can be accessed by configuration processors. |
| // |
| cliRequest.request.setEventSpyDispatcher(eventSpyDispatcher); |
| |
| // |
| // We expect at most 2 implementations to be available. The SettingsXmlConfigurationProcessor implementation |
| // is always available in the core and likely always will be, but we may have another ConfigurationProcessor |
| // present supplied by the user. The rule is that we only allow the execution of one ConfigurationProcessor. |
| // If there is more than one then we execute the one supplied by the user, otherwise we execute the |
| // default SettingsXmlConfigurationProcessor. |
| // |
| int userSuppliedConfigurationProcessorCount = configurationProcessors.size() - 1; |
| |
| if (userSuppliedConfigurationProcessorCount == 0) { |
| // |
| // Our settings.xml source is historically how we have configured Maven from the CLI so we are going to |
| // have to honour its existence forever. So let's run it. |
| // |
| configurationProcessors.get(SettingsXmlConfigurationProcessor.HINT).process(cliRequest); |
| } else if (userSuppliedConfigurationProcessorCount == 1) { |
| // |
| // Run the user supplied ConfigurationProcessor |
| // |
| for (Entry<String, ConfigurationProcessor> entry : configurationProcessors.entrySet()) { |
| String hint = entry.getKey(); |
| if (!hint.equals(SettingsXmlConfigurationProcessor.HINT)) { |
| ConfigurationProcessor configurationProcessor = entry.getValue(); |
| configurationProcessor.process(cliRequest); |
| } |
| } |
| } else if (userSuppliedConfigurationProcessorCount > 1) { |
| // |
| // There are too many ConfigurationProcessors so we don't know which one to run so report the error. |
| // |
| StringBuilder sb = new StringBuilder(String.format( |
| "%nThere can only be one user supplied ConfigurationProcessor, there are %s:%n%n", |
| userSuppliedConfigurationProcessorCount)); |
| for (Entry<String, ConfigurationProcessor> entry : configurationProcessors.entrySet()) { |
| String hint = entry.getKey(); |
| if (!hint.equals(SettingsXmlConfigurationProcessor.HINT)) { |
| ConfigurationProcessor configurationProcessor = entry.getValue(); |
| sb.append(String.format( |
| "%s%n", configurationProcessor.getClass().getName())); |
| } |
| } |
| throw new Exception(sb.toString()); |
| } |
| } |
| |
| void toolchains(CliRequest cliRequest) throws Exception { |
| File userToolchainsFile; |
| |
| if (cliRequest.commandLine.hasOption(CLIManager.ALTERNATE_USER_TOOLCHAINS)) { |
| userToolchainsFile = new File(cliRequest.commandLine.getOptionValue(CLIManager.ALTERNATE_USER_TOOLCHAINS)); |
| userToolchainsFile = resolveFile(userToolchainsFile, cliRequest.workingDirectory); |
| |
| if (!userToolchainsFile.isFile()) { |
| throw new FileNotFoundException( |
| "The specified user toolchains file does not exist: " + userToolchainsFile); |
| } |
| } else { |
| userToolchainsFile = DEFAULT_USER_TOOLCHAINS_FILE; |
| } |
| |
| File globalToolchainsFile; |
| |
| if (cliRequest.commandLine.hasOption(CLIManager.ALTERNATE_GLOBAL_TOOLCHAINS)) { |
| globalToolchainsFile = |
| new File(cliRequest.commandLine.getOptionValue(CLIManager.ALTERNATE_GLOBAL_TOOLCHAINS)); |
| globalToolchainsFile = resolveFile(globalToolchainsFile, cliRequest.workingDirectory); |
| |
| if (!globalToolchainsFile.isFile()) { |
| throw new FileNotFoundException( |
| "The specified global toolchains file does not exist: " + globalToolchainsFile); |
| } |
| } else { |
| globalToolchainsFile = DEFAULT_GLOBAL_TOOLCHAINS_FILE; |
| } |
| |
| cliRequest.request.setGlobalToolchainsFile(globalToolchainsFile); |
| cliRequest.request.setUserToolchainsFile(userToolchainsFile); |
| |
| DefaultToolchainsBuildingRequest toolchainsRequest = new DefaultToolchainsBuildingRequest(); |
| if (globalToolchainsFile.isFile()) { |
| toolchainsRequest.setGlobalToolchainsSource(new FileSource(globalToolchainsFile)); |
| } |
| if (userToolchainsFile.isFile()) { |
| toolchainsRequest.setUserToolchainsSource(new FileSource(userToolchainsFile)); |
| } |
| |
| eventSpyDispatcher.onEvent(toolchainsRequest); |
| |
| slf4jLogger.debug( |
| "Reading global toolchains from '{}'", |
| getLocation(toolchainsRequest.getGlobalToolchainsSource(), globalToolchainsFile)); |
| slf4jLogger.debug( |
| "Reading user toolchains from '{}'", |
| getLocation(toolchainsRequest.getUserToolchainsSource(), userToolchainsFile)); |
| |
| ToolchainsBuildingResult toolchainsResult = toolchainsBuilder.build(toolchainsRequest); |
| |
| eventSpyDispatcher.onEvent(toolchainsResult); |
| |
| executionRequestPopulator.populateFromToolchains(cliRequest.request, toolchainsResult.getEffectiveToolchains()); |
| |
| if (!toolchainsResult.getProblems().isEmpty() && slf4jLogger.isWarnEnabled()) { |
| slf4jLogger.warn(""); |
| slf4jLogger.warn("Some problems were encountered while building the effective toolchains"); |
| |
| for (Problem problem : toolchainsResult.getProblems()) { |
| slf4jLogger.warn("{} @ {}", problem.getMessage(), problem.getLocation()); |
| } |
| |
| slf4jLogger.warn(""); |
| } |
| } |
| |
| private Object getLocation(Source source, File defaultLocation) { |
| if (source != null) { |
| return source.getLocation(); |
| } |
| return defaultLocation; |
| } |
| |
| protected MavenExecutionRequest populateRequest(CliRequest cliRequest) { |
| return populateRequest(cliRequest, cliRequest.request); |
| } |
| |
| private MavenExecutionRequest populateRequest(CliRequest cliRequest, MavenExecutionRequest request) { |
| slf4jLoggerFactory = LoggerFactory.getILoggerFactory(); |
| CommandLine commandLine = cliRequest.commandLine; |
| String workingDirectory = cliRequest.workingDirectory; |
| boolean quiet = cliRequest.quiet; |
| boolean verbose = cliRequest.verbose; |
| request.setShowErrors(cliRequest.showErrors); // default: false |
| File baseDirectory = new File(workingDirectory, "").getAbsoluteFile(); |
| |
| disableInteractiveModeIfNeeded(cliRequest, request); |
| enableOnPresentOption(commandLine, CLIManager.SUPPRESS_SNAPSHOT_UPDATES, request::setNoSnapshotUpdates); |
| request.setGoals(commandLine.getArgList()); |
| request.setReactorFailureBehavior(determineReactorFailureBehaviour(commandLine)); |
| disableOnPresentOption(commandLine, CLIManager.NON_RECURSIVE, request::setRecursive); |
| enableOnPresentOption(commandLine, CLIManager.OFFLINE, request::setOffline); |
| enableOnPresentOption(commandLine, CLIManager.UPDATE_SNAPSHOTS, request::setUpdateSnapshots); |
| request.setGlobalChecksumPolicy(determineGlobalCheckPolicy(commandLine)); |
| request.setBaseDirectory(baseDirectory); |
| request.setSystemProperties(cliRequest.systemProperties); |
| request.setUserProperties(cliRequest.userProperties); |
| request.setMultiModuleProjectDirectory(cliRequest.multiModuleProjectDirectory); |
| request.setRootDirectory(cliRequest.rootDirectory); |
| request.setTopDirectory(cliRequest.topDirectory); |
| request.setPom(determinePom(commandLine, workingDirectory, baseDirectory)); |
| request.setTransferListener(determineTransferListener(quiet, verbose, commandLine, request)); |
| request.setExecutionListener(determineExecutionListener()); |
| |
| if ((request.getPom() != null) && (request.getPom().getParentFile() != null)) { |
| request.setBaseDirectory(request.getPom().getParentFile()); |
| } |
| |
| request.setResumeFrom(commandLine.getOptionValue(CLIManager.RESUME_FROM)); |
| enableOnPresentOption(commandLine, CLIManager.RESUME, request::setResume); |
| request.setMakeBehavior(determineMakeBehavior(commandLine)); |
| boolean cacheNotFound = !commandLine.hasOption(CLIManager.CACHE_ARTIFACT_NOT_FOUND) |
| || Boolean.parseBoolean(commandLine.getOptionValue(CLIManager.CACHE_ARTIFACT_NOT_FOUND)); |
| request.setCacheNotFound(cacheNotFound); |
| request.setCacheTransferError(false); |
| boolean strictArtifactDescriptorPolicy = commandLine.hasOption(CLIManager.STRICT_ARTIFACT_DESCRIPTOR_POLICY) |
| && Boolean.parseBoolean(commandLine.getOptionValue(CLIManager.STRICT_ARTIFACT_DESCRIPTOR_POLICY)); |
| if (strictArtifactDescriptorPolicy) { |
| request.setIgnoreMissingArtifactDescriptor(false); |
| request.setIgnoreInvalidArtifactDescriptor(false); |
| } else { |
| request.setIgnoreMissingArtifactDescriptor(true); |
| request.setIgnoreInvalidArtifactDescriptor(true); |
| } |
| enableOnPresentOption( |
| commandLine, CLIManager.IGNORE_TRANSITIVE_REPOSITORIES, request::setIgnoreTransitiveRepositories); |
| |
| performProjectActivation(commandLine, request.getProjectActivation()); |
| performProfileActivation(commandLine, request.getProfileActivation()); |
| |
| final String localRepositoryPath = determineLocalRepositoryPath(request); |
| if (localRepositoryPath != null) { |
| request.setLocalRepositoryPath(localRepositoryPath); |
| } |
| |
| // |
| // Builder, concurrency and parallelism |
| // |
| // We preserve the existing methods for builder selection which is to look for various inputs in the threading |
| // configuration. We don't have an easy way to allow a pluggable builder to provide its own configuration |
| // parameters but this is sufficient for now. Ultimately we want components like Builders to provide a way to |
| // extend the command line to accept its own configuration parameters. |
| // |
| final String threadConfiguration = commandLine.getOptionValue(CLIManager.THREADS); |
| |
| if (threadConfiguration != null) { |
| int degreeOfConcurrency = calculateDegreeOfConcurrency(threadConfiguration); |
| if (degreeOfConcurrency > 1) { |
| request.setBuilderId("multithreaded"); |
| request.setDegreeOfConcurrency(degreeOfConcurrency); |
| } |
| } |
| |
| // |
| // Allow the builder to be overridden by the user if requested. The builders are now pluggable. |
| // |
| request.setBuilderId(commandLine.getOptionValue(CLIManager.BUILDER, request.getBuilderId())); |
| |
| return request; |
| } |
| |
| private void disableInteractiveModeIfNeeded(final CliRequest cliRequest, final MavenExecutionRequest request) { |
| CommandLine commandLine = cliRequest.getCommandLine(); |
| if (commandLine.hasOption(FORCE_INTERACTIVE)) { |
| return; |
| } |
| |
| if (commandLine.hasOption(BATCH_MODE) || commandLine.hasOption(NON_INTERACTIVE)) { |
| request.setInteractiveMode(false); |
| } else { |
| boolean runningOnCI = isRunningOnCI(cliRequest.getSystemProperties()); |
| if (runningOnCI) { |
| slf4jLogger.info( |
| "Making this build non-interactive, because the environment variable CI equals \"true\"." |
| + " Disable this detection by removing that variable or adding --force-interactive."); |
| request.setInteractiveMode(false); |
| } |
| } |
| } |
| |
| private static boolean isRunningOnCI(Properties systemProperties) { |
| String ciEnv = systemProperties.getProperty("env.CI"); |
| return ciEnv != null && !"false".equals(ciEnv); |
| } |
| |
| private String determineLocalRepositoryPath(final MavenExecutionRequest request) { |
| String userDefinedLocalRepo = request.getUserProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY); |
| if (userDefinedLocalRepo != null) { |
| return userDefinedLocalRepo; |
| } |
| |
| // TODO Investigate why this can also be a Java system property and not just a Maven user property like |
| // other properties |
| return request.getSystemProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY); |
| } |
| |
| private File determinePom(final CommandLine commandLine, final String workingDirectory, final File baseDirectory) { |
| String alternatePomFile = null; |
| if (commandLine.hasOption(CLIManager.ALTERNATE_POM_FILE)) { |
| alternatePomFile = commandLine.getOptionValue(CLIManager.ALTERNATE_POM_FILE); |
| } |
| |
| File current = baseDirectory; |
| if (alternatePomFile != null) { |
| current = resolveFile(new File(alternatePomFile), workingDirectory); |
| } |
| |
| if (modelProcessor != null) { |
| return modelProcessor.locateExistingPom(current); |
| } else { |
| return current.isFile() ? current : null; |
| } |
| } |
| |
| // Visible for testing |
| static void performProjectActivation(final CommandLine commandLine, final ProjectActivation projectActivation) { |
| if (commandLine.hasOption(CLIManager.PROJECT_LIST)) { |
| final String[] optionValues = commandLine.getOptionValues(CLIManager.PROJECT_LIST); |
| |
| if (optionValues == null || optionValues.length == 0) { |
| return; |
| } |
| |
| for (final String optionValue : optionValues) { |
| for (String token : optionValue.split(",")) { |
| String selector = token.trim(); |
| boolean active = true; |
| if (selector.charAt(0) == '-' || selector.charAt(0) == '!') { |
| active = false; |
| selector = selector.substring(1); |
| } else if (token.charAt(0) == '+') { |
| selector = selector.substring(1); |
| } |
| |
| boolean optional = selector.charAt(0) == '?'; |
| selector = selector.substring(optional ? 1 : 0); |
| |
| projectActivation.addProjectActivation(selector, active, optional); |
| } |
| } |
| } |
| } |
| |
| // Visible for testing |
| static void performProfileActivation(final CommandLine commandLine, final ProfileActivation profileActivation) { |
| if (commandLine.hasOption(CLIManager.ACTIVATE_PROFILES)) { |
| final String[] optionValues = commandLine.getOptionValues(CLIManager.ACTIVATE_PROFILES); |
| |
| if (optionValues == null || optionValues.length == 0) { |
| return; |
| } |
| |
| for (final String optionValue : optionValues) { |
| for (String token : optionValue.split(",")) { |
| String profileId = token.trim(); |
| boolean active = true; |
| if (profileId.charAt(0) == '-' || profileId.charAt(0) == '!') { |
| active = false; |
| profileId = profileId.substring(1); |
| } else if (token.charAt(0) == '+') { |
| profileId = profileId.substring(1); |
| } |
| |
| boolean optional = profileId.charAt(0) == '?'; |
| profileId = profileId.substring(optional ? 1 : 0); |
| |
| profileActivation.addProfileActivation(profileId, active, optional); |
| } |
| } |
| } |
| } |
| |
| private ExecutionListener determineExecutionListener() { |
| ExecutionListener executionListener = new ExecutionEventLogger(messageBuilderFactory); |
| if (eventSpyDispatcher != null) { |
| return eventSpyDispatcher.chainListener(executionListener); |
| } else { |
| return executionListener; |
| } |
| } |
| |
| private String determineReactorFailureBehaviour(final CommandLine commandLine) { |
| if (commandLine.hasOption(CLIManager.FAIL_FAST)) { |
| return MavenExecutionRequest.REACTOR_FAIL_FAST; |
| } else if (commandLine.hasOption(CLIManager.FAIL_AT_END)) { |
| return MavenExecutionRequest.REACTOR_FAIL_AT_END; |
| } else if (commandLine.hasOption(CLIManager.FAIL_NEVER)) { |
| return MavenExecutionRequest.REACTOR_FAIL_NEVER; |
| } else { |
| // this is the default behavior. |
| return MavenExecutionRequest.REACTOR_FAIL_FAST; |
| } |
| } |
| |
| private TransferListener determineTransferListener( |
| final boolean quiet, |
| final boolean verbose, |
| final CommandLine commandLine, |
| final MavenExecutionRequest request) { |
| boolean runningOnCI = isRunningOnCI(request.getSystemProperties()); |
| boolean quietCI = runningOnCI && !commandLine.hasOption(FORCE_INTERACTIVE); |
| |
| if (quiet || commandLine.hasOption(CLIManager.NO_TRANSFER_PROGRESS) || quietCI) { |
| return new QuietMavenTransferListener(); |
| } else if (request.isInteractiveMode() && !commandLine.hasOption(CLIManager.LOG_FILE)) { |
| // |
| // If we're logging to a file then we don't want the console transfer listener as it will spew |
| // download progress all over the place |
| // |
| return getConsoleTransferListener(verbose); |
| } else { |
| // default: batch mode which goes along with interactive |
| return getBatchTransferListener(); |
| } |
| } |
| |
| private String determineMakeBehavior(final CommandLine cl) { |
| if (cl.hasOption(CLIManager.ALSO_MAKE) && !cl.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) { |
| return MavenExecutionRequest.REACTOR_MAKE_UPSTREAM; |
| } else if (!cl.hasOption(CLIManager.ALSO_MAKE) && cl.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) { |
| return MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM; |
| } else if (cl.hasOption(CLIManager.ALSO_MAKE) && cl.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) { |
| return MavenExecutionRequest.REACTOR_MAKE_BOTH; |
| } else { |
| return null; |
| } |
| } |
| |
| private String determineGlobalCheckPolicy(final CommandLine commandLine) { |
| if (commandLine.hasOption(CLIManager.CHECKSUM_FAILURE_POLICY)) { |
| return MavenExecutionRequest.CHECKSUM_POLICY_FAIL; |
| } else if (commandLine.hasOption(CLIManager.CHECKSUM_WARNING_POLICY)) { |
| return MavenExecutionRequest.CHECKSUM_POLICY_WARN; |
| } else { |
| return null; |
| } |
| } |
| |
| private void disableOnPresentOption( |
| final CommandLine commandLine, final String option, final Consumer<Boolean> setting) { |
| if (commandLine.hasOption(option)) { |
| setting.accept(false); |
| } |
| } |
| |
| private void disableOnPresentOption( |
| final CommandLine commandLine, final char option, final Consumer<Boolean> setting) { |
| disableOnPresentOption(commandLine, String.valueOf(option), setting); |
| } |
| |
| private void enableOnPresentOption( |
| final CommandLine commandLine, final String option, final Consumer<Boolean> setting) { |
| if (commandLine.hasOption(option)) { |
| setting.accept(true); |
| } |
| } |
| |
| private void enableOnPresentOption( |
| final CommandLine commandLine, final char option, final Consumer<Boolean> setting) { |
| enableOnPresentOption(commandLine, String.valueOf(option), setting); |
| } |
| |
| private void enableOnAbsentOption( |
| final CommandLine commandLine, final char option, final Consumer<Boolean> setting) { |
| if (!commandLine.hasOption(option)) { |
| setting.accept(true); |
| } |
| } |
| |
| int calculateDegreeOfConcurrency(String threadConfiguration) { |
| try { |
| if (threadConfiguration.endsWith("C")) { |
| String str = threadConfiguration.substring(0, threadConfiguration.length() - 1); |
| float coreMultiplier = Float.parseFloat(str); |
| |
| if (coreMultiplier <= 0.0f) { |
| throw new IllegalArgumentException("Invalid threads core multiplier value: '" + threadConfiguration |
| + "'. Value must be positive."); |
| } |
| |
| int procs = Runtime.getRuntime().availableProcessors(); |
| int threads = (int) (coreMultiplier * procs); |
| return threads == 0 ? 1 : threads; |
| } else { |
| int threads = Integer.parseInt(threadConfiguration); |
| if (threads <= 0) { |
| throw new IllegalArgumentException( |
| "Invalid threads value: '" + threadConfiguration + "'. Value must be positive."); |
| } |
| return threads; |
| } |
| } catch (NumberFormatException e) { |
| throw new IllegalArgumentException("Invalid threads value: '" + threadConfiguration |
| + "'. Supported are int and float values ending with C."); |
| } |
| } |
| |
| // ---------------------------------------------------------------------- |
| // Properties handling |
| // ---------------------------------------------------------------------- |
| |
| static void populateProperties( |
| CommandLine commandLine, Properties paths, Properties systemProperties, Properties userProperties) |
| throws Exception { |
| EnvironmentUtils.addEnvVars(systemProperties); |
| |
| // ---------------------------------------------------------------------- |
| // Options that are set on the command line become system properties |
| // and therefore are set in the session properties. System properties |
| // are most dominant. |
| // ---------------------------------------------------------------------- |
| |
| final Properties userSpecifiedProperties = |
| commandLine.getOptionProperties(String.valueOf(CLIManager.SET_USER_PROPERTY)); |
| |
| SystemProperties.addSystemProperties(systemProperties); |
| |
| // ---------------------------------------------------------------------- |
| // Properties containing info about the currently running version of Maven |
| // These override any corresponding properties set on the command line |
| // ---------------------------------------------------------------------- |
| |
| Properties buildProperties = CLIReportingUtils.getBuildProperties(); |
| |
| String mavenVersion = buildProperties.getProperty(CLIReportingUtils.BUILD_VERSION_PROPERTY); |
| systemProperties.setProperty("maven.version", mavenVersion); |
| |
| String mavenBuildVersion = CLIReportingUtils.createMavenVersionString(buildProperties); |
| systemProperties.setProperty("maven.build.version", mavenBuildVersion); |
| |
| BasicInterpolator interpolator = |
| createInterpolator(paths, systemProperties, userProperties, userSpecifiedProperties); |
| for (Map.Entry<Object, Object> e : userSpecifiedProperties.entrySet()) { |
| String name = (String) e.getKey(); |
| String value = interpolator.interpolate((String) e.getValue()); |
| userProperties.setProperty(name, value); |
| // ---------------------------------------------------------------------- |
| // I'm leaving the setting of system properties here as not to break |
| // the SystemPropertyProfileActivator. This won't harm embedding. jvz. |
| // ---------------------------------------------------------------------- |
| if (System.getProperty(name) == null) { |
| System.setProperty(name, value); |
| } |
| } |
| } |
| |
| private static BasicInterpolator createInterpolator(Properties... properties) { |
| StringSearchInterpolator interpolator = new StringSearchInterpolator(); |
| interpolator.addValueSource(new AbstractValueSource(false) { |
| @Override |
| public Object getValue(String expression) { |
| for (Properties props : properties) { |
| Object val = props.getProperty(expression); |
| if (val != null) { |
| return val; |
| } |
| } |
| return null; |
| } |
| }); |
| return interpolator; |
| } |
| |
| private static String stripLeadingAndTrailingQuotes(String str) { |
| final int length = str.length(); |
| if (length > 1 |
| && str.startsWith("\"") |
| && str.endsWith("\"") |
| && str.substring(1, length - 1).indexOf('"') == -1) { |
| str = str.substring(1, length - 1); |
| } |
| |
| return str; |
| } |
| |
| private static Path getCanonicalPath(Path path) { |
| try { |
| return path.toRealPath(); |
| } catch (IOException e) { |
| return getCanonicalPath(path.getParent()).resolve(path.getFileName()); |
| } |
| } |
| |
| static class ExitException extends Exception { |
| int exitCode; |
| |
| ExitException(int exitCode) { |
| this.exitCode = exitCode; |
| } |
| } |
| |
| // |
| // Customizations available via the CLI |
| // |
| |
| protected TransferListener getConsoleTransferListener(boolean printResourceNames) { |
| return new SimplexTransferListener( |
| new ConsoleMavenTransferListener(messageBuilderFactory, System.out, printResourceNames)); |
| } |
| |
| protected TransferListener getBatchTransferListener() { |
| return new Slf4jMavenTransferListener(); |
| } |
| |
| protected void customizeContainer(PlexusContainer container) {} |
| |
| protected ModelProcessor createModelProcessor(PlexusContainer container) throws ComponentLookupException { |
| return container.lookup(ModelProcessor.class); |
| } |
| } |