This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-mvnd.git


The following commit(s) were added to refs/heads/master by this push:
     new 95b40a3d Provide distributions for both maven 3.9.x and 4.0.x (#796)
95b40a3d is described below

commit 95b40a3d8a6adf91686234ebb39281ff0c935fea
Author: Guillaume Nodet <[email protected]>
AuthorDate: Wed Mar 8 00:03:49 2023 +0100

    Provide distributions for both maven 3.9.x and 4.0.x (#796)
---
 daemon-m39/pom.xml                                 |  50 ++
 .../java/org/apache/maven/cli/DaemonMavenCli.java  | 490 ++++++-------
 .../apache/maven/project/SnapshotModelCache.java   |  15 +-
 .../maven/project/SnapshotModelCacheFactory.java   |  13 +-
 .../InvalidatingPluginDescriptorCache.java         |   0
 .../invalidating/InvalidatingPluginRealmCache.java |   6 +-
 .../InvalidatingRealmCacheEventSpy.java            |   0
 .../mvnd/execution/BuildResumptionAnalyzer.java    |  57 ++
 .../mvnd/execution/BuildResumptionData.java        |  62 ++
 .../execution/BuildResumptionDataRepository.java   |  74 ++
 .../BuildResumptionPersistenceException.java       |  49 ++
 .../execution/DefaultBuildResumptionAnalyzer.java  |  90 +++
 .../DefaultBuildResumptionDataRepository.java      | 155 ++++
 .../mvnd/plugin/CachingPluginVersionResolver.java  |  14 -
 .../mvnd/plugin/CliMavenPluginManager.java         | 779 +++++++++++++++++++++
 .../plugin/ValidatingConfigurationListener.java    |  82 +++
 daemon-m40/pom.xml                                 |  50 ++
 .../java/org/apache/maven/cli/DaemonMavenCli.java  |  10 +-
 .../apache/maven/project/SnapshotModelCache.java   |   6 +-
 .../maven/project/SnapshotModelCacheFactory.java   |   0
 .../org/apache/maven/settings/SettingsUtilsV4.java |   0
 .../InvalidatingPluginDescriptorCache.java         |   0
 .../invalidating/InvalidatingPluginRealmCache.java |   0
 .../InvalidatingRealmCacheEventSpy.java            |   0
 .../mvnd/plugin/CachingPluginVersionResolver.java  |   0
 daemon/pom.xml                                     |   2 +-
 .../main/java/org/apache/maven/cli/DaemonCli.java  |  37 +
 .../java/org/mvndaemon/mvnd/daemon/Server.java     |  10 +-
 {dist => dist-m39}/pom.xml                         |  10 +-
 .../src/main/provisio/maven-distro.xml             |  17 +-
 {dist => dist-m40}/pom.xml                         |  10 +-
 .../src/main/provisio/maven-distro.xml             |  15 +-
 dist/src/main/resources/platform-darwin-aarch64    |  13 +
 dist/src/main/resources/platform-darwin-amd64      |  13 +
 dist/src/main/resources/platform-linux-amd64       |  13 +
 dist/src/main/resources/platform-windows-amd64     |  13 +
 integration-tests/pom.xml                          |  91 ++-
 pom.xml                                            |  25 +-
 38 files changed, 1955 insertions(+), 316 deletions(-)

diff --git a/daemon-m39/pom.xml b/daemon-m39/pom.xml
new file mode 100644
index 00000000..62ec50a0
--- /dev/null
+++ b/daemon-m39/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2019 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.maven.daemon</groupId>
+    <artifactId>mvnd</artifactId>
+    <version>1.0.0-m5-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>mvnd-daemon-m39</artifactId>
+
+  <packaging>jar</packaging>
+  <name>Maven Daemon - Daemon 3.9.x specifics</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-core</artifactId>
+      <version>${maven3.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-embedder</artifactId>
+      <version>${maven3.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.daemon</groupId>
+      <artifactId>mvnd-daemon</artifactId>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java 
b/daemon-m39/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
similarity index 81%
copy from daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
copy to daemon-m39/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
index 940d6180..feac407e 100644
--- a/daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
+++ b/daemon-m39/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
@@ -23,6 +23,8 @@ import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.PrintStream;
 import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -34,7 +36,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.Set;
-import java.util.function.Consumer;
+import java.util.StringTokenizer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -44,7 +46,6 @@ import com.google.inject.AbstractModule;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.ParseException;
-import org.apache.commons.lang3.math.NumberUtils;
 import org.apache.maven.InternalErrorException;
 import org.apache.maven.Maven;
 import org.apache.maven.building.FileSource;
@@ -61,14 +62,17 @@ import 
org.apache.maven.eventspy.internal.EventSpyDispatcher;
 import org.apache.maven.exception.DefaultExceptionHandler;
 import org.apache.maven.exception.ExceptionHandler;
 import org.apache.maven.exception.ExceptionSummary;
-import org.apache.maven.execution.*;
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.execution.MavenExecutionRequestPopulationException;
+import org.apache.maven.execution.MavenExecutionRequestPopulator;
+import org.apache.maven.execution.MavenExecutionResult;
 import org.apache.maven.execution.scope.internal.MojoExecutionScopeModule;
 import org.apache.maven.extension.internal.CoreExports;
-import org.apache.maven.extension.internal.CoreExportsProvider;
 import org.apache.maven.extension.internal.CoreExtensionEntry;
 import org.apache.maven.lifecycle.LifecycleExecutionException;
 import org.apache.maven.model.building.ModelProcessor;
 import org.apache.maven.plugin.ExtensionRealmCache;
+import org.apache.maven.plugin.MavenPluginManager;
 import org.apache.maven.plugin.PluginArtifactsCache;
 import org.apache.maven.plugin.PluginRealmCache;
 import org.apache.maven.plugin.version.PluginVersionResolver;
@@ -98,11 +102,15 @@ import 
org.mvndaemon.mvnd.cache.invalidating.InvalidatingProjectArtifactsCache;
 import org.mvndaemon.mvnd.cli.EnvHelper;
 import org.mvndaemon.mvnd.common.Environment;
 import org.mvndaemon.mvnd.common.Os;
+import org.mvndaemon.mvnd.execution.BuildResumptionPersistenceException;
+import org.mvndaemon.mvnd.execution.DefaultBuildResumptionAnalyzer;
+import org.mvndaemon.mvnd.execution.DefaultBuildResumptionDataRepository;
 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.plugin.CliMavenPluginManager;
 import org.mvndaemon.mvnd.transfer.DaemonMavenTransferListener;
 import org.slf4j.ILoggerFactory;
 import org.slf4j.Logger;
@@ -119,7 +127,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";
@@ -141,6 +149,8 @@ public class DaemonMavenCli {
 
     public static final String STYLE_COLOR_PROPERTY = "style.color";
 
+    public static final String RESUME = "r";
+
     public static final String RAW_STREAMS = "raw-streams";
 
     private final Slf4jLoggerManager plexusLoggerManager;
@@ -169,9 +179,7 @@ public class DaemonMavenCli {
 
     private final LoggingExecutionListener executionListener;
 
-    /**
-     * Non-volatile, assuming that it is accessed only from the main thread
-     */
+    /** Non-volatile, assuming that it is accessed only from the main thread */
     private BuildEventListener buildEventListener = BuildEventListener.dummy();
 
     public DaemonMavenCli() throws Exception {
@@ -179,7 +187,8 @@ public class DaemonMavenCli {
         slf4jLogger = slf4jLoggerFactory.getLogger(this.getClass().getName());
         plexusLoggerManager = new Slf4jLoggerManager();
 
-        this.classWorld = ((ClassRealm) 
Thread.currentThread().getContextClassLoader()).getWorld();
+        ClassLoader cl = Thread.currentThread().getContextClassLoader();
+        classWorld = new ClassWorld("plexus.core", cl);
 
         container = container();
 
@@ -303,6 +312,11 @@ public class DaemonMavenCli {
 
     private CLIManager newCLIManager() {
         CLIManager cliManager = new CLIManager();
+        cliManager.options.addOption(Option.builder(RESUME)
+                .longOpt("resume")
+                .desc("Resume reactor from "
+                        + "the last failed project, using the 
resume.properties file in the build directory")
+                .build());
         cliManager.options.addOption(Option.builder()
                 .longOpt(RAW_STREAMS)
                 .desc("Do not decorate output and error streams")
@@ -345,12 +359,12 @@ public class DaemonMavenCli {
      */
     void logging(CliRequest cliRequest) {
         // LOG LEVEL
-        cliRequest.verbose = 
cliRequest.commandLine.hasOption(CLIManager.VERBOSE);
-        cliRequest.quiet = !cliRequest.verbose && 
cliRequest.commandLine.hasOption(CLIManager.QUIET);
-        cliRequest.showErrors = cliRequest.verbose || 
cliRequest.commandLine.hasOption(CLIManager.ERRORS);
+        cliRequest.debug = cliRequest.commandLine.hasOption(CLIManager.DEBUG);
+        cliRequest.quiet = !cliRequest.debug && 
cliRequest.commandLine.hasOption(CLIManager.QUIET);
+        cliRequest.showErrors = cliRequest.debug || 
cliRequest.commandLine.hasOption(CLIManager.ERRORS);
 
         ch.qos.logback.classic.Level level;
-        if (cliRequest.verbose) {
+        if (cliRequest.debug) {
             level = ch.qos.logback.classic.Level.DEBUG;
         } else if (cliRequest.quiet) {
             level = ch.qos.logback.classic.Level.WARN;
@@ -409,7 +423,7 @@ public class DaemonMavenCli {
     }
 
     private void version(CliRequest cliRequest) throws ExitException {
-        if (cliRequest.verbose || 
cliRequest.commandLine.hasOption(CLIManager.VERSION)) {
+        if (cliRequest.debug || 
cliRequest.commandLine.hasOption(CLIManager.VERSION)) {
             buildEventListener.log(CLIReportingUtils.showVersion());
             if (cliRequest.commandLine.hasOption(CLIManager.VERSION)) {
                 throw new ExitException(0);
@@ -472,7 +486,6 @@ public class DaemonMavenCli {
 
         List<File> extClassPath = Stream.of(
                         Environment.MVND_EXT_CLASSPATH.asString().split(","))
-                .filter(s -> s != null && !s.isEmpty())
                 .map(File::new)
                 .collect(Collectors.toList());
 
@@ -519,11 +532,11 @@ public class DaemonMavenCli {
             protected void configure() {
                 bind(ILoggerFactory.class).toInstance(slf4jLoggerFactory);
                 bind(CoreExports.class).toInstance(exports);
-                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(ProjectArtifactsCache.class).to(InvalidatingProjectArtifactsCache.class);
+                bind(MavenPluginManager.class).to(CliMavenPluginManager.class);
                 
bind(PluginVersionResolver.class).to(CachingPluginVersionResolver.class);
             }
         });
@@ -581,6 +594,7 @@ public class DaemonMavenCli {
                 populateRequest(
                         cliRequest,
                         cliRequest.request,
+                        slf4jLogger,
                         eventSpyDispatcher,
                         container.lookup(ModelProcessor.class),
                         createTransferListener(cliRequest),
@@ -739,7 +753,22 @@ public class DaemonMavenCli {
                 }
             }
 
-            if (result.canResume()) {
+            boolean canResume = new DefaultBuildResumptionAnalyzer()
+                    .determineBuildResumptionData(result)
+                    .map(resumption -> {
+                        try {
+                            Path directory =
+                                    
Paths.get(request.getBaseDirectory()).resolve("target");
+                            new 
DefaultBuildResumptionDataRepository().persistResumptionData(directory, 
resumption);
+                            return true;
+                        } catch (BuildResumptionPersistenceException e) {
+                            slf4jLogger.warn("Could not persist build 
resumption data", e);
+                        }
+                        return false;
+                    })
+                    .orElse(false);
+
+            if (canResume) {
                 logBuildResumeHint("mvn <args> -r");
             } else if (!failedProjects.isEmpty()) {
                 List<MavenProject> sortedProjects = 
result.getTopologicallySortedProjects();
@@ -762,6 +791,8 @@ public class DaemonMavenCli {
                 return 1;
             }
         } else {
+            Path directory = 
Paths.get(request.getBaseDirectory()).resolve("target");
+            new 
DefaultBuildResumptionDataRepository().removeResumptionData(directory);
             return 0;
         }
     }
@@ -1000,6 +1031,7 @@ public class DaemonMavenCli {
         populateRequest(
                 cliRequest,
                 cliRequest.request,
+                slf4jLogger,
                 eventSpyDispatcher,
                 modelProcessor,
                 createTransferListener(cliRequest),
@@ -1007,9 +1039,10 @@ public class DaemonMavenCli {
                 executionListener);
     }
 
-    private void populateRequest(
+    private static void populateRequest(
             CliRequest cliRequest,
             MavenExecutionRequest request,
+            Logger slf4jLogger,
             EventSpyDispatcher eventSpyDispatcher,
             ModelProcessor modelProcessor,
             TransferListener transferListener,
@@ -1017,283 +1050,244 @@ public class DaemonMavenCli {
             LoggingExecutionListener executionListener) {
         CommandLine commandLine = cliRequest.commandLine;
         String workingDirectory = cliRequest.workingDirectory;
-        boolean quiet = cliRequest.quiet;
-        boolean verbose = cliRequest.verbose;
-        request.setShowErrors(cliRequest.showErrors); // default: false
-        File baseDirectory = new File(workingDirectory, "").getAbsoluteFile();
+        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);
+            }
+        }
 
-        disableOnPresentOption(commandLine, CLIManager.BATCH_MODE, 
request::setInteractiveMode);
-        enableOnPresentOption(commandLine, 
CLIManager.SUPPRESS_SNAPSHOT_UPDATES, request::setNoSnapshotUpdates);
-        request.setGoals(commandLine.getArgList());
-        
request.setReactorFailureBehavior(determineReactorFailureBehaviour(commandLine));
-        disableOnPresentOption(commandLine, CLIManager.NON_RECURSIVE, 
request::setRecursive);
-        enableOnPresentOption(commandLine, CLIManager.OFFLINE, 
request::setOffline);
-        enableOnPresentOption(commandLine, CLIManager.UPDATE_SNAPSHOTS, 
request::setUpdateSnapshots);
-        
request.setGlobalChecksumPolicy(determineGlobalCheckPolicy(commandLine));
-        request.setBaseDirectory(baseDirectory);
-        request.setSystemProperties(cliRequest.systemProperties);
-        request.setUserProperties(cliRequest.userProperties);
-        
request.setMultiModuleProjectDirectory(cliRequest.multiModuleProjectDirectory);
-        request.setPom(determinePom(modelProcessor, commandLine, 
workingDirectory, baseDirectory));
-        request.setTransferListener(transferListener);
-        request.setExecutionListener(executionListener);
+        // 
----------------------------------------------------------------------
+        // Now that we have everything that we need we will fire up plexus and
+        // bring the maven component to life for use.
+        // 
----------------------------------------------------------------------
 
-        ExecutionEventLogger executionEventLogger = new ExecutionEventLogger();
-        
executionListener.init(eventSpyDispatcher.chainListener(executionEventLogger), 
buildEventListener);
+        if (commandLine.hasOption(CLIManager.BATCH_MODE)) {
+            request.setInteractiveMode(false);
+        }
 
-        if ((request.getPom() != null) && (request.getPom().getParentFile() != 
null)) {
-            request.setBaseDirectory(request.getPom().getParentFile());
+        boolean noSnapshotUpdates = false;
+        if (commandLine.hasOption(CLIManager.SUPRESS_SNAPSHOT_UPDATES)) {
+            noSnapshotUpdates = true;
         }
 
-        
request.setResumeFrom(commandLine.getOptionValue(CLIManager.RESUME_FROM));
-        enableOnPresentOption(commandLine, CLIManager.RESUME, 
request::setResume);
-        request.setMakeBehavior(determineMakeBehavior(commandLine));
-        request.setCacheNotFound(true);
-        request.setCacheTransferError(false);
+        // 
----------------------------------------------------------------------
+        //
+        // 
----------------------------------------------------------------------
+
+        List<String> goals = commandLine.getArgList();
+
+        boolean recursive = true;
 
-        performProjectActivation(commandLine, request.getProjectActivation());
-        performProfileActivation(commandLine, request.getProfileActivation());
+        // this is the default behavior.
+        String reactorFailureBehaviour = 
MavenExecutionRequest.REACTOR_FAIL_FAST;
 
-        final String localRepositoryPath = 
determineLocalRepositoryPath(request);
-        if (localRepositoryPath != null) {
-            request.setLocalRepositoryPath(localRepositoryPath);
+        if (commandLine.hasOption(CLIManager.NON_RECURSIVE)) {
+            recursive = 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.getOptionValue(CLIManager.THREADS);
+        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 (threadConfiguration != null) {
-            int degreeOfConcurrency = 
calculateDegreeOfConcurrency(threadConfiguration);
-            if (degreeOfConcurrency > 1) {
-                request.setBuilderId("multithreaded");
-                request.setDegreeOfConcurrency(degreeOfConcurrency);
-            }
+        if (commandLine.hasOption(CLIManager.OFFLINE)) {
+            request.setOffline(true);
         }
 
-        //
-        // Allow the builder to be overridden by the user if requested. The 
builders are now pluggable.
-        //
-        request.setBuilderId(commandLine.getOptionValue(CLIManager.BUILDER, 
request.getBuilderId()));
-    }
+        boolean updateSnapshots = false;
 
-    private String determineLocalRepositoryPath(final MavenExecutionRequest 
request) {
-        String userDefinedLocalRepo = 
request.getUserProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY);
-        if (userDefinedLocalRepo != null) {
-            return userDefinedLocalRepo;
+        if (commandLine.hasOption(CLIManager.UPDATE_SNAPSHOTS)) {
+            updateSnapshots = true;
         }
 
-        return 
request.getSystemProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY);
-    }
+        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<String> activeProfiles = new ArrayList<>();
+
+        List<String> 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);
 
-    private File determinePom(
-            ModelProcessor modelProcessor,
-            final CommandLine commandLine,
-            final String workingDirectory,
-            final File baseDirectory) {
         String alternatePomFile = null;
         if (commandLine.hasOption(CLIManager.ALTERNATE_POM_FILE)) {
             alternatePomFile = 
commandLine.getOptionValue(CLIManager.ALTERNATE_POM_FILE);
         }
 
+        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");
             }
 
-            return pom;
+            request.setPom(pom);
         } else if (modelProcessor != null) {
             File pom = modelProcessor.locatePom(baseDirectory);
 
             if (pom.isFile()) {
-                return pom;
+                request.setPom(pom);
             }
         }
 
-        return null;
-    }
-
-    // Visible for testing
-    static void performProjectActivation(final CommandLine commandLine, final 
ProjectActivation projectActivation) {
-        if (commandLine.hasOption(CLIManager.PROJECT_LIST)) {
-            final String[] optionValues = 
commandLine.getOptionValues(CLIManager.PROJECT_LIST);
-
-            if (optionValues == null || optionValues.length == 0) {
-                return;
-            }
-
-            for (final String optionValue : optionValues) {
-                for (String token : optionValue.split(",")) {
-                    String selector = token.trim();
-                    boolean active = true;
-                    if (selector.charAt(0) == '-' || selector.charAt(0) == 
'!') {
-                        active = false;
-                        selector = selector.substring(1);
-                    } else if (token.charAt(0) == '+') {
-                        selector = selector.substring(1);
-                    }
+        if ((request.getPom() != null) && (request.getPom().getParentFile() != 
null)) {
+            request.setBaseDirectory(request.getPom().getParentFile());
+        }
 
-                    boolean optional = selector.charAt(0) == '?';
-                    selector = selector.substring(optional ? 1 : 0);
+        if (commandLine.hasOption(RESUME)) {
+            new DefaultBuildResumptionDataRepository()
+                    .applyResumptionData(
+                            request, 
Paths.get(request.getBaseDirectory()).resolve("target"));
+        }
 
-                    projectActivation.addProjectActivation(selector, active, 
optional);
-                }
-            }
+        if (commandLine.hasOption(CLIManager.RESUME_FROM)) {
+            
request.setResumeFrom(commandLine.getOptionValue(CLIManager.RESUME_FROM));
         }
-    }
 
-    // Visible for testing
-    static void performProfileActivation(final CommandLine commandLine, final 
ProfileActivation profileActivation) {
-        if (commandLine.hasOption(CLIManager.ACTIVATE_PROFILES)) {
-            final String[] optionValues = 
commandLine.getOptionValues(CLIManager.ACTIVATE_PROFILES);
+        if (commandLine.hasOption(CLIManager.PROJECT_LIST)) {
+            String[] projectOptionValues = 
commandLine.getOptionValues(CLIManager.PROJECT_LIST);
 
-            if (optionValues == null || optionValues.length == 0) {
-                return;
-            }
+            List<String> inclProjects = new ArrayList<>();
+            List<String> exclProjects = new ArrayList<>();
 
-            for (final String optionValue : optionValues) {
-                for (String token : optionValue.split(",")) {
-                    String profileId = token.trim();
-                    boolean active = true;
-                    if (profileId.charAt(0) == '-' || profileId.charAt(0) == 
'!') {
-                        active = false;
-                        profileId = profileId.substring(1);
-                    } else if (token.charAt(0) == '+') {
-                        profileId = profileId.substring(1);
-                    }
+            if (projectOptionValues != null) {
+                for (String projectOptionValue : projectOptionValues) {
+                    StringTokenizer projectTokens = new 
StringTokenizer(projectOptionValue, ",");
 
-                    boolean optional = profileId.charAt(0) == '?';
-                    profileId = profileId.substring(optional ? 1 : 0);
+                    while (projectTokens.hasMoreTokens()) {
+                        String projectAction = 
projectTokens.nextToken().trim();
 
-                    profileActivation.addProfileActivation(profileId, active, 
optional);
+                        if (projectAction.startsWith("-") || 
projectAction.startsWith("!")) {
+                            exclProjects.add(projectAction.substring(1));
+                        } else if (projectAction.startsWith("+")) {
+                            inclProjects.add(projectAction.substring(1));
+                        } else {
+                            inclProjects.add(projectAction);
+                        }
+                    }
                 }
             }
-        }
-    }
-
-    private ExecutionListener determineExecutionListener(EventSpyDispatcher 
eventSpyDispatcher) {
-        ExecutionListener executionListener = new ExecutionEventLogger();
-        if (eventSpyDispatcher != null) {
-            return eventSpyDispatcher.chainListener(executionListener);
-        } else {
-            return executionListener;
-        }
-    }
-
-    private String determineReactorFailureBehaviour(final CommandLine 
commandLine) {
-        if (commandLine.hasOption(CLIManager.FAIL_FAST)) {
-            return MavenExecutionRequest.REACTOR_FAIL_FAST;
-        } else if (commandLine.hasOption(CLIManager.FAIL_AT_END)) {
-            return MavenExecutionRequest.REACTOR_FAIL_AT_END;
-        } else if (commandLine.hasOption(CLIManager.FAIL_NEVER)) {
-            return MavenExecutionRequest.REACTOR_FAIL_NEVER;
-        } else {
-            // this is the default behavior.
-            return MavenExecutionRequest.REACTOR_FAIL_FAST;
-        }
-    }
 
-    private String determineMakeBehavior(final CommandLine cl) {
-        if (cl.hasOption(CLIManager.ALSO_MAKE) && 
!cl.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) {
-            return MavenExecutionRequest.REACTOR_MAKE_UPSTREAM;
-        } else if (!cl.hasOption(CLIManager.ALSO_MAKE) && 
cl.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) {
-            return MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM;
-        } else if (cl.hasOption(CLIManager.ALSO_MAKE) && 
cl.hasOption(CLIManager.ALSO_MAKE_DEPENDENTS)) {
-            return MavenExecutionRequest.REACTOR_MAKE_BOTH;
-        } else {
-            return null;
+            request.setSelectedProjects(inclProjects);
+            request.setExcludedProjects(exclProjects);
         }
-    }
 
-    private String determineGlobalCheckPolicy(final CommandLine commandLine) {
-        if (commandLine.hasOption(CLIManager.CHECKSUM_FAILURE_POLICY)) {
-            return MavenExecutionRequest.CHECKSUM_POLICY_FAIL;
-        } else if (commandLine.hasOption(CLIManager.CHECKSUM_WARNING_POLICY)) {
-            return MavenExecutionRequest.CHECKSUM_POLICY_WARN;
-        } else {
-            return null;
+        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);
         }
-    }
 
-    private void disableOnPresentOption(
-            final CommandLine commandLine, final String option, final 
Consumer<Boolean> setting) {
-        if (commandLine.hasOption(option)) {
-            setting.accept(false);
-        }
-    }
+        String localRepoProperty = 
request.getUserProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY);
 
-    private void disableOnPresentOption(
-            final CommandLine commandLine, final char option, final 
Consumer<Boolean> setting) {
-        disableOnPresentOption(commandLine, String.valueOf(option), setting);
-    }
-
-    private void enableOnPresentOption(
-            final CommandLine commandLine, final String option, final 
Consumer<Boolean> setting) {
-        if (commandLine.hasOption(option)) {
-            setting.accept(true);
+        if (localRepoProperty == null) {
+            localRepoProperty = 
request.getSystemProperties().getProperty(MavenCli.LOCAL_REPO_PROPERTY);
         }
-    }
-
-    private void enableOnPresentOption(
-            final CommandLine commandLine, final char option, final 
Consumer<Boolean> setting) {
-        enableOnPresentOption(commandLine, String.valueOf(option), setting);
-    }
 
-    private void enableOnAbsentOption(
-            final CommandLine commandLine, final char option, final 
Consumer<Boolean> setting) {
-        if (!commandLine.hasOption(option)) {
-            setting.accept(true);
+        if (localRepoProperty != null) {
+            request.setLocalRepositoryPath(localRepoProperty);
         }
-    }
-
-    int calculateDegreeOfConcurrency(String threadConfiguration) {
-        if (threadConfiguration.endsWith("C")) {
-            threadConfiguration = threadConfiguration.substring(0, 
threadConfiguration.length() - 1);
 
-            if (!NumberUtils.isParsable(threadConfiguration)) {
-                throw new IllegalArgumentException("Invalid threads core 
multiplier value: '" + threadConfiguration
-                        + "C'. Supported are int and float values ending with 
C.");
-            }
+        request.setCacheNotFound(true);
+        request.setCacheTransferError(false);
 
-            float coreMultiplier = Float.parseFloat(threadConfiguration);
+        //
+        // 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 (coreMultiplier <= 0.0f) {
-                throw new IllegalArgumentException("Invalid threads core 
multiplier value: '" + threadConfiguration
-                        + "C'. Value must be positive.");
-            }
+        if (threadConfiguration != null) {
+            //
+            // Default to the standard multithreaded builder
+            //
+            request.setBuilderId("multithreaded");
 
-            int procs = Runtime.getRuntime().availableProcessors();
-            int threads = (int) (coreMultiplier * procs);
-            return threads == 0 ? 1 : threads;
-        } else {
-            if (!NumberUtils.isParsable(threadConfiguration)) {
-                throw new IllegalArgumentException(
-                        "Invalid threads value: '" + threadConfiguration + "'. 
Supported are int values.");
+            if (threadConfiguration.contains("C")) {
+                
request.setDegreeOfConcurrency(calculateDegreeOfConcurrencyWithCoreMultiplier(threadConfiguration));
+            } else {
+                
request.setDegreeOfConcurrency(Integer.parseInt(threadConfiguration));
             }
+        }
 
-            try {
-                int threads = Integer.parseInt(threadConfiguration);
-
-                if (threads <= 0) {
-                    throw new IllegalArgumentException(
-                            "Invalid threads value: '" + threadConfiguration + 
"'. Value must be positive.");
-                }
-
-                return threads;
-            } catch (NumberFormatException e) {
-                throw new IllegalArgumentException(
-                        "Invalid threads value: '" + threadConfiguration + "'. 
Supported are integer values.");
-            }
+        //
+        // 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;
@@ -1320,9 +1314,15 @@ public class DaemonMavenCli {
         // are most dominant.
         // 
----------------------------------------------------------------------
 
-        final Properties userSpecifiedProperties =
-                
commandLine.getOptionProperties(String.valueOf(CLIManager.SET_SYSTEM_PROPERTY));
-        userSpecifiedProperties.forEach((prop, value) -> 
setCliProperty((String) prop, (String) value, userProperties));
+        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);
 
@@ -1351,7 +1351,23 @@ public class DaemonMavenCli {
         }
     }
 
-    private static void setCliProperty(String name, String value, Properties 
properties) {
+    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);
 
         // 
----------------------------------------------------------------------
diff --git 
a/daemon/src/main/java/org/apache/maven/project/SnapshotModelCache.java 
b/daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCache.java
similarity index 83%
copy from daemon/src/main/java/org/apache/maven/project/SnapshotModelCache.java
copy to 
daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCache.java
index 39ff8b04..8d7d20ea 100644
--- a/daemon/src/main/java/org/apache/maven/project/SnapshotModelCache.java
+++ b/daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCache.java
@@ -18,7 +18,8 @@
  */
 package org.apache.maven.project;
 
-import org.apache.maven.building.Source;
+import java.util.Objects;
+
 import org.apache.maven.model.building.ModelCache;
 
 public class SnapshotModelCache implements ModelCache {
@@ -27,16 +28,8 @@ public class SnapshotModelCache implements ModelCache {
     private final ModelCache reactorCache;
 
     public SnapshotModelCache(ModelCache globalCache, ModelCache reactorCache) 
{
-        this.globalCache = globalCache;
-        this.reactorCache = reactorCache;
-    }
-
-    public Object get(Source path, String tag) {
-        return reactorCache.get(path, tag);
-    }
-
-    public void put(Source path, String tag, Object data) {
-        reactorCache.put(path, tag, data);
+        this.globalCache = Objects.requireNonNull(globalCache);
+        this.reactorCache = Objects.requireNonNull(reactorCache);
     }
 
     @Override
diff --git 
a/daemon/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java 
b/daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java
similarity index 76%
copy from 
daemon/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java
copy to 
daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java
index fe285af6..08946257 100644
--- 
a/daemon/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java
+++ 
b/daemon-m39/src/main/java/org/apache/maven/project/SnapshotModelCacheFactory.java
@@ -25,12 +25,11 @@ 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;
 
-import static org.mvndaemon.mvnd.common.Environment.MVND_NO_MODEL_CACHE;
-
 @Singleton
 @Named
 @Priority(10)
@@ -42,15 +41,13 @@ public class SnapshotModelCacheFactory implements 
ModelCacheFactory {
     @Inject
     public SnapshotModelCacheFactory(DefaultModelCacheFactory factory) {
         this.factory = factory;
-        this.globalCache = factory.createCache(new 
DefaultRepositorySystemSession());
+        DefaultRepositorySystemSession session = new 
DefaultRepositorySystemSession();
+        session.setCache(new DefaultRepositoryCache());
+        this.globalCache = factory.createCache(session);
     }
 
     @Override
     public ModelCache createCache(RepositorySystemSession session) {
-        boolean noModelCache =
-                
Boolean.parseBoolean(MVND_NO_MODEL_CACHE.asOptional().orElse(MVND_NO_MODEL_CACHE.getDefault()));
-        ModelCache reactorCache = factory.createCache(session);
-        ModelCache globalCache = noModelCache ? reactorCache : 
this.globalCache;
-        return new SnapshotModelCache(globalCache, reactorCache);
+        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%
copy from 
daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java
copy to 
daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java
diff --git 
a/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
 
b/daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
similarity index 96%
copy from 
daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
copy to 
daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
index 805866e5..67796309 100644
--- 
a/daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
+++ 
b/daemon-m39/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginRealmCache.java
@@ -42,6 +42,11 @@ import org.mvndaemon.mvnd.cache.CacheFactory;
 @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;
@@ -80,7 +85,6 @@ public class InvalidatingPluginRealmCache extends 
DefaultPluginRealmCache {
         return r != null ? r.record : null;
     }
 
-    @Override
     public CacheRecord get(Key key, PluginRealmSupplier supplier)
             throws PluginResolutionException, PluginContainerException {
         try {
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%
copy from 
daemon/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java
copy 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<BuildResumptionData> 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<String> remainingProjects;
+
+    public BuildResumptionData(final List<String> 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<String> 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 <code>false</code> 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<BuildResumptionData> determineBuildResumptionData(final 
MavenExecutionResult result) {
+        if (!result.hasExceptions()) {
+            return Optional.empty();
+        }
+
+        List<MavenProject> sortedProjects = 
result.getTopologicallySortedProjects();
+
+        boolean hasNoSuccess =
+                sortedProjects.stream().noneMatch(project -> 
result.getBuildSummary(project) instanceof BuildSuccess);
+
+        if (hasNoSuccess) {
+            return Optional.empty();
+        }
+
+        List<String> 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/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
 
b/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
similarity index 84%
copy from 
daemon/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
copy to 
daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
index 13745e8a..19c73f9b 100644
--- 
a/daemon/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
+++ 
b/daemon-m39/src/main/java/org/mvndaemon/mvnd/plugin/CachingPluginVersionResolver.java
@@ -18,7 +18,6 @@
  */
 package org.mvndaemon.mvnd.plugin;
 
-import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
 
@@ -27,17 +26,13 @@ import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import org.apache.maven.artifact.repository.metadata.io.MetadataReader;
-import org.apache.maven.plugin.MavenPluginManager;
 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.RepositorySystem;
 import org.eclipse.aether.SessionData;
 import org.eclipse.aether.repository.RemoteRepository;
-import org.eclipse.aether.version.VersionScheme;
 import org.eclipse.sisu.Priority;
 import org.eclipse.sisu.Typed;
 
@@ -49,15 +44,6 @@ public class CachingPluginVersionResolver extends 
DefaultPluginVersionResolver {
 
     private static final Object CACHE_KEY = new Object();
 
-    @Inject
-    public CachingPluginVersionResolver(
-            RepositorySystem repositorySystem,
-            MetadataReader metadataReader,
-            MavenPluginManager pluginManager,
-            VersionScheme versionScheme) {
-        super(repositorySystem, metadataReader, pluginManager, versionScheme);
-    }
-
     @Override
     public PluginVersionResult resolve(PluginVersionRequest request) throws 
PluginVersionResolutionException {
         Map<String, PluginVersionResult> cache =
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 {
+
+    /**
+     * <p>
+     * PluginId =&gt; 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.
+     * </p>
+     * <strong>Note:</strong> 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<RemoteRepository> 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<RemoteRepository> 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<String> 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<Artifact> pluginArtifacts = extensionRecord.getArtifacts();
+
+            for (ComponentDescriptor<?> componentDescriptor : 
pluginDescriptor.getComponents()) {
+                componentDescriptor.setRealm(pluginRealm);
+            }
+
+            pluginDescriptor.setClassRealm(pluginRealm);
+            pluginDescriptor.setArtifacts(pluginArtifacts);
+        } else {
+            Map<String, ClassLoader> 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<String, ClassLoader> 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<Artifact> 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<org.eclipse.aether.artifact.Artifact> toAetherArtifacts(final 
List<Artifact> pluginArtifacts) {
+        return new ArrayList<>(RepositoryUtils.toArtifacts(pluginArtifacts));
+    }
+
+    private List<Artifact> toMavenArtifacts(DependencyNode root, 
PreorderNodeListGenerator nlg) {
+        List<Artifact> artifacts = new ArrayList<>(nlg.getNodes().size());
+        RepositoryUtils.toArtifacts(artifacts, Collections.singleton(root), 
Collections.<String>emptyList(), null);
+        for (Iterator<Artifact> it = artifacts.iterator(); it.hasNext(); ) {
+            Artifact artifact = it.next();
+            if (artifact.getFile() == null) {
+                it.remove();
+            }
+        }
+        return Collections.unmodifiableList(artifacts);
+    }
+
+    private Map<String, ClassLoader> calcImports(MavenProject project, 
ClassLoader parent, List<String> imports) {
+        Map<String, ClassLoader> 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> T getConfiguredMojo(Class<T> 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<String, Object> 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<Parameter> 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<Parameter> 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<String, ExtensionRealmCache.CacheRecord> pluginRealms =
+                (Map<String, ExtensionRealmCache.CacheRecord>) 
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<RemoteRepository> 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<Artifact> 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<Artifact> resolveExtensionArtifacts(
+            Plugin extensionPlugin, List<RemoteRepository> 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<String, Parameter> 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<Parameter> 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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright 2019 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.maven.daemon</groupId>
+    <artifactId>mvnd</artifactId>
+    <version>1.0.0-m5-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>mvnd-daemon-m40</artifactId>
+
+  <packaging>jar</packaging>
+  <name>Maven Daemon - Daemon 4.0.x specifics</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-core</artifactId>
+      <version>${maven4.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven</groupId>
+      <artifactId>maven-embedder</artifactId>
+      <version>${maven4.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.daemon</groupId>
+      <artifactId>mvnd-daemon</artifactId>
+    </dependency>
+  </dependencies>
+
+</project>
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/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java
 
b/daemon-m40/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-m40/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingPluginDescriptorCache.java
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/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java
 
b/daemon-m40/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-m40/src/main/java/org/mvndaemon/mvnd/cache/invalidating/InvalidatingRealmCacheEventSpy.java
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 @@
   <artifactId>mvnd-daemon</artifactId>
 
   <packaging>jar</packaging>
-  <name>Maven Daemon</name>
+  <name>Maven Daemon - Daemon</name>
 
   <dependencies>
     <dependency>
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<String> args,
+            String workingDir,
+            String projectDir,
+            Map<String, String> 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%
copy from dist/pom.xml
copy to dist-m39/pom.xml
index 46aeb88e..464fc7e9 100644
--- a/dist/pom.xml
+++ b/dist-m39/pom.xml
@@ -25,10 +25,10 @@
     <version>1.0.0-m5-SNAPSHOT</version>
   </parent>
 
-  <artifactId>mvnd-dist</artifactId>
+  <artifactId>mvnd-dist-m39</artifactId>
 
   <packaging>pom</packaging>
-  <name>Maven Daemon - Distribution</name>
+  <name>Maven Daemon - Distribution for 3.9.x</name>
 
   <properties>
     <maven.compiler.target>11</maven.compiler.target>
@@ -52,6 +52,10 @@
       <groupId>org.apache.maven.daemon</groupId>
       <artifactId>mvnd-daemon</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.maven.daemon</groupId>
+      <artifactId>mvnd-daemon-m39</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
@@ -67,7 +71,7 @@
             </goals>
             <phase>package</phase>
             <configuration>
-              
<outputDirectory>${project.build.directory}/maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}</outputDirectory>
+              
<outputDirectory>${project.build.directory}/maven-${project.version}-mvnd-m39-${os.detected.name}-${os.detected.arch}</outputDirectory>
             </configuration>
           </execution>
         </executions>
diff --git a/dist/src/main/provisio/maven-distro.xml 
b/dist-m39/src/main/provisio/maven-distro.xml
similarity index 80%
copy from dist/src/main/provisio/maven-distro.xml
copy to dist-m39/src/main/provisio/maven-distro.xml
index 93647c4b..db3c73ff 100644
--- a/dist/src/main/provisio/maven-distro.xml
+++ b/dist-m39/src/main/provisio/maven-distro.xml
@@ -18,9 +18,9 @@
 <assembly>
 
     <artifactSet to="/">
-        <artifact id="org.apache.maven:apache-maven:tar.gz:bin">
+        <artifact 
id="org.apache.maven:apache-maven:tar.gz:bin:${maven3.version}">
             <unpack useRoot="false"
-                    
excludes="conf/logging/*,lib/maven-slf4j-provider*,lib/plexus-utils-3.*" />
+                    excludes="conf/logging/*,lib/maven-slf4j-provider*" />
         </artifact>
     </artifactSet>
 
@@ -37,6 +37,9 @@
         <artifact id="org.apache.maven.daemon:mvnd-daemon:${project.version}">
             <exclusion id="*:*"/>
         </artifact>
+        <artifact 
id="org.apache.maven.daemon:mvnd-daemon-m39:${project.version}">
+            <exclusion id="*:*"/>
+        </artifact>
         <artifact id="org.apache.maven.daemon:mvnd-client:${project.version}">
             <exclusion id="*:*"/>
         </artifact>
@@ -64,7 +67,7 @@
     </artifactSet>
 
     <fileSet to="/">
-        <directory path="${basedir}/src/main/distro"/>
+        <directory path="${basedir}/../dist/src/main/distro"/>
         <directory path="${basedir}/..">
             <include>NOTICE.txt</include>
             <include>LICENSE.txt</include>
@@ -76,13 +79,15 @@
             <include>mvnd</include>
             <include>mvnd.exe</include>
         </directory>
-        <file touch="platform-${os.detected.name}-${os.detected.arch}"/>
+        <directory path="${basedir}/../dist/src/main/resources">
+            <include>platform-${os.detected.name}-${os.detected.arch}</include>
+        </directory>
     </fileSet>
 
-    <archive 
name="maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}.zip"
+    <archive 
name="maven-${project.version}-mvnd-mvn39-${os.detected.name}-${os.detected.arch}.zip"
              executable="**/bin/mvnd"/>
 
-    <archive 
name="maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}.tar.gz"
+    <archive 
name="maven-${project.version}-mvnd-mvn39-${os.detected.name}-${os.detected.arch}.tar.gz"
              executable="**/bin/mvnd"/>
 
 </assembly>
diff --git a/dist/pom.xml b/dist-m40/pom.xml
similarity index 88%
rename from dist/pom.xml
rename to dist-m40/pom.xml
index 46aeb88e..e9516be4 100644
--- a/dist/pom.xml
+++ b/dist-m40/pom.xml
@@ -25,10 +25,10 @@
     <version>1.0.0-m5-SNAPSHOT</version>
   </parent>
 
-  <artifactId>mvnd-dist</artifactId>
+  <artifactId>mvnd-dist-m40</artifactId>
 
   <packaging>pom</packaging>
-  <name>Maven Daemon - Distribution</name>
+  <name>Maven Daemon - Distribution for 4.0.x</name>
 
   <properties>
     <maven.compiler.target>11</maven.compiler.target>
@@ -52,6 +52,10 @@
       <groupId>org.apache.maven.daemon</groupId>
       <artifactId>mvnd-daemon</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apache.maven.daemon</groupId>
+      <artifactId>mvnd-daemon-m40</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
@@ -67,7 +71,7 @@
             </goals>
             <phase>package</phase>
             <configuration>
-              
<outputDirectory>${project.build.directory}/maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}</outputDirectory>
+              
<outputDirectory>${project.build.directory}/maven-${project.version}-mvnd-m40-${os.detected.name}-${os.detected.arch}</outputDirectory>
             </configuration>
           </execution>
         </executions>
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 @@
 <assembly>
 
     <artifactSet to="/">
-        <artifact id="org.apache.maven:apache-maven:tar.gz:bin">
+        <artifact 
id="org.apache.maven:apache-maven:tar.gz:bin:${maven4.version}">
             <unpack useRoot="false"
                     
excludes="conf/logging/*,lib/maven-slf4j-provider*,lib/plexus-utils-3.*" />
         </artifact>
@@ -37,6 +37,9 @@
         <artifact id="org.apache.maven.daemon:mvnd-daemon:${project.version}">
             <exclusion id="*:*"/>
         </artifact>
+        <artifact 
id="org.apache.maven.daemon:mvnd-daemon-m40:${project.version}">
+            <exclusion id="*:*"/>
+        </artifact>
         <artifact id="org.apache.maven.daemon:mvnd-client:${project.version}">
             <exclusion id="*:*"/>
         </artifact>
@@ -64,7 +67,7 @@
     </artifactSet>
 
     <fileSet to="/">
-        <directory path="${basedir}/src/main/distro"/>
+        <directory path="${basedir}/../dist/src/main/distro"/>
         <directory path="${basedir}/..">
             <include>NOTICE.txt</include>
             <include>LICENSE.txt</include>
@@ -76,13 +79,15 @@
             <include>mvnd</include>
             <include>mvnd.exe</include>
         </directory>
-        <file touch="platform-${os.detected.name}-${os.detected.arch}"/>
+        <directory path="${basedir}/../dist/src/main/resources">
+            <include>platform-${os.detected.name}-${os.detected.arch}</include>
+        </directory>
     </fileSet>
 
-    <archive 
name="maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}.zip"
+    <archive 
name="maven-${project.version}-mvnd-40-${os.detected.name}-${os.detected.arch}.zip"
              executable="**/bin/mvnd"/>
 
-    <archive 
name="maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}.tar.gz"
+    <archive 
name="maven-${project.version}-mvnd-40-${os.detected.name}-${os.detected.arch}.tar.gz"
              executable="**/bin/mvnd"/>
 
 </assembly>
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 @@
   <properties>
     <maven.compiler.target>11</maven.compiler.target>
     <maven.compiler.source>11</maven.compiler.source>
-    
<mvnd.home>${project.basedir}/../dist/target/maven-mvnd-${project.version}-${os.detected.name}-${os.detected.arch}</mvnd.home>
+    
<mvnd.m39.home>${project.basedir}/../dist-m39/target/maven-${project.version}-mvnd-m39-${os.detected.name}-${os.detected.arch}</mvnd.m39.home>
+    
<mvnd.m40.home>${project.basedir}/../dist-m40/target/maven-${project.version}-mvnd-m40-${os.detected.name}-${os.detected.arch}</mvnd.m40.home>
     
<preinstall.artifacts>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 @@
     </dependency>
     <dependency>
       <groupId>org.apache.maven.daemon</groupId>
-      <artifactId>mvnd-dist</artifactId>
+      <artifactId>mvnd-dist-m39</artifactId>
+      <type>pom</type>
+      <scope>test</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>org.slf4j</groupId>
+          <artifactId>slf4j-simple</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.daemon</groupId>
+      <artifactId>mvnd-dist-m40</artifactId>
       <type>pom</type>
       <scope>test</scope>
       <exclusions>
@@ -89,17 +102,50 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
-          <systemPropertyVariables>
-            <project.version>${project.version}</project.version>
-            <mvnd.home>${mvnd.home}</mvnd.home>
-            <mrm.repository.url>${mrm.repository.url}</mrm.repository.url>
-            <os.detected.name>${os.detected.name}</os.detected.name>
-            <os.detected.arch>${os.detected.arch}</os.detected.arch>
-            
<mvnd.test.hostLocalMavenRepo>${settings.localRepository}</mvnd.test.hostLocalMavenRepo>
-            
<preinstall.artifacts>${preinstall.artifacts}</preinstall.artifacts>
-          </systemPropertyVariables>
-          <rerunFailingTestsCount>2</rerunFailingTestsCount>
+          <rerunFailingTestsCount>4</rerunFailingTestsCount>
         </configuration>
+        <executions>
+          <execution>
+            <id>default-test</id>
+            <phase>none</phase>
+          </execution>
+          <execution>
+            <id>mvn-39</id>
+            <goals>
+              <goal>test</goal>
+            </goals>
+            <phase>test</phase>
+            <configuration>
+              <systemPropertyVariables>
+                <mvnd.home>${mvnd.m39.home}</mvnd.home>
+                <project.version>${project.version}</project.version>
+                <mrm.repository.url>${mrm.repository.url}</mrm.repository.url>
+                <os.detected.name>${os.detected.name}</os.detected.name>
+                <os.detected.arch>${os.detected.arch}</os.detected.arch>
+                
<mvnd.test.hostLocalMavenRepo>${settings.localRepository}</mvnd.test.hostLocalMavenRepo>
+                
<preinstall.artifacts>${preinstall.artifacts}</preinstall.artifacts>
+              </systemPropertyVariables>
+            </configuration>
+          </execution>
+          <execution>
+            <id>mvn-40</id>
+            <goals>
+              <goal>test</goal>
+            </goals>
+            <phase>test</phase>
+            <configuration>
+              <systemPropertyVariables>
+                <mvnd.home>${mvnd.m40.home}</mvnd.home>
+                <project.version>${project.version}</project.version>
+                <mrm.repository.url>${mrm.repository.url}</mrm.repository.url>
+                <os.detected.name>${os.detected.name}</os.detected.name>
+                <os.detected.arch>${os.detected.arch}</os.detected.arch>
+                
<mvnd.test.hostLocalMavenRepo>${settings.localRepository}</mvnd.test.hostLocalMavenRepo>
+                
<preinstall.artifacts>${preinstall.artifacts}</preinstall.artifacts>
+              </systemPropertyVariables>
+            </configuration>
+          </execution>
+        </executions>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
@@ -168,6 +214,25 @@
             <artifactId>maven-failsafe-plugin</artifactId>
             <executions>
               <execution>
+                <id>native-39</id>
+                <goals>
+                  <goal>integration-test</goal>
+                  <goal>verify</goal>
+                </goals>
+                <configuration>
+                  <systemPropertyVariables>
+                    <project.version>${project.version}</project.version>
+                    <mvnd.home>${mvnd.m39.home}</mvnd.home>
+                    
<mrm.repository.url>${mrm.repository.url}</mrm.repository.url>
+                    <os.detected.name>${os.detected.name}</os.detected.name>
+                    <os.detected.arch>${os.detected.arch}</os.detected.arch>
+                    
<mvnd.test.hostLocalMavenRepo>${settings.localRepository}</mvnd.test.hostLocalMavenRepo>
+                    
<preinstall.artifacts>${preinstall.artifacts}</preinstall.artifacts>
+                  </systemPropertyVariables>
+                </configuration>
+              </execution>
+              <execution>
+                <id>native-40</id>
                 <goals>
                   <goal>integration-test</goal>
                   <goal>verify</goal>
@@ -175,7 +240,7 @@
                 <configuration>
                   <systemPropertyVariables>
                     <project.version>${project.version}</project.version>
-                    <mvnd.home>${mvnd.home}</mvnd.home>
+                    <mvnd.home>${mvnd.m40.home}</mvnd.home>
                     
<mrm.repository.url>${mrm.repository.url}</mrm.repository.url>
                     <os.detected.name>${os.detected.name}</os.detected.name>
                     <os.detected.arch>${os.detected.arch}</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 @@
     <module>common</module>
     <module>client</module>
     <module>daemon</module>
-    <module>dist</module>
+    <module>daemon-m39</module>
+    <module>daemon-m40</module>
+    <module>dist-m39</module>
+    <module>dist-m40</module>
     <module>integration-tests</module>
   </modules>
 
@@ -85,6 +88,8 @@
     <junit.jupiter.version>5.9.2</junit.jupiter.version>
     <logback.version>1.2.11</logback.version>
     <maven.version>4.0.0-alpha-4</maven.version>
+    <maven3.version>3.9.0</maven3.version>
+    <maven4.version>${maven.version}</maven4.version>
     <!-- Keep in sync with Maven -->
     <maven.resolver.version>1.9.4</maven.resolver.version>
     <slf4j.version>1.7.36</slf4j.version>
@@ -251,7 +256,13 @@
       </dependency>
       <dependency>
         <groupId>org.apache.maven.daemon</groupId>
-        <artifactId>mvnd-dist</artifactId>
+        <artifactId>mvnd-dist-m39</artifactId>
+        <version>${project.version}</version>
+        <type>pom</type>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.maven.daemon</groupId>
+        <artifactId>mvnd-dist-m40</artifactId>
         <version>${project.version}</version>
         <type>pom</type>
       </dependency>
@@ -260,6 +271,16 @@
         <artifactId>mvnd-daemon</artifactId>
         <version>${project.version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.maven.daemon</groupId>
+        <artifactId>mvnd-daemon-m39</artifactId>
+        <version>${project.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.maven.daemon</groupId>
+        <artifactId>mvnd-daemon-m40</artifactId>
+        <version>${project.version}</version>
+      </dependency>
       <dependency>
         <groupId>org.apache.maven.daemon</groupId>
         <artifactId>mvnd-helper-agent</artifactId>


Reply via email to