-rf " + resumeFromSelector);
+ }
+ }
+
+ if (MavenExecutionRequest.REACTOR_FAIL_NEVER.equals(cliRequest.request.getReactorFailureBehavior())) {
+ slf4jLogger.info("Build failures were ignored.");
+
+ return 0;
+ } else {
+ return 1;
+ }
+ } else {
+ Path directory = Paths.get(request.getBaseDirectory()).resolve("target");
+ new DefaultBuildResumptionDataRepository().removeResumptionData(directory);
+ 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(buffer().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.
+ *
+ * {@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.
+ *
+ * Then the returned value is {@code groupId:artifactId} when there is a name clash and
+ * {@code :artifactId} if there is no conflict.
+ *
+ * @param mavenProjects Maven projects which are part of build execution.
+ * @param failedProject 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).
+ */
+ private String getResumeFromSelector(List mavenProjects, MavenProject failedProject) {
+ for (MavenProject buildProject : mavenProjects) {
+ if (failedProject.getArtifactId().equals(buildProject.getArtifactId())
+ && !failedProject.equals(buildProject)) {
+ return failedProject.getGroupId() + ":" + failedProject.getArtifactId();
+ }
+ }
+ return ":" + failedProject.getArtifactId();
+ }
+
+ private void logSummary(
+ ExceptionSummary summary, Map references, String indent, boolean showErrors) {
+ String referenceKey = "";
+
+ if (StringUtils.isNotEmpty(summary.getReference())) {
+ referenceKey = references.get(summary.getReference());
+ if (referenceKey == null) {
+ referenceKey = "[Help " + (references.size() + 1) + "]";
+ references.put(summary.getReference(), referenceKey);
+ }
+ }
+
+ String msg = summary.getMessage();
+
+ if (StringUtils.isNotEmpty(referenceKey)) {
+ if (msg.indexOf('\n') < 0) {
+ msg += " -> " + buffer().strong(referenceKey);
+ } else {
+ msg += "\n-> " + buffer().strong(referenceKey);
+ }
+ }
+
+ String[] lines = msg.split("(\r\n)|(\r)|(\n)");
+ 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 static void configure(
+ CliRequest cliRequest,
+ EventSpyDispatcher eventSpyDispatcher,
+ Map configurationProcessors)
+ 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
+ // 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 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 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()));
+ }
+ }
+ sb.append("\n");
+ 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;
+ }
+
+ private void populateRequest(CliRequest cliRequest) {
+ populateRequest(
+ cliRequest,
+ cliRequest.request,
+ slf4jLogger,
+ eventSpyDispatcher,
+ modelProcessor,
+ createTransferListener(cliRequest),
+ buildEventListener,
+ executionListener);
+ }
+
+ private static void populateRequest(
+ CliRequest cliRequest,
+ MavenExecutionRequest request,
+ Logger slf4jLogger,
+ EventSpyDispatcher eventSpyDispatcher,
+ ModelProcessor modelProcessor,
+ TransferListener transferListener,
+ BuildEventListener buildEventListener,
+ LoggingExecutionListener executionListener) {
+ CommandLine commandLine = cliRequest.commandLine;
+ String workingDirectory = cliRequest.workingDirectory;
+ boolean showErrors = cliRequest.showErrors;
+
+ String[] deprecatedOptions = {"up", "npu", "cpu", "npr"};
+ for (String deprecatedOption : deprecatedOptions) {
+ if (commandLine.hasOption(deprecatedOption)) {
+ slf4jLogger.warn(
+ "Command line option -{} is deprecated and will be removed in future Maven versions.",
+ deprecatedOption);
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Now that we have everything that we need we will fire up plexus and
+ // bring the maven component to life for use.
+ // ----------------------------------------------------------------------
+
+ if (commandLine.hasOption(CLIManager.BATCH_MODE)) {
+ request.setInteractiveMode(false);
+ }
+
+ boolean noSnapshotUpdates = false;
+ if (commandLine.hasOption(CLIManager.SUPRESS_SNAPSHOT_UPDATES)) {
+ noSnapshotUpdates = true;
+ }
+
+ // ----------------------------------------------------------------------
+ //
+ // ----------------------------------------------------------------------
+
+ List goals = commandLine.getArgList();
+
+ boolean recursive = true;
+
+ // this is the default behavior.
+ String reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_FAST;
+
+ if (commandLine.hasOption(CLIManager.NON_RECURSIVE)) {
+ recursive = false;
+ }
+
+ if (commandLine.hasOption(CLIManager.FAIL_FAST)) {
+ reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_FAST;
+ } else if (commandLine.hasOption(CLIManager.FAIL_AT_END)) {
+ reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_AT_END;
+ } else if (commandLine.hasOption(CLIManager.FAIL_NEVER)) {
+ reactorFailureBehaviour = MavenExecutionRequest.REACTOR_FAIL_NEVER;
+ }
+
+ if (commandLine.hasOption(CLIManager.OFFLINE)) {
+ request.setOffline(true);
+ }
+
+ boolean updateSnapshots = false;
+
+ if (commandLine.hasOption(CLIManager.UPDATE_SNAPSHOTS)) {
+ updateSnapshots = true;
+ }
+
+ String globalChecksumPolicy = null;
+
+ if (commandLine.hasOption(CLIManager.CHECKSUM_FAILURE_POLICY)) {
+ globalChecksumPolicy = MavenExecutionRequest.CHECKSUM_POLICY_FAIL;
+ } else if (commandLine.hasOption(CLIManager.CHECKSUM_WARNING_POLICY)) {
+ globalChecksumPolicy = MavenExecutionRequest.CHECKSUM_POLICY_WARN;
+ }
+
+ File baseDirectory = new File(workingDirectory, "").getAbsoluteFile();
+
+ // ----------------------------------------------------------------------
+ // Profile Activation
+ // ----------------------------------------------------------------------
+
+ List activeProfiles = new ArrayList<>();
+
+ List inactiveProfiles = new ArrayList<>();
+
+ if (commandLine.hasOption(CLIManager.ACTIVATE_PROFILES)) {
+ String[] profileOptionValues = commandLine.getOptionValues(CLIManager.ACTIVATE_PROFILES);
+ if (profileOptionValues != null) {
+ for (String profileOptionValue : profileOptionValues) {
+ StringTokenizer profileTokens = new StringTokenizer(profileOptionValue, ",");
+
+ while (profileTokens.hasMoreTokens()) {
+ String profileAction = profileTokens.nextToken().trim();
+
+ if (profileAction.startsWith("-") || profileAction.startsWith("!")) {
+ inactiveProfiles.add(profileAction.substring(1));
+ } else if (profileAction.startsWith("+")) {
+ activeProfiles.add(profileAction.substring(1));
+ } else {
+ activeProfiles.add(profileAction);
+ }
+ }
+ }
+ }
+ }
+
+ ExecutionEventLogger executionEventLogger = new ExecutionEventLogger();
+ executionListener.init(eventSpyDispatcher.chainListener(executionEventLogger), buildEventListener);
+
+ String alternatePomFile = null;
+ if (commandLine.hasOption(CLIManager.ALTERNATE_POM_FILE)) {
+ alternatePomFile = commandLine.getOptionValue(CLIManager.ALTERNATE_POM_FILE);
+ }
+
+ request.setBaseDirectory(baseDirectory)
+ .setGoals(goals)
+ .setSystemProperties(cliRequest.systemProperties)
+ .setUserProperties(cliRequest.userProperties)
+ .setReactorFailureBehavior(reactorFailureBehaviour) // default: fail fast
+ .setRecursive(recursive) // default: true
+ .setShowErrors(showErrors) // default: false
+ .addActiveProfiles(activeProfiles) // optional
+ .addInactiveProfiles(inactiveProfiles) // optional
+ .setExecutionListener(executionListener)
+ .setTransferListener(transferListener) // default: batch mode which goes along with interactive
+ .setUpdateSnapshots(updateSnapshots) // default: false
+ .setNoSnapshotUpdates(noSnapshotUpdates) // default: false
+ .setGlobalChecksumPolicy(globalChecksumPolicy) // default: warn
+ .setMultiModuleProjectDirectory(cliRequest.getMultiModuleProjectDirectory());
+
+ if (alternatePomFile != null) {
+ File pom = resolveFile(new File(alternatePomFile), workingDirectory);
+ if (pom.isDirectory()) {
+ pom = new File(pom, "pom.xml");
+ }
+
+ request.setPom(pom);
+ } else if (modelProcessor != null) {
+ File pom = modelProcessor.locatePom(baseDirectory);
+
+ if (pom.isFile()) {
+ request.setPom(pom);
+ }
+ }
+
+ if ((request.getPom() != null) && (request.getPom().getParentFile() != null)) {
+ request.setBaseDirectory(request.getPom().getParentFile());
+ }
+
+ if (commandLine.hasOption(RESUME)) {
+ new DefaultBuildResumptionDataRepository()
+ .applyResumptionData(
+ request, Paths.get(request.getBaseDirectory()).resolve("target"));
+ }
+
+ if (commandLine.hasOption(CLIManager.RESUME_FROM)) {
+ request.setResumeFrom(commandLine.getOptionValue(CLIManager.RESUME_FROM));
+ }
+
+ if (commandLine.hasOption(CLIManager.PROJECT_LIST)) {
+ String[] projectOptionValues = commandLine.getOptionValues(CLIManager.PROJECT_LIST);
+
+ List inclProjects = new ArrayList<>();
+ List exclProjects = new ArrayList<>();
+
+ if (projectOptionValues != null) {
+ for (String projectOptionValue : projectOptionValues) {
+ StringTokenizer projectTokens = new StringTokenizer(projectOptionValue, ",");
+
+ while (projectTokens.hasMoreTokens()) {
+ String projectAction = projectTokens.nextToken().trim();
+
+ if (projectAction.startsWith("-") || projectAction.startsWith("!")) {
+ exclProjects.add(projectAction.substring(1));
+ } else if (projectAction.startsWith("+")) {
+ inclProjects.add(projectAction.substring(1));
+ } else {
+ inclProjects.add(projectAction);
+ }
+ }
+ }
+ }
+
+ request.setSelectedProjects(inclProjects);
+ request.setExcludedProjects(exclProjects);
+ }
+
+ if (commandLine.hasOption(CLIManager.ALSO_MAKE) && !commandLine.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) {
+ request.setMakeBehavior(MavenExecutionRequest.REACTOR_MAKE_UPSTREAM);
+ } else if (!commandLine.hasOption(CLIManager.ALSO_MAKE)
+ && commandLine.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) {
+ request.setMakeBehavior(MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM);
+ } else if (commandLine.hasOption(CLIManager.ALSO_MAKE)
+ && commandLine.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) {
+ request.setMakeBehavior(MavenExecutionRequest.REACTOR_MAKE_BOTH);
+ }
+
+ String localRepoProperty = request.getUserProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY);
+
+ if (localRepoProperty == null) {
+ localRepoProperty = request.getSystemProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY);
+ }
+
+ if (localRepoProperty != null) {
+ request.setLocalRepositoryPath(localRepoProperty);
+ }
+
+ request.setCacheNotFound(true);
+ request.setCacheTransferError(false);
+
+ //
+ // 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.hasOption(CLIManager.THREADS) ? commandLine.getOptionValue(CLIManager.THREADS) : null;
+
+ if (threadConfiguration != null) {
+ //
+ // Default to the standard multithreaded builder
+ //
+ request.setBuilderId("multithreaded");
+
+ if (threadConfiguration.contains("C")) {
+ request.setDegreeOfConcurrency(calculateDegreeOfConcurrencyWithCoreMultiplier(threadConfiguration));
+ } else {
+ request.setDegreeOfConcurrency(Integer.parseInt(threadConfiguration));
+ }
+ }
+
+ //
+ // Allow the builder to be overridden by the user if requested. The builders are now pluggable.
+ //
+ if (commandLine.hasOption(CLIManager.BUILDER)) {
+ request.setBuilderId(commandLine.getOptionValue(CLIManager.BUILDER));
+ }
+ }
+
+ static int calculateDegreeOfConcurrencyWithCoreMultiplier(String threadConfiguration) {
+ int procs = Runtime.getRuntime().availableProcessors();
+ return (int) (Float.parseFloat(threadConfiguration.replace("C", "")) * procs);
+ }
+
+ static File resolveFile(File file, String workingDirectory) {
+ if (file == null) {
+ return null;
+ } else if (file.isAbsolute()) {
+ return file;
+ } else if (file.getPath().startsWith(File.separator)) {
+ // drive-relative Windows path
+ return file.getAbsoluteFile();
+ } else {
+ return new File(workingDirectory, file.getPath()).getAbsoluteFile();
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // System properties handling
+ // ----------------------------------------------------------------------
+
+ static void populateProperties(CommandLine commandLine, Properties systemProperties, Properties userProperties) {
+ 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.
+ // ----------------------------------------------------------------------
+
+ if (commandLine.hasOption(CLIManager.SET_SYSTEM_PROPERTY)) {
+ String[] defStrs = commandLine.getOptionValues(CLIManager.SET_SYSTEM_PROPERTY);
+
+ if (defStrs != null) {
+ for (String defStr : defStrs) {
+ setCliProperty(defStr, userProperties);
+ }
+ }
+ }
+
+ 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);
+ }
+
+ public static void addEnvVars(Properties props) {
+ if (props != null) {
+ boolean caseSensitive = Os.current() == Os.WINDOWS;
+ for (Map.Entry entry : System.getenv().entrySet()) {
+ String key = "env."
+ + (caseSensitive ? entry.getKey() : entry.getKey().toUpperCase(Locale.ENGLISH));
+ props.setProperty(key, entry.getValue());
+ }
+ }
+ }
+
+ private static void setCliProperty(String property, Properties properties) {
+ String name;
+
+ String value;
+
+ int i = property.indexOf('=');
+
+ if (i <= 0) {
+ name = property.trim();
+
+ value = "true";
+ } else {
+ name = property.substring(0, i).trim();
+
+ value = property.substring(i + 1);
+ }
+
+ properties.setProperty(name, value);
+
+ // ----------------------------------------------------------------------
+ // I'm leaving the setting of system properties here as not to break
+ // the SystemPropertyProfileActivator. This won't harm embedding. jvz.
+ // ----------------------------------------------------------------------
+
+ System.setProperty(name, value);
+ }
+
+ static class ExitException extends Exception {
+ static final long serialVersionUID = 1L;
+ int exitCode;
+
+ ExitException(int exitCode) {
+ this.exitCode = exitCode;
+ }
+ }
+
+ //
+ // Customizations available via the CLI
+ //
+
+ protected TransferListener createTransferListener(CliRequest cliRequest) {
+ if (cliRequest.quiet || cliRequest.commandLine.hasOption(CLIManager.NO_TRANSFER_PROGRESS)) {
+ return new QuietMavenTransferListener();
+ } else if (cliRequest.request.isInteractiveMode() && !cliRequest.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();
+ } else {
+ return getBatchTransferListener();
+ }
+ }
+
+ protected TransferListener getConsoleTransferListener() {
+ return new DaemonMavenTransferListener(buildEventListener, new Slf4jMavenTransferListener());
+ }
+
+ protected TransferListener getBatchTransferListener() {
+ return new Slf4jMavenTransferListener();
+ }
+
+ protected ModelProcessor createModelProcessor(PlexusContainer container) throws ComponentLookupException {
+ return container.lookup(ModelProcessor.class);
+ }
+}
diff --git a/daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCache.java b/daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCache.java
new file mode 100644
index 00000000..8d7d20ea
--- /dev/null
+++ b/daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCache.java
@@ -0,0 +1,48 @@
+/*
+ * 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.project;
+
+import java.util.Objects;
+
+import org.apache.maven.model.building.ModelCache;
+
+public class SnapshotModelCache implements ModelCache {
+
+ private final ModelCache globalCache;
+ private final ModelCache reactorCache;
+
+ public SnapshotModelCache(ModelCache globalCache, ModelCache reactorCache) {
+ this.globalCache = Objects.requireNonNull(globalCache);
+ this.reactorCache = Objects.requireNonNull(reactorCache);
+ }
+
+ @Override
+ public void put(String groupId, String artifactId, String version, String tag, Object data) {
+ getDelegate(version).put(groupId, artifactId, version, tag, data);
+ }
+
+ @Override
+ public Object get(String groupId, String artifactId, String version, String tag) {
+ return getDelegate(version).get(groupId, artifactId, version, tag);
+ }
+
+ private ModelCache getDelegate(String version) {
+ return version.contains("SNAPSHOT") || version.contains("${") ? reactorCache : globalCache;
+ }
+}
diff --git a/daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java b/daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java
new file mode 100644
index 00000000..08946257
--- /dev/null
+++ b/daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java
@@ -0,0 +1,53 @@
+/*
+ * 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.project;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.apache.maven.model.building.ModelCache;
+import org.apache.maven.repository.internal.DefaultModelCacheFactory;
+import org.apache.maven.repository.internal.ModelCacheFactory;
+import org.eclipse.aether.DefaultRepositoryCache;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.sisu.Priority;
+
+@Singleton
+@Named
+@Priority(10)
+public class SnapshotModelCacheFactory implements ModelCacheFactory {
+
+ private final ModelCacheFactory factory;
+ private final ModelCache globalCache;
+
+ @Inject
+ public SnapshotModelCacheFactory(DefaultModelCacheFactory factory) {
+ this.factory = factory;
+ DefaultRepositorySystemSession session = new DefaultRepositorySystemSession();
+ session.setCache(new DefaultRepositoryCache());
+ this.globalCache = factory.createCache(session);
+ }
+
+ @Override
+ public ModelCache createCache(RepositorySystemSession session) {
+ return new SnapshotModelCache(globalCache, factory.createCache(session));
+ }
+}
diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java
similarity index 100%
rename from daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java
rename to daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java
diff --git a/daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
new file mode 100644
index 00000000..67796309
--- /dev/null
+++ b/daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
@@ -0,0 +1,125 @@
+/*
+ * 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.mvndaemon.mvnd.cache.invalidating;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.DefaultPluginRealmCache;
+import org.apache.maven.plugin.PluginContainerException;
+import org.apache.maven.plugin.PluginResolutionException;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
+import org.eclipse.sisu.Priority;
+import org.mvndaemon.mvnd.cache.Cache;
+import org.mvndaemon.mvnd.cache.CacheFactory;
+
+@Singleton
+@Named
+@Priority(10)
+public class InvalidatingPluginRealmCache extends DefaultPluginRealmCache {
+
+ @FunctionalInterface
+ public interface PluginRealmSupplier {
+ CacheRecord load() throws PluginResolutionException, PluginContainerException;
+ }
+
+ protected static class Record implements org.mvndaemon.mvnd.cache.CacheRecord {
+
+ final CacheRecord record;
+
+ public Record(CacheRecord record) {
+ this.record = record;
+ }
+
+ @Override
+ public Stream getDependencyPaths() {
+ return record.getArtifacts().stream()
+ .map(artifact -> artifact.getFile().toPath());
+ }
+
+ @Override
+ public void invalidate() {
+ ClassRealm realm = record.getRealm();
+ try {
+ realm.getWorld().disposeRealm(realm.getId());
+ } catch (NoSuchRealmException e) {
+ // ignore
+ }
+ }
+ }
+
+ final Cache cache;
+
+ @Inject
+ public InvalidatingPluginRealmCache(CacheFactory cacheFactory) {
+ cache = cacheFactory.newCache();
+ }
+
+ @Override
+ public CacheRecord get(Key key) {
+ Record r = cache.get(key);
+ return r != null ? r.record : null;
+ }
+
+ public CacheRecord get(Key key, PluginRealmSupplier supplier)
+ throws PluginResolutionException, PluginContainerException {
+ try {
+ Record r = cache.computeIfAbsent(key, k -> {
+ try {
+ return new Record(supplier.load());
+ } catch (PluginResolutionException | PluginContainerException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ return r.record;
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof PluginResolutionException) {
+ throw (PluginResolutionException) e.getCause();
+ }
+ if (e.getCause() instanceof PluginContainerException) {
+ throw (PluginContainerException) e.getCause();
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public CacheRecord put(Key key, ClassRealm pluginRealm, List pluginArtifacts) {
+ CacheRecord record = super.put(key, pluginRealm, pluginArtifacts);
+ super.cache.remove(key);
+ cache.put(key, new Record(record));
+ return record;
+ }
+
+ @Override
+ public void flush() {
+ cache.clear();
+ }
+
+ @Override
+ public void register(MavenProject project, Key key, CacheRecord record) {}
+}
diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java
similarity index 100%
rename from daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java
rename to daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java
diff --git a/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionAnalyzer.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionAnalyzer.java
new file mode 100644
index 00000000..c0c0ac26
--- /dev/null
+++ b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionAnalyzer.java
@@ -0,0 +1,57 @@
+/*
+ * 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.mvndaemon.mvnd.execution;
+
+/*
+ * 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.
+ */
+
+import java.util.Optional;
+
+import org.apache.maven.execution.MavenExecutionResult;
+
+/**
+ * Instances of this class are responsible for determining whether it makes sense to "resume" a build (i.e., using
+ * the {@code --resume} flag.
+ */
+public interface BuildResumptionAnalyzer {
+ /**
+ * Construct an instance of {@link BuildResumptionData} based on the outcome of the current Maven build.
+ *
+ * @param result Outcome of the current Maven build.
+ * @return A {@link BuildResumptionData} instance or {@link Optional#empty()} if resuming the build is not
+ * possible.
+ */
+ Optional determineBuildResumptionData(final MavenExecutionResult result);
+}
diff --git a/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionData.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionData.java
new file mode 100644
index 00000000..b9ae3eda
--- /dev/null
+++ b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionData.java
@@ -0,0 +1,62 @@
+/*
+ * 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.mvndaemon.mvnd.execution;
+
+/*
+ * 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.
+ */
+import java.util.List;
+
+/**
+ * This class holds the information required to enable resuming a Maven build with {@code --resume}.
+ */
+public class BuildResumptionData {
+ /**
+ * The list of projects that remain to be built.
+ */
+ private final List remainingProjects;
+
+ public BuildResumptionData(final List remainingProjects) {
+ this.remainingProjects = remainingProjects;
+ }
+
+ /**
+ * Returns the projects that still need to be built when resuming.
+ *
+ * @return A list containing the group and artifact id of the projects.
+ */
+ public List getRemainingProjects() {
+ return this.remainingProjects;
+ }
+}
diff --git a/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionDataRepository.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionDataRepository.java
new file mode 100644
index 00000000..6dbae8b8
--- /dev/null
+++ b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionDataRepository.java
@@ -0,0 +1,74 @@
+/*
+ * 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.mvndaemon.mvnd.execution;
+
+/*
+ * 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.
+ */
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.project.MavenProject;
+
+/**
+ * Instances of this interface retrieve and store data for the --resume / -r feature. This data is used to ensure newer
+ * builds of the same project, that have the -r command-line flag, skip successfully built projects during earlier
+ * invocations of Maven.
+ */
+public interface BuildResumptionDataRepository {
+ /**
+ * Persists any data needed to resume the build at a later point in time, using a new Maven invocation. This method
+ * may also decide it is not needed or meaningful to persist such data, and return false
to indicate
+ * so.
+ *
+ * @param rootProject The root project that is being built.
+ * @param buildResumptionData Information needed to resume the build.
+ * @throws BuildResumptionPersistenceException When an error occurs while persisting data.
+ */
+ void persistResumptionData(final MavenProject rootProject, final BuildResumptionData buildResumptionData)
+ throws BuildResumptionPersistenceException;
+
+ /**
+ * Uses previously stored resumption data to enrich an existing execution request.
+ *
+ * @param request The execution request that will be enriched.
+ * @param rootProject The root project that is being built.
+ */
+ void applyResumptionData(final MavenExecutionRequest request, final MavenProject rootProject);
+
+ /**
+ * Removes previously stored resumption data.
+ *
+ * @param rootProject The root project that is being built.
+ */
+ void removeResumptionData(final MavenProject rootProject);
+}
diff --git a/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionPersistenceException.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionPersistenceException.java
new file mode 100644
index 00000000..00f99ca3
--- /dev/null
+++ b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/BuildResumptionPersistenceException.java
@@ -0,0 +1,49 @@
+/*
+ * 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.mvndaemon.mvnd.execution;
+
+/*
+ * 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.
+ */
+
+/**
+ * This exception will be thrown when something fails while persisting build resumption data.
+ *
+ * @see BuildResumptionDataRepository#persistResumptionData
+ */
+public class BuildResumptionPersistenceException extends Exception {
+ public BuildResumptionPersistenceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/DefaultBuildResumptionAnalyzer.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/DefaultBuildResumptionAnalyzer.java
new file mode 100644
index 00000000..96da4dc0
--- /dev/null
+++ b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/DefaultBuildResumptionAnalyzer.java
@@ -0,0 +1,90 @@
+/*
+ * 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.mvndaemon.mvnd.execution;
+
+/*
+ * 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.
+ */
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.apache.maven.execution.BuildFailure;
+import org.apache.maven.execution.BuildSuccess;
+import org.apache.maven.execution.MavenExecutionResult;
+import org.apache.maven.project.MavenProject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Default implementation of {@link BuildResumptionAnalyzer}.
+ */
+@Named
+@Singleton
+public class DefaultBuildResumptionAnalyzer implements BuildResumptionAnalyzer {
+ private static final Logger LOGGER = LoggerFactory.getLogger(DefaultBuildResumptionAnalyzer.class);
+
+ @Override
+ public Optional determineBuildResumptionData(final MavenExecutionResult result) {
+ if (!result.hasExceptions()) {
+ return Optional.empty();
+ }
+
+ List sortedProjects = result.getTopologicallySortedProjects();
+
+ boolean hasNoSuccess =
+ sortedProjects.stream().noneMatch(project -> result.getBuildSummary(project) instanceof BuildSuccess);
+
+ if (hasNoSuccess) {
+ return Optional.empty();
+ }
+
+ List remainingProjects = sortedProjects.stream()
+ .filter(project -> result.getBuildSummary(project) == null
+ || result.getBuildSummary(project) instanceof BuildFailure)
+ .map(project -> project.getGroupId() + ":" + project.getArtifactId())
+ .collect(Collectors.toList());
+
+ if (remainingProjects.isEmpty()) {
+ LOGGER.info("No remaining projects found, resuming the build would not make sense.");
+ return Optional.empty();
+ }
+
+ return Optional.of(new BuildResumptionData(remainingProjects));
+ }
+}
diff --git a/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/DefaultBuildResumptionDataRepository.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/DefaultBuildResumptionDataRepository.java
new file mode 100644
index 00000000..d61c5151
--- /dev/null
+++ b/daemon-m39/src/main/java/org/mvndaemon/mvnd/execution/DefaultBuildResumptionDataRepository.java
@@ -0,0 +1,155 @@
+/*
+ * 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.mvndaemon.mvnd.execution;
+
+/*
+ * 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.
+ */
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Properties;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.project.MavenProject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This implementation of {@link BuildResumptionDataRepository} persists information in a properties file. The file is
+ * stored in the build output directory under the Maven execution root.
+ */
+@Named
+@Singleton
+public class DefaultBuildResumptionDataRepository implements BuildResumptionDataRepository {
+ private static final String RESUME_PROPERTIES_FILENAME = "resume.properties";
+ private static final String REMAINING_PROJECTS = "remainingProjects";
+ private static final String PROPERTY_DELIMITER = ", ";
+ private static final Logger LOGGER = LoggerFactory.getLogger(DefaultBuildResumptionDataRepository.class);
+
+ @Override
+ public void persistResumptionData(MavenProject rootProject, BuildResumptionData buildResumptionData)
+ throws BuildResumptionPersistenceException {
+ Path directory = Paths.get(rootProject.getBuild().getDirectory());
+ persistResumptionData(directory, buildResumptionData);
+ }
+
+ public void persistResumptionData(Path directory, BuildResumptionData buildResumptionData)
+ throws BuildResumptionPersistenceException {
+ Properties properties = convertToProperties(buildResumptionData);
+
+ Path resumeProperties = directory.resolve(RESUME_PROPERTIES_FILENAME);
+ try {
+ Files.createDirectories(resumeProperties.getParent());
+ try (Writer writer = Files.newBufferedWriter(resumeProperties)) {
+ properties.store(writer, null);
+ }
+ } catch (IOException e) {
+ String message = "Could not create " + RESUME_PROPERTIES_FILENAME + " file.";
+ throw new BuildResumptionPersistenceException(message, e);
+ }
+ }
+
+ private Properties convertToProperties(final BuildResumptionData buildResumptionData) {
+ Properties properties = new Properties();
+
+ String value = String.join(PROPERTY_DELIMITER, buildResumptionData.getRemainingProjects());
+ properties.setProperty(REMAINING_PROJECTS, value);
+
+ return properties;
+ }
+
+ @Override
+ public void applyResumptionData(MavenExecutionRequest request, MavenProject rootProject) {
+ Path directory = Paths.get(rootProject.getBuild().getDirectory());
+ applyResumptionData(request, directory);
+ }
+
+ public void applyResumptionData(MavenExecutionRequest request, Path directory) {
+ Properties properties = loadResumptionFile(directory);
+ applyResumptionProperties(request, properties);
+ }
+
+ @Override
+ public void removeResumptionData(MavenProject rootProject) {
+ Path directory = Paths.get(rootProject.getBuild().getDirectory());
+ removeResumptionData(directory);
+ }
+
+ public void removeResumptionData(Path directory) {
+ Path resumeProperties = directory.resolve(RESUME_PROPERTIES_FILENAME);
+ try {
+ Files.deleteIfExists(resumeProperties);
+ } catch (IOException e) {
+ LOGGER.warn("Could not delete {} file. ", RESUME_PROPERTIES_FILENAME, e);
+ }
+ }
+
+ private Properties loadResumptionFile(Path rootBuildDirectory) {
+ Properties properties = new Properties();
+ Path path = rootBuildDirectory.resolve(RESUME_PROPERTIES_FILENAME);
+ if (!Files.exists(path)) {
+ LOGGER.warn("The {} file does not exist. The --resume / -r feature will not work.", path);
+ return properties;
+ }
+
+ try (Reader reader = Files.newBufferedReader(path)) {
+ properties.load(reader);
+ } catch (IOException e) {
+ LOGGER.warn("Unable to read {}. The --resume / -r feature will not work.", path);
+ }
+
+ return properties;
+ }
+
+ // This method is made package-private for testing purposes
+ void applyResumptionProperties(MavenExecutionRequest request, Properties properties) {
+ if (properties.containsKey(REMAINING_PROJECTS) && StringUtils.isEmpty(request.getResumeFrom())) {
+ String propertyValue = properties.getProperty(REMAINING_PROJECTS);
+ Stream.of(propertyValue.split(PROPERTY_DELIMITER))
+ .filter(StringUtils::isNotEmpty)
+ .forEach(request.getSelectedProjects()::add);
+ LOGGER.info("Resuming from {} due to the --resume / -r feature.", propertyValue);
+ }
+ }
+}
diff --git a/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
new file mode 100644
index 00000000..19c73f9b
--- /dev/null
+++ b/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
@@ -0,0 +1,79 @@
+/*
+ * 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.mvndaemon.mvnd.plugin;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.maven.plugin.version.PluginVersionRequest;
+import org.apache.maven.plugin.version.PluginVersionResolutionException;
+import org.apache.maven.plugin.version.PluginVersionResolver;
+import org.apache.maven.plugin.version.PluginVersionResult;
+import org.apache.maven.plugin.version.internal.DefaultPluginVersionResolver;
+import org.eclipse.aether.SessionData;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.sisu.Priority;
+import org.eclipse.sisu.Typed;
+
+@Named
+@Singleton
+@Priority(10)
+@Typed(PluginVersionResolver.class)
+public class CachingPluginVersionResolver extends DefaultPluginVersionResolver {
+
+ private static final Object CACHE_KEY = new Object();
+
+ @Override
+ public PluginVersionResult resolve(PluginVersionRequest request) throws PluginVersionResolutionException {
+ Map cache =
+ getCache(request.getRepositorySession().getData());
+ String key = getKey(request);
+ PluginVersionResult result = cache.get(key);
+ if (result == null) {
+ result = super.resolve(request);
+ cache.putIfAbsent(key, result);
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map getCache(SessionData data) {
+ Map cache = (Map) data.get(CACHE_KEY);
+ while (cache == null) {
+ cache = new ConcurrentHashMap<>(256);
+ if (data.set(CACHE_KEY, null, cache)) {
+ break;
+ }
+ cache = (Map) data.get(CACHE_KEY);
+ }
+ return cache;
+ }
+
+ private static String getKey(PluginVersionRequest request) {
+ return Stream.concat(
+ Stream.of(request.getGroupId(), request.getArtifactId()),
+ request.getRepositories().stream().map(RemoteRepository::getId))
+ .collect(Collectors.joining(":"));
+ }
+}
diff --git a/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java
new file mode 100644
index 00000000..5edb9d64
--- /dev/null
+++ b/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/CliMavenPluginManager.java
@@ -0,0 +1,779 @@
+/*
+ * 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.mvndaemon.mvnd.plugin;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import org.apache.maven.RepositoryUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.classrealm.ClassRealmManager;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.monitor.logging.DefaultLog;
+import org.apache.maven.plugin.*;
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.Parameter;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder;
+import org.apache.maven.plugin.internal.PluginDependenciesResolver;
+import org.apache.maven.plugin.version.DefaultPluginVersionRequest;
+import org.apache.maven.plugin.version.PluginVersionRequest;
+import org.apache.maven.plugin.version.PluginVersionResolutionException;
+import org.apache.maven.plugin.version.PluginVersionResolver;
+import org.apache.maven.project.ExtensionDescriptor;
+import org.apache.maven.project.ExtensionDescriptorBuilder;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.rtinfo.RuntimeInformation;
+import org.apache.maven.session.scope.internal.SessionScopeModule;
+import org.codehaus.plexus.DefaultPlexusContainer;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.codehaus.plexus.component.composition.CycleDetectedInComponentGraphException;
+import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
+import org.codehaus.plexus.component.configurator.ComponentConfigurator;
+import org.codehaus.plexus.component.configurator.ConfigurationListener;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
+import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
+import org.codehaus.plexus.component.repository.ComponentDescriptor;
+import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+import org.codehaus.plexus.configuration.PlexusConfiguration;
+import org.codehaus.plexus.configuration.PlexusConfigurationException;
+import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration;
+import org.codehaus.plexus.logging.Logger;
+import org.codehaus.plexus.logging.LoggerManager;
+import org.codehaus.plexus.util.ReaderFactory;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.graph.DependencyFilter;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.util.filter.AndDependencyFilter;
+import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator;
+import org.eclipse.sisu.Priority;
+import org.eclipse.sisu.Typed;
+import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginDescriptorCache;
+import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginRealmCache;
+
+/*
+ * gnodet: This file is based on maven DefaultMavenPluginManager and changed in order
+ * to better support parallel builds. See https://github.com/apache/maven-mvnd/issues/310
+ */
+/**
+ * Provides basic services to manage Maven plugins and their mojos. This component is kept general in its design such
+ * that the plugins/mojos can be used in arbitrary contexts. In particular, the mojos can be used for ordinary build
+ * plugins as well as special purpose plugins like reports.
+ *
+ * @author Benjamin Bentmann
+ * @since 3.0
+ */
+@Singleton
+@Named
+@Priority(10)
+@Typed(MavenPluginManager.class)
+public class CliMavenPluginManager implements MavenPluginManager {
+
+ /**
+ *
+ * PluginId => ExtensionRealmCache.CacheRecord map MavenProject context value key. The map is used to ensure the
+ * same class realm is used to load build extensions and load mojos for extensions=true plugins.
+ *
+ * Note: This is part of internal implementation and may be changed or removed without notice
+ *
+ * @since 3.3.0
+ */
+ public static final String KEY_EXTENSIONS_REALMS = CliMavenPluginManager.class.getName() + "/extensionsRealms";
+
+ @Inject
+ private Logger logger;
+
+ @Inject
+ private LoggerManager loggerManager;
+
+ @Inject
+ private PlexusContainer container;
+
+ @Inject
+ private ClassRealmManager classRealmManager;
+
+ @Inject
+ private InvalidatingPluginDescriptorCache pluginDescriptorCache;
+
+ @Inject
+ private InvalidatingPluginRealmCache pluginRealmCache;
+
+ @Inject
+ private PluginDependenciesResolver pluginDependenciesResolver;
+
+ @Inject
+ private RuntimeInformation runtimeInformation;
+
+ @Inject
+ private ExtensionRealmCache extensionRealmCache;
+
+ @Inject
+ private PluginVersionResolver pluginVersionResolver;
+
+ @Inject
+ private PluginArtifactsCache pluginArtifactsCache;
+
+ private ExtensionDescriptorBuilder extensionDescriptorBuilder = new ExtensionDescriptorBuilder();
+
+ private PluginDescriptorBuilder builder = new PluginDescriptorBuilder();
+
+ public PluginDescriptor getPluginDescriptor(
+ Plugin plugin, List repositories, RepositorySystemSession session)
+ throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException {
+ PluginDescriptorCache.Key cacheKey = pluginDescriptorCache.createKey(plugin, repositories, session);
+
+ PluginDescriptor pluginDescriptor = pluginDescriptorCache.get(cacheKey, () -> {
+ org.eclipse.aether.artifact.Artifact artifact =
+ pluginDependenciesResolver.resolve(plugin, repositories, session);
+
+ Artifact pluginArtifact = RepositoryUtils.toArtifact(artifact);
+
+ PluginDescriptor descriptor = extractPluginDescriptor(pluginArtifact, plugin);
+
+ descriptor.setRequiredMavenVersion(artifact.getProperty("requiredMavenVersion", null));
+
+ return descriptor;
+ });
+
+ pluginDescriptor.setPlugin(plugin);
+
+ return pluginDescriptor;
+ }
+
+ private PluginDescriptor extractPluginDescriptor(Artifact pluginArtifact, Plugin plugin)
+ throws PluginDescriptorParsingException, InvalidPluginDescriptorException {
+ PluginDescriptor pluginDescriptor = null;
+
+ File pluginFile = pluginArtifact.getFile();
+
+ try {
+ if (pluginFile.isFile()) {
+ try (JarFile pluginJar = new JarFile(pluginFile, false)) {
+ ZipEntry pluginDescriptorEntry = pluginJar.getEntry(getPluginDescriptorLocation());
+
+ if (pluginDescriptorEntry != null) {
+ InputStream is = pluginJar.getInputStream(pluginDescriptorEntry);
+
+ pluginDescriptor = parsePluginDescriptor(is, plugin, pluginFile.getAbsolutePath());
+ }
+ }
+ } else {
+ File pluginXml = new File(pluginFile, getPluginDescriptorLocation());
+
+ if (pluginXml.isFile()) {
+ try (InputStream is = new BufferedInputStream(new FileInputStream(pluginXml))) {
+ pluginDescriptor = parsePluginDescriptor(is, plugin, pluginXml.getAbsolutePath());
+ }
+ }
+ }
+
+ if (pluginDescriptor == null) {
+ throw new IOException("No plugin descriptor found at " + getPluginDescriptorLocation());
+ }
+ } catch (IOException e) {
+ throw new PluginDescriptorParsingException(plugin, pluginFile.getAbsolutePath(), e);
+ }
+
+ MavenPluginValidator validator = new MavenPluginValidator(pluginArtifact);
+
+ validator.validate(pluginDescriptor);
+
+ if (validator.hasErrors()) {
+ throw new InvalidPluginDescriptorException(
+ "Invalid plugin descriptor for " + plugin.getId() + " (" + pluginFile + ")", validator.getErrors());
+ }
+
+ pluginDescriptor.setPluginArtifact(pluginArtifact);
+
+ return pluginDescriptor;
+ }
+
+ private String getPluginDescriptorLocation() {
+ return "META-INF/maven/plugin.xml";
+ }
+
+ private PluginDescriptor parsePluginDescriptor(InputStream is, Plugin plugin, String descriptorLocation)
+ throws PluginDescriptorParsingException {
+ try {
+ Reader reader = ReaderFactory.newXmlReader(is);
+
+ PluginDescriptor pluginDescriptor = builder.build(reader, descriptorLocation);
+
+ return pluginDescriptor;
+ } catch (IOException | PlexusConfigurationException e) {
+ throw new PluginDescriptorParsingException(plugin, descriptorLocation, e);
+ }
+ }
+
+ public MojoDescriptor getMojoDescriptor(
+ Plugin plugin, String goal, List repositories, RepositorySystemSession session)
+ throws MojoNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
+ InvalidPluginDescriptorException {
+ PluginDescriptor pluginDescriptor = getPluginDescriptor(plugin, repositories, session);
+
+ MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(goal);
+
+ if (mojoDescriptor == null) {
+ throw new MojoNotFoundException(goal, pluginDescriptor);
+ }
+
+ return mojoDescriptor;
+ }
+
+ public void checkRequiredMavenVersion(PluginDescriptor pluginDescriptor) throws PluginIncompatibleException {
+ String requiredMavenVersion = pluginDescriptor.getRequiredMavenVersion();
+ if (StringUtils.isNotBlank(requiredMavenVersion)) {
+ try {
+ if (!runtimeInformation.isMavenVersion(requiredMavenVersion)) {
+ throw new PluginIncompatibleException(
+ pluginDescriptor.getPlugin(),
+ "The plugin " + pluginDescriptor.getId() + " requires Maven version "
+ + requiredMavenVersion);
+ }
+ } catch (RuntimeException e) {
+ logger.warn("Could not verify plugin's Maven prerequisite: " + e.getMessage());
+ }
+ }
+ }
+
+ public void setupPluginRealm(
+ PluginDescriptor pluginDescriptor,
+ MavenSession session,
+ ClassLoader parent,
+ List imports,
+ DependencyFilter filter)
+ throws PluginResolutionException, PluginContainerException {
+ Plugin plugin = pluginDescriptor.getPlugin();
+ MavenProject project = session.getCurrentProject();
+
+ if (plugin.isExtensions()) {
+ ExtensionRealmCache.CacheRecord extensionRecord;
+ try {
+ RepositorySystemSession repositorySession = session.getRepositorySession();
+ extensionRecord = setupExtensionsRealm(project, plugin, repositorySession);
+ } catch (PluginManagerException e) {
+ // extensions realm is expected to be fully setup at this point
+ // any exception means a problem in maven code, not a user error
+ throw new IllegalStateException(e);
+ }
+
+ ClassRealm pluginRealm = extensionRecord.getRealm();
+ List pluginArtifacts = extensionRecord.getArtifacts();
+
+ for (ComponentDescriptor> componentDescriptor : pluginDescriptor.getComponents()) {
+ componentDescriptor.setRealm(pluginRealm);
+ }
+
+ pluginDescriptor.setClassRealm(pluginRealm);
+ pluginDescriptor.setArtifacts(pluginArtifacts);
+ } else {
+ Map foreignImports = calcImports(project, parent, imports);
+
+ PluginRealmCache.Key cacheKey = pluginRealmCache.createKey(
+ plugin,
+ parent,
+ foreignImports,
+ filter,
+ project.getRemotePluginRepositories(),
+ session.getRepositorySession());
+
+ PluginRealmCache.CacheRecord cacheRecord = pluginRealmCache.get(cacheKey, () -> {
+ createPluginRealm(pluginDescriptor, session, parent, foreignImports, filter);
+ return new PluginRealmCache.CacheRecord(
+ pluginDescriptor.getClassRealm(), pluginDescriptor.getArtifacts());
+ });
+
+ if (cacheRecord != null) {
+ pluginDescriptor.setClassRealm(cacheRecord.getRealm());
+ pluginDescriptor.setArtifacts(new ArrayList<>(cacheRecord.getArtifacts()));
+ for (ComponentDescriptor> componentDescriptor : pluginDescriptor.getComponents()) {
+ componentDescriptor.setRealm(cacheRecord.getRealm());
+ }
+ }
+
+ pluginRealmCache.register(project, cacheKey, cacheRecord);
+ }
+ }
+
+ private void createPluginRealm(
+ PluginDescriptor pluginDescriptor,
+ MavenSession session,
+ ClassLoader parent,
+ Map foreignImports,
+ DependencyFilter filter)
+ throws PluginResolutionException, PluginContainerException {
+ Plugin plugin = Objects.requireNonNull(pluginDescriptor.getPlugin(), "pluginDescriptor.plugin cannot be null");
+
+ Artifact pluginArtifact = Objects.requireNonNull(
+ pluginDescriptor.getPluginArtifact(), "pluginDescriptor.pluginArtifact cannot be null");
+
+ MavenProject project = session.getCurrentProject();
+
+ final ClassRealm pluginRealm;
+ final List pluginArtifacts;
+
+ RepositorySystemSession repositorySession = session.getRepositorySession();
+ DependencyFilter dependencyFilter = project.getExtensionDependencyFilter();
+ dependencyFilter = AndDependencyFilter.newInstance(dependencyFilter, filter);
+
+ DependencyNode root = pluginDependenciesResolver.resolve(
+ plugin,
+ RepositoryUtils.toArtifact(pluginArtifact),
+ dependencyFilter,
+ project.getRemotePluginRepositories(),
+ repositorySession);
+
+ PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
+ root.accept(nlg);
+
+ pluginArtifacts = toMavenArtifacts(root, nlg);
+
+ if (parent == null) {
+ parent = new URLClassLoader(new URL[0]);
+ }
+ pluginRealm = classRealmManager.createPluginRealm(
+ plugin, parent, null, foreignImports, toAetherArtifacts(pluginArtifacts));
+
+ discoverPluginComponents(pluginRealm, plugin, pluginDescriptor);
+
+ pluginDescriptor.setClassRealm(pluginRealm);
+ pluginDescriptor.setArtifacts(pluginArtifacts);
+ }
+
+ private void discoverPluginComponents(
+ final ClassRealm pluginRealm, Plugin plugin, PluginDescriptor pluginDescriptor)
+ throws PluginContainerException {
+ try {
+ if (pluginDescriptor != null) {
+ for (ComponentDescriptor> componentDescriptor : pluginDescriptor.getComponents()) {
+ componentDescriptor.setRealm(pluginRealm);
+ container.addComponentDescriptor(componentDescriptor);
+ }
+ }
+
+ ((DefaultPlexusContainer) container)
+ .discoverComponents(
+ pluginRealm, new SessionScopeModule(container), new MojoExecutionScopeModule(container));
+ } catch (ComponentLookupException | CycleDetectedInComponentGraphException e) {
+ throw new PluginContainerException(
+ plugin,
+ pluginRealm,
+ "Error in component graph of plugin " + plugin.getId() + ": " + e.getMessage(),
+ e);
+ }
+ }
+
+ private List toAetherArtifacts(final List pluginArtifacts) {
+ return new ArrayList<>(RepositoryUtils.toArtifacts(pluginArtifacts));
+ }
+
+ private List toMavenArtifacts(DependencyNode root, PreorderNodeListGenerator nlg) {
+ List artifacts = new ArrayList<>(nlg.getNodes().size());
+ RepositoryUtils.toArtifacts(artifacts, Collections.singleton(root), Collections.emptyList(), null);
+ for (Iterator it = artifacts.iterator(); it.hasNext(); ) {
+ Artifact artifact = it.next();
+ if (artifact.getFile() == null) {
+ it.remove();
+ }
+ }
+ return Collections.unmodifiableList(artifacts);
+ }
+
+ private Map calcImports(MavenProject project, ClassLoader parent, List imports) {
+ Map foreignImports = new HashMap<>();
+
+ ClassLoader projectRealm = project.getClassRealm();
+ if (projectRealm != null) {
+ foreignImports.put("", projectRealm);
+ } else {
+ foreignImports.put("", classRealmManager.getMavenApiRealm());
+ }
+
+ if (parent != null && imports != null) {
+ for (String parentImport : imports) {
+ foreignImports.put(parentImport, parent);
+ }
+ }
+
+ return foreignImports;
+ }
+
+ public T getConfiguredMojo(Class mojoInterface, MavenSession session, MojoExecution mojoExecution)
+ throws PluginConfigurationException, PluginContainerException {
+ MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
+
+ PluginDescriptor pluginDescriptor = mojoDescriptor.getPluginDescriptor();
+
+ ClassRealm pluginRealm = pluginDescriptor.getClassRealm();
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Configuring mojo " + mojoDescriptor.getId() + " from plugin realm " + pluginRealm);
+ }
+
+ // We are forcing the use of the plugin realm for all lookups that might occur during
+ // the lifecycle that is part of the lookup. Here we are specifically trying to keep
+ // lookups that occur in contextualize calls in line with the right realm.
+ ClassRealm oldLookupRealm = container.setLookupRealm(pluginRealm);
+
+ ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(pluginRealm);
+
+ try {
+ T mojo;
+
+ try {
+ mojo = container.lookup(mojoInterface, mojoDescriptor.getRoleHint());
+ } catch (ComponentLookupException e) {
+ Throwable cause = e.getCause();
+ while (cause != null
+ && !(cause instanceof LinkageError)
+ && !(cause instanceof ClassNotFoundException)) {
+ cause = cause.getCause();
+ }
+
+ if ((cause instanceof NoClassDefFoundError) || (cause instanceof ClassNotFoundException)) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
+ PrintStream ps = new PrintStream(os);
+ ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ + pluginDescriptor.getId() + "'. A required class is missing: "
+ + cause.getMessage());
+ pluginRealm.display(ps);
+
+ throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
+ } else if (cause instanceof LinkageError) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
+ PrintStream ps = new PrintStream(os);
+ ps.println("Unable to load the mojo '" + mojoDescriptor.getGoal() + "' in the plugin '"
+ + pluginDescriptor.getId() + "' due to an API incompatibility: "
+ + e.getClass().getName() + ": " + cause.getMessage());
+ pluginRealm.display(ps);
+
+ throw new PluginContainerException(mojoDescriptor, pluginRealm, os.toString(), cause);
+ }
+
+ throw new PluginContainerException(
+ mojoDescriptor,
+ pluginRealm,
+ "Unable to load the mojo '" + mojoDescriptor.getGoal()
+ + "' (or one of its required components) from the plugin '"
+ + pluginDescriptor.getId() + "'",
+ e);
+ }
+
+ if (mojo instanceof ContextEnabled) {
+ MavenProject project = session.getCurrentProject();
+
+ Map pluginContext = session.getPluginContext(pluginDescriptor, project);
+
+ if (pluginContext != null) {
+ pluginContext.put("project", project);
+
+ pluginContext.put("pluginDescriptor", pluginDescriptor);
+
+ ((ContextEnabled) mojo).setPluginContext(pluginContext);
+ }
+ }
+
+ if (mojo instanceof Mojo) {
+ Logger mojoLogger = loggerManager.getLoggerForComponent(mojoDescriptor.getImplementation());
+ ((Mojo) mojo).setLog(new DefaultLog(mojoLogger));
+ }
+
+ Xpp3Dom dom = mojoExecution.getConfiguration();
+
+ PlexusConfiguration pomConfiguration;
+
+ if (dom == null) {
+ pomConfiguration = new XmlPlexusConfiguration("configuration");
+ } else {
+ pomConfiguration = new XmlPlexusConfiguration(dom);
+ }
+
+ ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution);
+
+ populatePluginFields(mojo, mojoDescriptor, pluginRealm, pomConfiguration, expressionEvaluator);
+
+ return mojo;
+ } finally {
+ Thread.currentThread().setContextClassLoader(oldClassLoader);
+ container.setLookupRealm(oldLookupRealm);
+ }
+ }
+
+ private void populatePluginFields(
+ Object mojo,
+ MojoDescriptor mojoDescriptor,
+ ClassRealm pluginRealm,
+ PlexusConfiguration configuration,
+ ExpressionEvaluator expressionEvaluator)
+ throws PluginConfigurationException {
+ ComponentConfigurator configurator = null;
+
+ String configuratorId = mojoDescriptor.getComponentConfigurator();
+
+ if (StringUtils.isEmpty(configuratorId)) {
+ configuratorId = "basic";
+ }
+
+ try {
+ // TODO could the configuration be passed to lookup and the configurator known to plexus via the descriptor
+ // so that this method could entirely be handled by a plexus lookup?
+ configurator = container.lookup(ComponentConfigurator.class, configuratorId);
+
+ ConfigurationListener listener = new DebugConfigurationListener(logger);
+
+ ValidatingConfigurationListener validator =
+ new ValidatingConfigurationListener(mojo, mojoDescriptor, listener);
+
+ logger.debug(
+ "Configuring mojo '" + mojoDescriptor.getId() + "' with " + configuratorId + " configurator -->");
+
+ configurator.configureComponent(mojo, configuration, expressionEvaluator, pluginRealm, validator);
+
+ logger.debug("-- end configuration --");
+
+ Collection missingParameters = validator.getMissingParameters();
+ if (!missingParameters.isEmpty()) {
+ if ("basic".equals(configuratorId)) {
+ throw new PluginParameterException(mojoDescriptor, new ArrayList<>(missingParameters));
+ } else {
+ /*
+ * NOTE: Other configurators like the map-oriented one don't call into the listener, so do it the
+ * hard way.
+ */
+ validateParameters(mojoDescriptor, configuration, expressionEvaluator);
+ }
+ }
+ } catch (ComponentConfigurationException e) {
+ String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
+ if (e.getFailedConfiguration() != null) {
+ message += " for parameter " + e.getFailedConfiguration().getName();
+ }
+ message += ": " + e.getMessage();
+
+ throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), message, e);
+ } catch (ComponentLookupException e) {
+ throw new PluginConfigurationException(
+ mojoDescriptor.getPluginDescriptor(),
+ "Unable to retrieve component configurator " + configuratorId + " for configuration of mojo "
+ + mojoDescriptor.getId(),
+ e);
+ } catch (NoClassDefFoundError e) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
+ PrintStream ps = new PrintStream(os);
+ ps.println("A required class was missing during configuration of mojo " + mojoDescriptor.getId() + ": "
+ + e.getMessage());
+ pluginRealm.display(ps);
+
+ throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
+ } catch (LinkageError e) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
+ PrintStream ps = new PrintStream(os);
+ ps.println("An API incompatibility was encountered during configuration of mojo " + mojoDescriptor.getId()
+ + ": " + e.getClass().getName() + ": " + e.getMessage());
+ pluginRealm.display(ps);
+
+ throw new PluginConfigurationException(mojoDescriptor.getPluginDescriptor(), os.toString(), e);
+ } finally {
+ if (configurator != null) {
+ try {
+ container.release(configurator);
+ } catch (ComponentLifecycleException e) {
+ logger.debug("Failed to release mojo configurator - ignoring.");
+ }
+ }
+ }
+ }
+
+ private void validateParameters(
+ MojoDescriptor mojoDescriptor, PlexusConfiguration configuration, ExpressionEvaluator expressionEvaluator)
+ throws ComponentConfigurationException, PluginParameterException {
+ if (mojoDescriptor.getParameters() == null) {
+ return;
+ }
+
+ List invalidParameters = new ArrayList<>();
+
+ for (Parameter parameter : mojoDescriptor.getParameters()) {
+ if (!parameter.isRequired()) {
+ continue;
+ }
+
+ Object value = null;
+
+ PlexusConfiguration config = configuration.getChild(parameter.getName(), false);
+ if (config != null) {
+ String expression = config.getValue(null);
+
+ try {
+ value = expressionEvaluator.evaluate(expression);
+
+ if (value == null) {
+ value = config.getAttribute("default-value", null);
+ }
+ } catch (ExpressionEvaluationException e) {
+ String msg = "Error evaluating the expression '" + expression + "' for configuration value '"
+ + configuration.getName() + "'";
+ throw new ComponentConfigurationException(configuration, msg, e);
+ }
+ }
+
+ if (value == null && (config == null || config.getChildCount() <= 0)) {
+ invalidParameters.add(parameter);
+ }
+ }
+
+ if (!invalidParameters.isEmpty()) {
+ throw new PluginParameterException(mojoDescriptor, invalidParameters);
+ }
+ }
+
+ public void releaseMojo(Object mojo, MojoExecution mojoExecution) {
+ if (mojo != null) {
+ try {
+ container.release(mojo);
+ } catch (ComponentLifecycleException e) {
+ String goalExecId = mojoExecution.getGoal();
+
+ if (mojoExecution.getExecutionId() != null) {
+ goalExecId += " {execution: " + mojoExecution.getExecutionId() + "}";
+ }
+
+ logger.debug("Error releasing mojo for " + goalExecId, e);
+ }
+ }
+ }
+
+ public ExtensionRealmCache.CacheRecord setupExtensionsRealm(
+ MavenProject project, Plugin plugin, RepositorySystemSession session) throws PluginManagerException {
+ @SuppressWarnings("unchecked")
+ Map pluginRealms =
+ (Map) project.getContextValue(KEY_EXTENSIONS_REALMS);
+ if (pluginRealms == null) {
+ pluginRealms = new HashMap<>();
+ project.setContextValue(KEY_EXTENSIONS_REALMS, pluginRealms);
+ }
+
+ final String pluginKey = plugin.getId();
+
+ ExtensionRealmCache.CacheRecord extensionRecord = pluginRealms.get(pluginKey);
+ if (extensionRecord != null) {
+ return extensionRecord;
+ }
+
+ final List repositories = project.getRemotePluginRepositories();
+
+ // resolve plugin version as necessary
+ if (plugin.getVersion() == null) {
+ PluginVersionRequest versionRequest = new DefaultPluginVersionRequest(plugin, session, repositories);
+ try {
+ plugin.setVersion(pluginVersionResolver.resolve(versionRequest).getVersion());
+ } catch (PluginVersionResolutionException e) {
+ throw new PluginManagerException(plugin, e.getMessage(), e);
+ }
+ }
+
+ // resolve plugin artifacts
+ List artifacts;
+ PluginArtifactsCache.Key cacheKey = pluginArtifactsCache.createKey(plugin, null, repositories, session);
+ PluginArtifactsCache.CacheRecord recordArtifacts;
+ try {
+ recordArtifacts = pluginArtifactsCache.get(cacheKey);
+ } catch (PluginResolutionException e) {
+ throw new PluginManagerException(plugin, e.getMessage(), e);
+ }
+ if (recordArtifacts != null) {
+ artifacts = recordArtifacts.getArtifacts();
+ } else {
+ try {
+ artifacts = resolveExtensionArtifacts(plugin, repositories, session);
+ recordArtifacts = pluginArtifactsCache.put(cacheKey, artifacts);
+ } catch (PluginResolutionException e) {
+ pluginArtifactsCache.put(cacheKey, e);
+ pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
+ throw new PluginManagerException(plugin, e.getMessage(), e);
+ }
+ }
+ pluginArtifactsCache.register(project, cacheKey, recordArtifacts);
+
+ // create and cache extensions realms
+ final ExtensionRealmCache.Key extensionKey = extensionRealmCache.createKey(artifacts);
+ extensionRecord = extensionRealmCache.get(extensionKey);
+ if (extensionRecord == null) {
+ ClassRealm extensionRealm = classRealmManager.createExtensionRealm(plugin, toAetherArtifacts(artifacts));
+
+ // TODO figure out how to use the same PluginDescriptor when running mojos
+
+ PluginDescriptor pluginDescriptor = null;
+ if (plugin.isExtensions() && !artifacts.isEmpty()) {
+ // ignore plugin descriptor parsing errors at this point
+ // these errors will reported during calculation of project build execution plan
+ try {
+ pluginDescriptor = extractPluginDescriptor(artifacts.get(0), plugin);
+ } catch (PluginDescriptorParsingException | InvalidPluginDescriptorException e) {
+ // ignore, see above
+ }
+ }
+
+ discoverPluginComponents(extensionRealm, plugin, pluginDescriptor);
+
+ ExtensionDescriptor extensionDescriptor = null;
+ Artifact extensionArtifact = artifacts.get(0);
+ try {
+ extensionDescriptor = extensionDescriptorBuilder.build(extensionArtifact.getFile());
+ } catch (IOException e) {
+ String message = "Invalid extension descriptor for " + plugin.getId() + ": " + e.getMessage();
+ if (logger.isDebugEnabled()) {
+ logger.error(message, e);
+ } else {
+ logger.error(message);
+ }
+ }
+ extensionRecord = extensionRealmCache.put(extensionKey, extensionRealm, extensionDescriptor, artifacts);
+ }
+ extensionRealmCache.register(project, extensionKey, extensionRecord);
+ pluginRealms.put(pluginKey, extensionRecord);
+
+ return extensionRecord;
+ }
+
+ private List resolveExtensionArtifacts(
+ Plugin extensionPlugin, List repositories, RepositorySystemSession session)
+ throws PluginResolutionException {
+ DependencyNode root = pluginDependenciesResolver.resolve(extensionPlugin, null, null, repositories, session);
+ PreorderNodeListGenerator nlg = new PreorderNodeListGenerator();
+ root.accept(nlg);
+ return toMavenArtifacts(root, nlg);
+ }
+}
diff --git a/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java b/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java
new file mode 100644
index 00000000..06d8b65f
--- /dev/null
+++ b/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/ValidatingConfigurationListener.java
@@ -0,0 +1,82 @@
+/*
+ * 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.mvndaemon.mvnd.plugin;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.maven.plugin.descriptor.MojoDescriptor;
+import org.apache.maven.plugin.descriptor.Parameter;
+import org.codehaus.plexus.component.configurator.ConfigurationListener;
+
+/**
+ * A configuration listener to help validate the plugin configuration. For instance, check for required but missing
+ * parameters.
+ *
+ * @author Benjamin Bentmann
+ */
+class ValidatingConfigurationListener implements ConfigurationListener {
+
+ private final Object mojo;
+
+ private final ConfigurationListener delegate;
+
+ private final Map missingParameters;
+
+ ValidatingConfigurationListener(Object mojo, MojoDescriptor mojoDescriptor, ConfigurationListener delegate) {
+ this.mojo = mojo;
+ this.delegate = delegate;
+ this.missingParameters = new HashMap<>();
+
+ if (mojoDescriptor.getParameters() != null) {
+ for (Parameter param : mojoDescriptor.getParameters()) {
+ if (param.isRequired()) {
+ missingParameters.put(param.getName(), param);
+ }
+ }
+ }
+ }
+
+ public Collection getMissingParameters() {
+ return missingParameters.values();
+ }
+
+ public void notifyFieldChangeUsingSetter(String fieldName, Object value, Object target) {
+ delegate.notifyFieldChangeUsingSetter(fieldName, value, target);
+
+ if (mojo == target) {
+ notify(fieldName, value);
+ }
+ }
+
+ public void notifyFieldChangeUsingReflection(String fieldName, Object value, Object target) {
+ delegate.notifyFieldChangeUsingReflection(fieldName, value, target);
+
+ if (mojo == target) {
+ notify(fieldName, value);
+ }
+ }
+
+ private void notify(String fieldName, Object value) {
+ if (value != null) {
+ missingParameters.remove(fieldName);
+ }
+ }
+}
diff --git a/daemon-m40/pom.xml b/daemon-m40/pom.xml
new file mode 100644
index 00000000..3500cb29
--- /dev/null
+++ b/daemon-m40/pom.xml
@@ -0,0 +1,50 @@
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.daemon
+ mvnd
+ 1.0.0-m5-SNAPSHOT
+
+
+ mvnd-daemon-m40
+
+ jar
+ Maven Daemon - Daemon 4.0.x specifics
+
+
+
+ org.apache.maven
+ maven-core
+ ${maven4.version}
+
+
+ org.apache.maven
+ maven-embedder
+ ${maven4.version}
+
+
+ org.apache.maven.daemon
+ mvnd-daemon
+
+
+
+
diff --git a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java b/daemon-m40/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
similarity index 99%
rename from daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
rename to daemon-m40/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
index 940d6180..92e13383 100644
--- a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
+++ b/daemon-m40/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
@@ -70,8 +70,6 @@ import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.model.building.ModelProcessor;
import org.apache.maven.plugin.ExtensionRealmCache;
import org.apache.maven.plugin.PluginArtifactsCache;
-import org.apache.maven.plugin.PluginRealmCache;
-import org.apache.maven.plugin.version.PluginVersionResolver;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.artifact.ProjectArtifactsCache;
import org.apache.maven.properties.internal.SystemProperties;
@@ -93,7 +91,6 @@ import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.transfer.TransferListener;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingExtensionRealmCache;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginArtifactsCache;
-import org.mvndaemon.mvnd.cache.invalidating.InvalidatingPluginRealmCache;
import org.mvndaemon.mvnd.cache.invalidating.InvalidatingProjectArtifactsCache;
import org.mvndaemon.mvnd.cli.EnvHelper;
import org.mvndaemon.mvnd.common.Environment;
@@ -102,7 +99,6 @@ import org.mvndaemon.mvnd.logging.internal.Slf4jLoggerManager;
import org.mvndaemon.mvnd.logging.smart.BuildEventListener;
import org.mvndaemon.mvnd.logging.smart.LoggingExecutionListener;
import org.mvndaemon.mvnd.logging.smart.LoggingOutputStream;
-import org.mvndaemon.mvnd.plugin.CachingPluginVersionResolver;
import org.mvndaemon.mvnd.transfer.DaemonMavenTransferListener;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
@@ -119,7 +115,7 @@ import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
*
* @author Jason van Zyl
*/
-public class DaemonMavenCli {
+public class DaemonMavenCli implements DaemonCli {
public static final String LOCAL_REPO_PROPERTY = "maven.repo.local";
public static final String MULTIMODULE_PROJECT_DIRECTORY = "maven.multiModuleProjectDirectory";
@@ -522,9 +518,9 @@ public class DaemonMavenCli {
bind(CoreExportsProvider.class).toInstance(new CoreExportsProvider(exports));
bind(ExtensionRealmCache.class).to(InvalidatingExtensionRealmCache.class);
bind(PluginArtifactsCache.class).to(InvalidatingPluginArtifactsCache.class);
- bind(PluginRealmCache.class).to(InvalidatingPluginRealmCache.class);
+ // bind(PluginRealmCache.class).to(InvalidatingPluginRealmCache.class);
bind(ProjectArtifactsCache.class).to(InvalidatingProjectArtifactsCache.class);
- bind(PluginVersionResolver.class).to(CachingPluginVersionResolver.class);
+ // bind(PluginVersionResolver.class).to(CachingPluginVersionResolver.class);
}
});
diff --git a/daemon/src/main/java/org/apache/maven/project/SnapshotModelCache.java b/daemon-m40/src/main/java/org/apache/maven/project/SnapshotModelCache.java
similarity index 92%
rename from daemon/src/main/java/org/apache/maven/project/SnapshotModelCache.java
rename to daemon-m40/src/main/java/org/apache/maven/project/SnapshotModelCache.java
index 39ff8b04..275fc97f 100644
--- a/daemon/src/main/java/org/apache/maven/project/SnapshotModelCache.java
+++ b/daemon-m40/src/main/java/org/apache/maven/project/SnapshotModelCache.java
@@ -18,6 +18,8 @@
*/
package org.apache.maven.project;
+import java.util.Objects;
+
import org.apache.maven.building.Source;
import org.apache.maven.model.building.ModelCache;
@@ -27,8 +29,8 @@ public class SnapshotModelCache implements ModelCache {
private final ModelCache reactorCache;
public SnapshotModelCache(ModelCache globalCache, ModelCache reactorCache) {
- this.globalCache = globalCache;
- this.reactorCache = reactorCache;
+ this.globalCache = Objects.requireNonNull(globalCache);
+ this.reactorCache = Objects.requireNonNull(reactorCache);
}
public Object get(Source path, String tag) {
diff --git a/daemon/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java b/daemon-m40/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java
similarity index 100%
rename from daemon/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java
rename to daemon-m40/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java
diff --git a/daemon/src/main/java/org/apache/maven/settings/SettingsUtilsV4.java b/daemon-m40/src/main/java/org/apache/maven/settings/SettingsUtilsV4.java
similarity index 100%
rename from daemon/src/main/java/org/apache/maven/settings/SettingsUtilsV4.java
rename to daemon-m40/src/main/java/org/apache/maven/settings/SettingsUtilsV4.java
diff --git a/daemon-m40/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java b/daemon-m40/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java
new file mode 100644
index 00000000..23862274
--- /dev/null
+++ b/daemon-m40/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java
@@ -0,0 +1,136 @@
+/*
+ * 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.mvndaemon.mvnd.cache.invalidating;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.maven.model.Plugin;
+import org.apache.maven.plugin.DefaultPluginDescriptorCache;
+import org.apache.maven.plugin.InvalidPluginDescriptorException;
+import org.apache.maven.plugin.PluginDescriptorParsingException;
+import org.apache.maven.plugin.PluginResolutionException;
+import org.apache.maven.plugin.descriptor.PluginDescriptor;
+import org.codehaus.plexus.classworlds.realm.ClassRealm;
+import org.codehaus.plexus.classworlds.realm.NoSuchRealmException;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.sisu.Priority;
+import org.mvndaemon.mvnd.cache.Cache;
+import org.mvndaemon.mvnd.cache.CacheFactory;
+import org.mvndaemon.mvnd.cache.CacheRecord;
+
+@Singleton
+@Named
+@Priority(10)
+public class InvalidatingPluginDescriptorCache extends DefaultPluginDescriptorCache {
+
+ @FunctionalInterface
+ public interface PluginDescriptorSupplier {
+ PluginDescriptor load()
+ throws PluginResolutionException, PluginDescriptorParsingException, InvalidPluginDescriptorException;
+ }
+
+ protected static class Record implements CacheRecord {
+
+ private final PluginDescriptor descriptor;
+
+ public Record(PluginDescriptor descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ @Override
+ public Stream getDependencyPaths() {
+ return Optional.ofNullable(descriptor.getArtifacts()).orElse(Collections.emptyList()).stream()
+ .map(artifact -> artifact.getFile().toPath());
+ }
+
+ @Override
+ public void invalidate() {
+ ClassRealm realm = descriptor.getClassRealm();
+ try {
+ realm.getWorld().disposeRealm(realm.getId());
+ } catch (NoSuchRealmException e) {
+ // ignore
+ }
+ }
+ }
+
+ final Cache cache;
+
+ @Inject
+ public InvalidatingPluginDescriptorCache(CacheFactory cacheFactory) {
+ this.cache = cacheFactory.newCache();
+ }
+
+ @Override
+ public Key createKey(Plugin plugin, List repositories, RepositorySystemSession session) {
+ return super.createKey(plugin, repositories, session);
+ }
+
+ @Override
+ public PluginDescriptor get(Key key) {
+ Record r = cache.get(key);
+ return r != null ? clone(r.descriptor) : null;
+ }
+
+ public PluginDescriptor get(Key key, PluginDescriptorSupplier supplier)
+ throws PluginDescriptorParsingException, PluginResolutionException, InvalidPluginDescriptorException {
+ try {
+ Record r = cache.computeIfAbsent(key, k -> {
+ try {
+ return new Record(clone(supplier.load()));
+ } catch (PluginDescriptorParsingException
+ | PluginResolutionException
+ | InvalidPluginDescriptorException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ return clone(r.descriptor);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof PluginDescriptorParsingException) {
+ throw (PluginDescriptorParsingException) e.getCause();
+ }
+ if (e.getCause() instanceof PluginResolutionException) {
+ throw (PluginResolutionException) e.getCause();
+ }
+ if (e.getCause() instanceof InvalidPluginDescriptorException) {
+ throw (InvalidPluginDescriptorException) e.getCause();
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public void put(Key key, PluginDescriptor descriptor) {
+ cache.put(key, new Record(clone(descriptor)));
+ }
+
+ @Override
+ public void flush() {
+ cache.clear();
+ }
+}
diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java b/daemon-m40/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
similarity index 100%
rename from daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
rename to daemon-m40/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
diff --git a/daemon-m40/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java b/daemon-m40/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java
new file mode 100644
index 00000000..e69da0a0
--- /dev/null
+++ b/daemon-m40/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java
@@ -0,0 +1,190 @@
+/*
+ * 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.mvndaemon.mvnd.cache.invalidating;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.eventspy.AbstractEventSpy;
+import org.apache.maven.eventspy.EventSpy;
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.execution.MavenExecutionResult;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.sisu.Typed;
+import org.mvndaemon.mvnd.common.Environment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Named
+@Singleton
+@Typed(EventSpy.class)
+public class InvalidatingRealmCacheEventSpy extends AbstractEventSpy {
+
+ private static final Logger LOG = LoggerFactory.getLogger(InvalidatingRealmCacheEventSpy.class);
+
+ private final InvalidatingPluginRealmCache pluginCache;
+ private final InvalidatingExtensionRealmCache extensionCache;
+ private final InvalidatingProjectArtifactsCache projectArtifactsCache;
+ private Path multiModuleProjectDirectory;
+ private String pattern;
+ private PathMatcher matcher;
+
+ @Inject
+ public InvalidatingRealmCacheEventSpy(
+ InvalidatingPluginRealmCache cache,
+ InvalidatingExtensionRealmCache extensionCache,
+ InvalidatingProjectArtifactsCache projectArtifactsCache) {
+ this.pluginCache = cache;
+ this.extensionCache = extensionCache;
+ this.projectArtifactsCache = projectArtifactsCache;
+ }
+
+ @Override
+ public void onEvent(Object event) throws Exception {
+ try {
+ if (event instanceof MavenExecutionRequest) {
+ /* Store the multiModuleProjectDirectory path */
+ multiModuleProjectDirectory = ((MavenExecutionRequest) event)
+ .getMultiModuleProjectDirectory()
+ .toPath();
+ pattern = Environment.MVND_PLUGIN_REALM_EVICT_PATTERN
+ .asOptional()
+ .orElse(Environment.MVND_PLUGIN_REALM_EVICT_PATTERN.getDefault());
+ if (!pattern.isEmpty()) {
+ String[] patterns = pattern.split(",");
+ List matchers = new ArrayList<>();
+ for (String pattern : patterns) {
+ if (pattern.startsWith("mvn:")) {
+ String[] parts = pattern.substring("mvn:".length()).split(":");
+ String groupId, artifactId, version;
+ if (parts.length >= 3) {
+ version = parts[2];
+ } else {
+ version = "*";
+ }
+ if (parts.length >= 2) {
+ groupId = parts[0];
+ artifactId = parts[1];
+ } else {
+ groupId = "*";
+ artifactId = parts[0];
+ }
+ pattern = "glob:**/" + ("*".equals(groupId) ? "" : groupId.replace('.', '/') + "/")
+ + artifactId + "/" + ("*".equals(version) ? "**" : version + "/**");
+ }
+ matchers.add(getPathMatcher(pattern));
+ }
+ if (matchers.size() == 1) {
+ matcher = matchers.iterator().next();
+ } else {
+ matcher = path -> matchers.stream().anyMatch(f -> f.matches(path));
+ }
+ }
+ } else if (event instanceof MavenExecutionResult) {
+ /* Evict the entries referring to jars under multiModuleProjectDirectory */
+ pluginCache.cache.removeIf(this::shouldEvict);
+ extensionCache.cache.removeIf(this::shouldEvict);
+ MavenExecutionResult mer = (MavenExecutionResult) event;
+ List projects = mer.getTopologicallySortedProjects();
+ projectArtifactsCache.cache.removeIf(
+ (k, r) -> shouldEvict(projects, (InvalidatingProjectArtifactsCache.CacheKey) k, r));
+ }
+ } catch (Exception e) {
+ LOG.warn("Could not notify CliPluginRealmCache", e);
+ }
+ }
+
+ private boolean shouldEvict(
+ List projects,
+ InvalidatingProjectArtifactsCache.CacheKey k,
+ InvalidatingProjectArtifactsCache.Record v) {
+ return projects.stream().anyMatch(p -> k.matches(p.getGroupId(), p.getArtifactId(), p.getVersion()));
+ }
+
+ private boolean shouldEvict(InvalidatingPluginRealmCache.Key k, InvalidatingPluginRealmCache.Record v) {
+ try {
+ for (URL url : v.record.getRealm().getURLs()) {
+ if (url.getProtocol().equals("file")) {
+ final Path path = Paths.get(url.toURI());
+ if (path.startsWith(multiModuleProjectDirectory)) {
+ LOG.debug(
+ "Removing PluginRealmCache entry {} because it refers to an artifact in the build tree {}",
+ k,
+ path);
+ return true;
+ } else if (matcher != null && matcher.matches(path)) {
+ LOG.debug(
+ "Removing PluginRealmCache entry {} because its components {} matches the eviction pattern '{}'",
+ k,
+ path,
+ pattern);
+ return true;
+ }
+ }
+ }
+ return false;
+ } catch (URISyntaxException e) {
+ return true;
+ }
+ }
+
+ private boolean shouldEvict(InvalidatingExtensionRealmCache.Key k, InvalidatingExtensionRealmCache.Record v) {
+ try {
+ for (URL url : v.record.getRealm().getURLs()) {
+ if (url.getProtocol().equals("file")) {
+ final Path path = Paths.get(url.toURI());
+ if (path.startsWith(multiModuleProjectDirectory)) {
+ LOG.debug(
+ "Removing ExtensionRealmCache entry {} because it refers to an artifact in the build tree {}",
+ k,
+ path);
+ return true;
+ } else if (matcher != null && matcher.matches(path)) {
+ LOG.debug(
+ "Removing ExtensionRealmCache entry {} because its components {} matches the eviction pattern '{}'",
+ k,
+ path,
+ pattern);
+ return true;
+ }
+ }
+ }
+ return false;
+ } catch (URISyntaxException e) {
+ return true;
+ }
+ }
+
+ private static PathMatcher getPathMatcher(String pattern) {
+ if (!pattern.startsWith("glob:") && !pattern.startsWith("regex:")) {
+ pattern = "glob:" + pattern;
+ }
+ return FileSystems.getDefault().getPathMatcher(pattern);
+ }
+}
diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java b/daemon-m40/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
similarity index 100%
rename from daemon/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
rename to daemon-m40/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
diff --git a/daemon/pom.xml b/daemon/pom.xml
index afb97832..d4d23522 100644
--- a/daemon/pom.xml
+++ b/daemon/pom.xml
@@ -28,7 +28,7 @@
mvnd-daemon
jar
- Maven Daemon
+ Maven Daemon - Daemon
diff --git a/daemon/src/main/java/org/apache/maven/cli/DaemonCli.java b/daemon/src/main/java/org/apache/maven/cli/DaemonCli.java
new file mode 100644
index 00000000..e0815167
--- /dev/null
+++ b/daemon/src/main/java/org/apache/maven/cli/DaemonCli.java
@@ -0,0 +1,37 @@
+/*
+ * 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 java.util.List;
+import java.util.Map;
+
+import org.mvndaemon.mvnd.logging.smart.BuildEventListener;
+
+/**
+ * Simple interface to bridge maven 3.9.x and 4.0.x CLI
+ */
+public interface DaemonCli {
+ int main(
+ List args,
+ String workingDir,
+ String projectDir,
+ Map env,
+ BuildEventListener buildEventListener)
+ throws Exception;
+}
diff --git a/daemon/src/main/java/org/mvndaemon/mvnd/daemon/Server.java b/daemon/src/main/java/org/mvndaemon/mvnd/daemon/Server.java
index a10502f0..0bbf27ae 100644
--- a/daemon/src/main/java/org/mvndaemon/mvnd/daemon/Server.java
+++ b/daemon/src/main/java/org/mvndaemon/mvnd/daemon/Server.java
@@ -50,7 +50,7 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
-import org.apache.maven.cli.DaemonMavenCli;
+import org.apache.maven.cli.DaemonCli;
import org.mvndaemon.mvnd.builder.SmartBuilder;
import org.mvndaemon.mvnd.common.DaemonConnection;
import org.mvndaemon.mvnd.common.DaemonException;
@@ -87,7 +87,7 @@ public class Server implements AutoCloseable, Runnable {
private final String daemonId;
private final boolean noDaemon;
private final ServerSocketChannel socket;
- private final DaemonMavenCli cli;
+ private final DaemonCli cli;
private volatile DaemonInfo info;
private final DaemonRegistry registry;
@@ -129,7 +129,11 @@ public class Server implements AutoCloseable, Runnable {
.orElse(SocketFamily.inet);
try {
- cli = new DaemonMavenCli();
+ cli = (DaemonCli) getClass()
+ .getClassLoader()
+ .loadClass("org.apache.maven.cli.DaemonMavenCli")
+ .getDeclaredConstructor()
+ .newInstance();
registry = new DaemonRegistry(Environment.MVND_REGISTRY.asPath());
socket = socketFamily.openServerSocket();
executor = Executors.newScheduledThreadPool(1);
diff --git a/dist/pom.xml b/dist-m39/pom.xml
similarity index 88%
rename from dist/pom.xml
rename to dist-m39/pom.xml
index 46aeb88e..464fc7e9 100644
--- a/dist/pom.xml
+++ b/dist-m39/pom.xml
@@ -25,10 +25,10 @@
1.0.0-m5-SNAPSHOT
- mvnd-dist
+ mvnd-dist-m39
pom
- Maven Daemon - Distribution
+ Maven Daemon - Distribution for 3.9.x
11
@@ -52,6 +52,10 @@
org.apache.maven.daemon
mvnd-daemon
+
+ org.apache.maven.daemon
+ mvnd-daemon-m39
+
@@ -67,7 +71,7 @@
package
- ${project.build.directory}/maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}
+ ${project.build.directory}/maven-${project.version}-mvnd-m39-${os.detected.name}-${os.detected.arch}
diff --git a/dist-m39/src/main/provisio/maven-distro.xml b/dist-m39/src/main/provisio/maven-distro.xml
new file mode 100644
index 00000000..db3c73ff
--- /dev/null
+++ b/dist-m39/src/main/provisio/maven-distro.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NOTICE.txt
+ LICENSE.txt
+ README.adoc
+
+
+
+
+ mvnd
+ mvnd.exe
+
+
+ platform-${os.detected.name}-${os.detected.arch}
+
+
+
+
+
+
+
+
diff --git a/dist-m40/pom.xml b/dist-m40/pom.xml
new file mode 100644
index 00000000..e9516be4
--- /dev/null
+++ b/dist-m40/pom.xml
@@ -0,0 +1,82 @@
+
+
+
+
+ 4.0.0
+
+ org.apache.maven.daemon
+ mvnd
+ 1.0.0-m5-SNAPSHOT
+
+
+ mvnd-dist-m40
+
+ pom
+ Maven Daemon - Distribution for 4.0.x
+
+
+ 11
+ 11
+
+
+
+
+ org.apache.maven.daemon
+ mvnd-agent
+
+
+ org.apache.maven.daemon
+ mvnd-helper-agent
+
+
+ org.apache.maven.daemon
+ mvnd-client
+
+
+ org.apache.maven.daemon
+ mvnd-daemon
+
+
+ org.apache.maven.daemon
+ mvnd-daemon-m40
+
+
+
+
+
+
+ ca.vanzyl.provisio.maven.plugins
+ provisio-maven-plugin
+
+
+ maven-distro
+
+ provision
+
+ package
+
+ ${project.build.directory}/maven-${project.version}-mvnd-m40-${os.detected.name}-${os.detected.arch}
+
+
+
+
+
+
+
+
diff --git a/dist/src/main/provisio/maven-distro.xml b/dist-m40/src/main/provisio/maven-distro.xml
similarity index 81%
rename from dist/src/main/provisio/maven-distro.xml
rename to dist-m40/src/main/provisio/maven-distro.xml
index 93647c4b..05b775a3 100644
--- a/dist/src/main/provisio/maven-distro.xml
+++ b/dist-m40/src/main/provisio/maven-distro.xml
@@ -18,7 +18,7 @@
-
+
@@ -37,6 +37,9 @@
+
+
+
@@ -64,7 +67,7 @@
-
+
NOTICE.txt
LICENSE.txt
@@ -76,13 +79,15 @@
mvnd
mvnd.exe
-
+
+ platform-${os.detected.name}-${os.detected.arch}
+
-
-
diff --git a/dist/src/main/resources/platform-darwin-aarch64 b/dist/src/main/resources/platform-darwin-aarch64
new file mode 100644
index 00000000..90e7887e
--- /dev/null
+++ b/dist/src/main/resources/platform-darwin-aarch64
@@ -0,0 +1,13 @@
+ Copyright 2019-2021 the original author or authors.
+
+ Licensed 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.
diff --git a/dist/src/main/resources/platform-darwin-amd64 b/dist/src/main/resources/platform-darwin-amd64
new file mode 100644
index 00000000..90e7887e
--- /dev/null
+++ b/dist/src/main/resources/platform-darwin-amd64
@@ -0,0 +1,13 @@
+ Copyright 2019-2021 the original author or authors.
+
+ Licensed 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.
diff --git a/dist/src/main/resources/platform-linux-amd64 b/dist/src/main/resources/platform-linux-amd64
new file mode 100644
index 00000000..90e7887e
--- /dev/null
+++ b/dist/src/main/resources/platform-linux-amd64
@@ -0,0 +1,13 @@
+ Copyright 2019-2021 the original author or authors.
+
+ Licensed 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.
diff --git a/dist/src/main/resources/platform-windows-amd64 b/dist/src/main/resources/platform-windows-amd64
new file mode 100644
index 00000000..90e7887e
--- /dev/null
+++ b/dist/src/main/resources/platform-windows-amd64
@@ -0,0 +1,13 @@
+ Copyright 2019-2021 the original author or authors.
+
+ Licensed 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.
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 286d9a18..d88a5457 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -32,7 +32,8 @@
11
11
- ${project.basedir}/../dist/target/maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}
+ ${project.basedir}/../dist-m39/target/maven-${project.version}-mvnd-m39-${os.detected.name}-${os.detected.arch}
+ ${project.basedir}/../dist-m40/target/maven-${project.version}-mvnd-m40-${os.detected.name}-${os.detected.arch}
org/apache/maven/surefire/surefire-providers/${surefire.version}
org/apache/maven/surefire/surefire-junit-platform/${surefire.version}
org/junit/platform/junit-platform-launcher/${junit-platform-launcher.version}
@@ -56,7 +57,19 @@
org.apache.maven.daemon
- mvnd-dist
+ mvnd-dist-m39
+ pom
+ test
+
+
+ org.slf4j
+ slf4j-simple
+
+
+
+
+ org.apache.maven.daemon
+ mvnd-dist-m40
pom
test
@@ -89,17 +102,50 @@
org.apache.maven.plugins
maven-surefire-plugin
-
- ${project.version}
- ${mvnd.home}
- ${mrm.repository.url}
- ${os.detected.name}
- ${os.detected.arch}
- ${settings.localRepository}
- ${preinstall.artifacts}
-
- 2
+ 4
+
+
+ default-test
+ none
+
+
+ mvn-39
+
+ test
+
+ test
+
+
+ ${mvnd.m39.home}
+ ${project.version}
+ ${mrm.repository.url}
+ ${os.detected.name}
+ ${os.detected.arch}
+ ${settings.localRepository}
+ ${preinstall.artifacts}
+
+
+
+
+ mvn-40
+
+ test
+
+ test
+
+
+ ${mvnd.m40.home}
+ ${project.version}
+ ${mrm.repository.url}
+ ${os.detected.name}
+ ${os.detected.arch}
+ ${settings.localRepository}
+ ${preinstall.artifacts}
+
+
+
+
org.apache.maven.plugins
@@ -168,6 +214,7 @@
maven-failsafe-plugin
+ native-39
integration-test
verify
@@ -175,7 +222,25 @@
${project.version}
- ${mvnd.home}
+ ${mvnd.m39.home}
+ ${mrm.repository.url}
+ ${os.detected.name}
+ ${os.detected.arch}
+ ${settings.localRepository}
+ ${preinstall.artifacts}
+
+
+
+
+ native-40
+
+ integration-test
+ verify
+
+
+
+ ${project.version}
+ ${mvnd.m40.home}
${mrm.repository.url}
${os.detected.name}
${os.detected.arch}
diff --git a/pom.xml b/pom.xml
index 25a2863f..a7bdad06 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,7 +51,10 @@
common
client
daemon
- dist
+ daemon-m39
+ daemon-m40
+ dist-m39
+ dist-m40
integration-tests
@@ -85,6 +88,8 @@
5.9.2
1.2.11
4.0.0-alpha-4
+ 3.9.0
+ ${maven.version}
1.9.4
1.7.36
@@ -251,7 +256,13 @@
org.apache.maven.daemon
- mvnd-dist
+ mvnd-dist-m39
+ ${project.version}
+ pom
+
+
+ org.apache.maven.daemon
+ mvnd-dist-m40
${project.version}
pom
@@ -260,6 +271,16 @@
mvnd-daemon
${project.version}
+
+ org.apache.maven.daemon
+ mvnd-daemon-m39
+ ${project.version}
+
+
+ org.apache.maven.daemon
+ mvnd-daemon-m40
+ ${project.version}
+
org.apache.maven.daemon
mvnd-helper-agent