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

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


The following commit(s) were added to refs/heads/master by this push:
     new 533790bb4a [MNG-8283] Maven CLIng smaller bugfixes and improvements 
(#1772)
533790bb4a is described below

commit 533790bb4a8156e6a83d8ec10770745f24533cc6
Author: Tamas Cservenak <ta...@cservenak.net>
AuthorDate: Fri Oct 4 14:23:10 2024 +0200

    [MNG-8283] Maven CLIng smaller bugfixes and improvements (#1772)
    
    This PR adopts CLIng for use in mvnd, and adds several improvements to 
CLIng overall.
    
    Major topics:
    * ability to pass in per-request Lookup for customization
    * makes parser request creation a bit friendlier
    * removes a log of redundancy (same stuff copied over)
    * ability to alter rootDirectory detection in parsers
    * resident invoker bugfix
    * adds UTs for 3 invoker implementations
    
    ---
    
    https://issues.apache.org/jira/browse/MNG-8283
---
 .../org/apache/maven/api/cli/InvokerRequest.java   |  48 +++---
 .../java/org/apache/maven/api/cli/Options.java     |   4 +-
 .../main/java/org/apache/maven/api/cli/Parser.java |  15 +-
 .../org/apache/maven/api/cli/ParserRequest.java    | 188 +++++++++++++++++++--
 .../java/org/apache/maven/cling/ClingSupport.java  |  29 +---
 .../java/org/apache/maven/cling/MavenCling.java    |  22 +--
 .../java/org/apache/maven/cling/MavenEncCling.java |  25 +--
 .../maven/cling/invoker/BaseInvokerRequest.java    |  29 +---
 .../org/apache/maven/cling/invoker/BaseParser.java | 116 +++++++------
 .../maven/cling/invoker/CommonsCliOptions.java     |  12 +-
 .../apache/maven/cling/invoker/LayeredOptions.java |   7 +-
 .../apache/maven/cling/invoker/LookupInvoker.java  |  41 +++--
 .../cling/invoker/mvn/DefaultMavenInvoker.java     | 175 ++++++++++---------
 .../invoker/mvn/DefaultMavenInvokerRequest.java    |  11 +-
 .../cling/invoker/mvn/DefaultMavenParser.java      |  10 +-
 .../mvn/forked/DefaultForkedMavenInvoker.java      | 142 +++++++++++++---
 .../forked/DefaultForkedMavenInvokerRequest.java   |  13 +-
 .../mvn/forked/DefaultForkedMavenParser.java       |  11 +-
 .../invoker/mvn/local/DefaultLocalMavenParser.java |   8 +-
 .../mvn/resident/DefaultResidentMavenInvoker.java  |   6 +-
 .../DefaultResidentMavenInvokerRequest.java        |  11 +-
 .../mvn/resident/DefaultResidentMavenParser.java   |   8 +-
 .../mvnenc/DefaultEncryptInvokerRequest.java       |  11 +-
 .../cling/invoker/mvnenc/DefaultEncryptParser.java |  10 +-
 .../forked/DefaultForkedMavenInvokerTest.java      |  50 ------
 .../cling/invoker/mvn/MavenInvokerTestSupport.java | 117 +++++++++++++
 .../mvn/forked/DefaultForkedMavenInvokerTest.java  |  52 ++++++
 .../mvn/local/DefaultLocalMavenInvokerTest.java}   |  64 ++++---
 .../resident/DefaultResidentMavenInvokerTest.java  | 110 ++++--------
 29 files changed, 818 insertions(+), 527 deletions(-)

diff --git 
a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java 
b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java
index e3f2a9f7df..34418a5fcc 100644
--- 
a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java
+++ 
b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java
@@ -26,8 +26,10 @@ import java.util.Map;
 import java.util.Optional;
 
 import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Immutable;
 import org.apache.maven.api.annotations.Nonnull;
 import org.apache.maven.api.cli.extensions.CoreExtension;
+import org.apache.maven.api.services.Lookup;
 import org.apache.maven.api.services.MessageBuilderFactory;
 
 /**
@@ -38,15 +40,35 @@ import org.apache.maven.api.services.MessageBuilderFactory;
  *
  * @since 4.0.0
  */
+@Immutable
 @Experimental
 public interface InvokerRequest<O extends Options> {
     /**
-     * Returns the command to be executed.
-     *
-     * @return the command string
+     * The parser request this instance was created from.
      */
     @Nonnull
-    String command();
+    ParserRequest parserRequest();
+
+    /**
+     * Shorthand for {@link Logger} to use.
+     */
+    default Logger logger() {
+        return parserRequest().logger();
+    }
+
+    /**
+     * Shorthand for {@link MessageBuilderFactory}.
+     */
+    default MessageBuilderFactory messageBuilderFactory() {
+        return parserRequest().messageBuilderFactory();
+    }
+
+    /**
+     * Shorthand for {@link Lookup}.
+     */
+    default Lookup lookup() {
+        return parserRequest().lookup();
+    }
 
     /**
      * Returns the current working directory for the Maven execution.
@@ -93,24 +115,6 @@ public interface InvokerRequest<O extends Options> {
     @Nonnull
     Map<String, String> systemProperties();
 
-    /**
-     * Returns the logger to be used during the early phases of Maven 
execution,
-     * before the main Maven logger is initialized.
-     *
-     * @return the early-phase logger
-     */
-    @Nonnull
-    Logger logger();
-
-    /**
-     * Returns the factory for creating message builders.
-     * Message builders are used for constructing formatted log messages.
-     *
-     * @return the message builder factory
-     */
-    @Nonnull
-    MessageBuilderFactory messageBuilderFactory();
-
     /**
      * Returns the top-level directory of the Maven invocation.
      * This is typically the directory containing the POM file being executed.
diff --git 
a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Options.java 
b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Options.java
index 7209cec47d..5a486bf215 100644
--- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Options.java
+++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Options.java
@@ -196,12 +196,12 @@ public interface Options {
      *
      * @param printWriter the PrintWriter to use for output
      */
-    default void warnAboutDeprecatedOptions(@Nonnull PrintWriter printWriter) 
{}
+    default void warnAboutDeprecatedOptions(@Nonnull ParserRequest request, 
@Nonnull PrintWriter printWriter) {}
 
     /**
      * Displays help information for these options.
      *
      * @param printWriter the PrintWriter to use for output
      */
-    void displayHelp(@Nonnull String command, @Nonnull PrintWriter 
printWriter);
+    void displayHelp(@Nonnull ParserRequest request, @Nonnull PrintWriter 
printWriter);
 }
diff --git 
a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Parser.java 
b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Parser.java
index 182cb298e4..a47a76f4e7 100644
--- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Parser.java
+++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Parser.java
@@ -36,10 +36,10 @@ import org.apache.maven.api.services.MessageBuilderFactory;
 @Experimental
 public interface Parser<R extends InvokerRequest<? extends Options>> {
     /**
-     * Parses the given command and arguments to create an InvokerRequest.
-     * This is a convenience method that internally creates a ParserRequest.
+     * Parses the given Maven arguments to create an InvokerRequest.
+     * This is a convenience method that internally creates a ParserRequest 
using
+     * {@link ParserRequest#mvn(String[], Logger, MessageBuilderFactory)}.
      *
-     * @param command the Maven command to execute
      * @param args the command-line arguments
      * @param logger the logger to use during parsing
      * @param messageBuilderFactory the factory for creating message builders
@@ -48,14 +48,9 @@ public interface Parser<R extends InvokerRequest<? extends 
Options>> {
      * @throws IOException if there's an I/O error during the parsing process
      */
     @Nonnull
-    default R parse(
-            @Nonnull String command,
-            @Nonnull String[] args,
-            @Nonnull Logger logger,
-            @Nonnull MessageBuilderFactory messageBuilderFactory)
+    default R mvn(@Nonnull String[] args, @Nonnull Logger logger, @Nonnull 
MessageBuilderFactory messageBuilderFactory)
             throws ParserException, IOException {
-        return parse(ParserRequest.builder(command, args, logger, 
messageBuilderFactory)
-                .build());
+        return parse(ParserRequest.mvn(args, logger, 
messageBuilderFactory).build());
     }
 
     /**
diff --git 
a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java 
b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java
index edcccaadad..ae5f83ec4b 100644
--- 
a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java
+++ 
b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java
@@ -21,10 +21,17 @@ package org.apache.maven.api.cli;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 
 import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Immutable;
 import org.apache.maven.api.annotations.Nonnull;
 import org.apache.maven.api.annotations.Nullable;
+import org.apache.maven.api.services.Lookup;
+import org.apache.maven.api.services.LookupException;
 import org.apache.maven.api.services.MessageBuilderFactory;
 
 import static java.util.Objects.requireNonNull;
@@ -36,16 +43,31 @@ import static java.util.Objects.requireNonNull;
  *
  * @since 4.0.0
  */
+@Immutable
 @Experimental
 public interface ParserRequest {
+    String MVN_CMD = "mvn";
+    String MVN_NAME = "Maven";
+
+    String MVNENC_CMD = "mvnenc";
+    String MVNENC_NAME = "Maven Password Encrypting Tool";
+
     /**
-     * Returns the Maven command to be executed.
+     * Returns the Maven command to be executed. This command is used in some 
invokers (ie forked) but also to
+     * present help to user.
      *
      * @return the command string
      */
     @Nonnull
     String command();
 
+    /**
+     * Returns the Maven command name (ie "Maven"). This string is used in 
some invokers to complete error messages.
+     *
+     * @return the command (human) name
+     */
+    String commandName();
+
     /**
      * Returns the logger to be used during the parsing process.
      *
@@ -65,10 +87,18 @@ public interface ParserRequest {
     /**
      * Returns the command-line arguments to be parsed.
      *
-     * @return an array of argument strings
+     * @return a list of argument strings
+     */
+    @Nonnull
+    List<String> args();
+
+    /**
+     * Per-request {@link Lookup} for customization.
+     *
+     * @return a lookup possibly with custom components
      */
     @Nonnull
-    String[] args();
+    Lookup lookup();
 
     /**
      * Returns the current working directory for the Maven execution.
@@ -124,10 +154,67 @@ public interface ParserRequest {
     @Nullable
     OutputStream err();
 
+    /**
+     * Creates a new Builder instance for constructing a Maven ParserRequest.
+     *
+     * @param args the command-line arguments
+     * @param logger the logger to be used during parsing
+     * @param messageBuilderFactory the factory for creating message builders
+     * @return a new Builder instance
+     */
+    @Nonnull
+    static Builder mvn(
+            @Nonnull String[] args, @Nonnull Logger logger, @Nonnull 
MessageBuilderFactory messageBuilderFactory) {
+        return mvn(Arrays.asList(args), logger, messageBuilderFactory);
+    }
+
+    /**
+     * Creates a new Builder instance for constructing a Maven ParserRequest.
+     *
+     * @param args the command-line arguments
+     * @param logger the logger to be used during parsing
+     * @param messageBuilderFactory the factory for creating message builders
+     * @return a new Builder instance
+     */
+    @Nonnull
+    static Builder mvn(
+            @Nonnull List<String> args, @Nonnull Logger logger, @Nonnull 
MessageBuilderFactory messageBuilderFactory) {
+        return builder(MVN_CMD, MVN_NAME, args, logger, messageBuilderFactory);
+    }
+
+    /**
+     * Creates a new Builder instance for constructing a Maven Encrypting Tool 
ParserRequest.
+     *
+     * @param args the command-line arguments
+     * @param logger the logger to be used during parsing
+     * @param messageBuilderFactory the factory for creating message builders
+     * @return a new Builder instance
+     */
+    @Nonnull
+    static Builder mvnenc(
+            @Nonnull String[] args, @Nonnull Logger logger, @Nonnull 
MessageBuilderFactory messageBuilderFactory) {
+        return mvnenc(Arrays.asList(args), logger, messageBuilderFactory);
+    }
+
+    /**
+     * Creates a new Builder instance for constructing a Maven Encrypting Tool 
ParserRequest.
+     *
+     * @param args the command-line arguments
+     * @param logger the logger to be used during parsing
+     * @param messageBuilderFactory the factory for creating message builders
+     * @return a new Builder instance
+     */
+    @Nonnull
+    static Builder mvnenc(
+            @Nonnull List<String> args, @Nonnull Logger logger, @Nonnull 
MessageBuilderFactory messageBuilderFactory) {
+        return builder(MVNENC_CMD, MVNENC_NAME, args, logger, 
messageBuilderFactory);
+    }
+
     /**
      * Creates a new Builder instance for constructing a ParserRequest.
      *
      * @param command the Maven command to be executed
+     * @param commandName the Maven command Name to be executed
      * @param args the command-line arguments
      * @param logger the logger to be used during parsing
      * @param messageBuilderFactory the factory for creating message builders
@@ -136,17 +223,20 @@ public interface ParserRequest {
     @Nonnull
     static Builder builder(
             @Nonnull String command,
-            @Nonnull String[] args,
+            @Nonnull String commandName,
+            @Nonnull List<String> args,
             @Nonnull Logger logger,
             @Nonnull MessageBuilderFactory messageBuilderFactory) {
-        return new Builder(command, args, logger, messageBuilderFactory);
+        return new Builder(command, commandName, args, logger, 
messageBuilderFactory);
     }
 
     class Builder {
         private final String command;
-        private final String[] args;
+        private final String commandName;
+        private final List<String> args;
         private final Logger logger;
         private final MessageBuilderFactory messageBuilderFactory;
+        private Lookup lookup = EMPTY_LOOKUP;
         private Path cwd;
         private Path mavenHome;
         private Path userHome;
@@ -154,13 +244,24 @@ public interface ParserRequest {
         private OutputStream out;
         private OutputStream err;
 
-        private Builder(String command, String[] args, Logger logger, 
MessageBuilderFactory messageBuilderFactory) {
-            this.command = requireNonNull(command, "appName");
+        private Builder(
+                String command,
+                String commandName,
+                List<String> args,
+                Logger logger,
+                MessageBuilderFactory messageBuilderFactory) {
+            this.command = requireNonNull(command, "command");
+            this.commandName = requireNonNull(commandName, "commandName");
             this.args = requireNonNull(args, "args");
             this.logger = requireNonNull(logger, "logger");
             this.messageBuilderFactory = requireNonNull(messageBuilderFactory, 
"messageBuilderFactory");
         }
 
+        public Builder lookup(@Nonnull Lookup lookup) {
+            this.lookup = requireNonNull(lookup);
+            return this;
+        }
+
         public Builder cwd(Path cwd) {
             this.cwd = cwd;
             return this;
@@ -193,15 +294,28 @@ public interface ParserRequest {
 
         public ParserRequest build() {
             return new ParserRequestImpl(
-                    command, args, logger, messageBuilderFactory, cwd, 
mavenHome, userHome, in, out, err);
+                    command,
+                    commandName,
+                    args,
+                    logger,
+                    messageBuilderFactory,
+                    lookup,
+                    cwd,
+                    mavenHome,
+                    userHome,
+                    in,
+                    out,
+                    err);
         }
 
         @SuppressWarnings("ParameterNumber")
         private static class ParserRequestImpl implements ParserRequest {
             private final String command;
+            private final String commandName;
             private final Logger logger;
             private final MessageBuilderFactory messageBuilderFactory;
-            private final String[] args;
+            private final List<String> args;
+            private final Lookup lookup;
             private final Path cwd;
             private final Path mavenHome;
             private final Path userHome;
@@ -211,19 +325,23 @@ public interface ParserRequest {
 
             private ParserRequestImpl(
                     String command,
-                    String[] args,
+                    String commandName,
+                    List<String> args,
                     Logger logger,
                     MessageBuilderFactory messageBuilderFactory,
+                    Lookup lookup,
                     Path cwd,
                     Path mavenHome,
                     Path userHome,
                     InputStream in,
                     OutputStream out,
                     OutputStream err) {
-                this.command = requireNonNull(command, "appName");
-                this.args = requireNonNull(args, "args");
+                this.command = requireNonNull(command, "command");
+                this.commandName = requireNonNull(commandName, "commandName");
+                this.args = List.copyOf(requireNonNull(args, "args"));
                 this.logger = requireNonNull(logger, "logger");
                 this.messageBuilderFactory = 
requireNonNull(messageBuilderFactory, "messageBuilderFactory");
+                this.lookup = requireNonNull(lookup, "lookup");
                 this.cwd = cwd;
                 this.mavenHome = mavenHome;
                 this.userHome = userHome;
@@ -237,6 +355,11 @@ public interface ParserRequest {
                 return command;
             }
 
+            @Override
+            public String commandName() {
+                return commandName;
+            }
+
             @Override
             public Logger logger() {
                 return logger;
@@ -248,10 +371,15 @@ public interface ParserRequest {
             }
 
             @Override
-            public String[] args() {
+            public List<String> args() {
                 return args;
             }
 
+            @Override
+            public Lookup lookup() {
+                return lookup;
+            }
+
             @Override
             public Path cwd() {
                 return cwd;
@@ -282,5 +410,37 @@ public interface ParserRequest {
                 return err;
             }
         }
+
+        private static final Lookup EMPTY_LOOKUP = new Lookup() {
+            @Override
+            public <T> T lookup(Class<T> type) {
+                throw new LookupException("empty lookup");
+            }
+
+            @Override
+            public <T> T lookup(Class<T> type, String name) {
+                throw new LookupException("empty lookup");
+            }
+
+            @Override
+            public <T> Optional<T> lookupOptional(Class<T> type) {
+                return Optional.empty();
+            }
+
+            @Override
+            public <T> Optional<T> lookupOptional(Class<T> type, String name) {
+                return Optional.empty();
+            }
+
+            @Override
+            public <T> List<T> lookupList(Class<T> type) {
+                return List.of();
+            }
+
+            @Override
+            public <T> Map<String, T> lookupMap(Class<T> type) {
+                return Map.of();
+            }
+        };
     }
 }
diff --git a/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java 
b/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java
index 2eb512f283..5ea9ec72db 100644
--- a/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java
+++ b/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java
@@ -23,13 +23,8 @@ import java.io.IOException;
 import org.apache.maven.api.cli.Invoker;
 import org.apache.maven.api.cli.InvokerException;
 import org.apache.maven.api.cli.InvokerRequest;
-import org.apache.maven.api.cli.Logger;
 import org.apache.maven.api.cli.Options;
-import org.apache.maven.api.cli.Parser;
 import org.apache.maven.api.cli.ParserException;
-import org.apache.maven.api.services.MessageBuilderFactory;
-import org.apache.maven.cling.invoker.ProtoLogger;
-import org.apache.maven.jline.JLineMessageBuilderFactory;
 import org.apache.maven.jline.MessageUtils;
 import org.codehaus.plexus.classworlds.ClassWorld;
 
@@ -44,26 +39,24 @@ import static java.util.Objects.requireNonNull;
 public abstract class ClingSupport<O extends Options, R extends 
InvokerRequest<O>> {
     static final String CORE_CLASS_REALM_ID = "plexus.core";
 
-    protected final String command;
     protected final ClassWorld classWorld;
     protected final boolean classWorldManaged;
 
     /**
      * Ctor that creates "managed" ClassWorld. This constructor is not used in 
"normal" circumstances.
      */
-    public ClingSupport(String command) {
-        this(command, new ClassWorld(CORE_CLASS_REALM_ID, 
Thread.currentThread().getContextClassLoader()), true);
+    public ClingSupport() {
+        this(new ClassWorld(CORE_CLASS_REALM_ID, 
Thread.currentThread().getContextClassLoader()), true);
     }
 
     /**
      * Ctor to be used when running in ClassWorlds Launcher.
      */
-    public ClingSupport(String command, ClassWorld classWorld) {
-        this(command, classWorld, false);
+    public ClingSupport(ClassWorld classWorld) {
+        this(classWorld, false);
     }
 
-    protected ClingSupport(String command, ClassWorld classWorld, boolean 
classWorldManaged) {
-        this.command = requireNonNull(command, "command");
+    private ClingSupport(ClassWorld classWorld, boolean classWorldManaged) {
         this.classWorld = requireNonNull(classWorld);
         this.classWorldManaged = classWorldManaged;
     }
@@ -75,7 +68,7 @@ public abstract class ClingSupport<O extends Options, R 
extends InvokerRequest<O
         MessageUtils.systemInstall();
         MessageUtils.registerShutdownHook();
         try (Invoker<R> invoker = createInvoker()) {
-            return invoker.invoke(createParser().parse(command, args, 
createLogger(), createMessageBuilderFactory()));
+            return invoker.invoke(parseArguments(args));
         } catch (ParserException e) {
             System.err.println(e.getMessage());
             return 1;
@@ -94,13 +87,5 @@ public abstract class ClingSupport<O extends Options, R 
extends InvokerRequest<O
 
     protected abstract Invoker<R> createInvoker();
 
-    protected abstract Parser<R> createParser();
-
-    protected Logger createLogger() {
-        return new ProtoLogger();
-    }
-
-    protected MessageBuilderFactory createMessageBuilderFactory() {
-        return new JLineMessageBuilderFactory();
-    }
+    protected abstract R parseArguments(String[] args) throws ParserException, 
IOException;
 }
diff --git a/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java 
b/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
index 60be0e4fa5..ac9130bd31 100644
--- a/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
+++ b/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
@@ -21,26 +21,26 @@ package org.apache.maven.cling;
 import java.io.IOException;
 
 import org.apache.maven.api.cli.Invoker;
-import org.apache.maven.api.cli.Parser;
+import org.apache.maven.api.cli.ParserException;
 import org.apache.maven.api.cli.mvn.MavenInvokerRequest;
 import org.apache.maven.api.cli.mvn.MavenOptions;
+import org.apache.maven.cling.invoker.ProtoLogger;
 import org.apache.maven.cling.invoker.ProtoLookup;
 import org.apache.maven.cling.invoker.mvn.local.DefaultLocalMavenInvoker;
 import org.apache.maven.cling.invoker.mvn.local.DefaultLocalMavenParser;
+import org.apache.maven.jline.JLineMessageBuilderFactory;
 import org.codehaus.plexus.classworlds.ClassWorld;
 
 /**
  * Maven CLI "new-gen".
  */
 public class MavenCling extends ClingSupport<MavenOptions, 
MavenInvokerRequest<MavenOptions>> {
-    public static final String NAME = "mvn";
-
     /**
      * "Normal" Java entry point. Note: Maven uses ClassWorld Launcher and 
this entry point is NOT used under normal
      * circumstances.
      */
     public static void main(String[] args) throws IOException {
-        int exitCode = new MavenCling(NAME).run(args);
+        int exitCode = new MavenCling().run(args);
         System.exit(exitCode);
     }
 
@@ -48,15 +48,15 @@ public class MavenCling extends ClingSupport<MavenOptions, 
MavenInvokerRequest<M
      * ClassWorld Launcher "enhanced" entry point: returning exitCode and 
accepts Class World.
      */
     public static int main(String[] args, ClassWorld world) throws IOException 
{
-        return new MavenCling(NAME, world).run(args);
+        return new MavenCling(world).run(args);
     }
 
-    public MavenCling(String command) {
-        super(command);
+    public MavenCling() {
+        super();
     }
 
-    public MavenCling(String command, ClassWorld classWorld) {
-        super(command, classWorld);
+    public MavenCling(ClassWorld classWorld) {
+        super(classWorld);
     }
 
     @Override
@@ -66,7 +66,7 @@ public class MavenCling extends ClingSupport<MavenOptions, 
MavenInvokerRequest<M
     }
 
     @Override
-    protected Parser<MavenInvokerRequest<MavenOptions>> createParser() {
-        return new DefaultLocalMavenParser();
+    protected MavenInvokerRequest<MavenOptions> parseArguments(String[] args) 
throws ParserException, IOException {
+        return new DefaultLocalMavenParser().mvn(args, new ProtoLogger(), new 
JLineMessageBuilderFactory());
     }
 }
diff --git a/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java 
b/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java
index 4b90eb9ce6..488eb9eae4 100644
--- a/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java
+++ b/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java
@@ -21,26 +21,27 @@ package org.apache.maven.cling;
 import java.io.IOException;
 
 import org.apache.maven.api.cli.Invoker;
-import org.apache.maven.api.cli.Parser;
+import org.apache.maven.api.cli.ParserException;
+import org.apache.maven.api.cli.ParserRequest;
 import org.apache.maven.api.cli.mvnenc.EncryptInvokerRequest;
 import org.apache.maven.api.cli.mvnenc.EncryptOptions;
+import org.apache.maven.cling.invoker.ProtoLogger;
 import org.apache.maven.cling.invoker.ProtoLookup;
 import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker;
 import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptParser;
+import org.apache.maven.jline.JLineMessageBuilderFactory;
 import org.codehaus.plexus.classworlds.ClassWorld;
 
 /**
  * Maven encrypt CLI "new-gen".
  */
 public class MavenEncCling extends ClingSupport<EncryptOptions, 
EncryptInvokerRequest> {
-    public static final String NAME = "mvnenc";
-
     /**
      * "Normal" Java entry point. Note: Maven uses ClassWorld Launcher and 
this entry point is NOT used under normal
      * circumstances.
      */
     public static void main(String[] args) throws IOException {
-        int exitCode = new MavenEncCling(NAME).run(args);
+        int exitCode = new MavenEncCling().run(args);
         System.exit(exitCode);
     }
 
@@ -48,15 +49,15 @@ public class MavenEncCling extends 
ClingSupport<EncryptOptions, EncryptInvokerRe
      * ClassWorld Launcher "enhanced" entry point: returning exitCode and 
accepts Class World.
      */
     public static int main(String[] args, ClassWorld world) throws IOException 
{
-        return new MavenEncCling(NAME, world).run(args);
+        return new MavenEncCling(world).run(args);
     }
 
-    public MavenEncCling(String command) {
-        super(command);
+    public MavenEncCling() {
+        super();
     }
 
-    public MavenEncCling(String command, ClassWorld classWorld) {
-        super(command, classWorld);
+    public MavenEncCling(ClassWorld classWorld) {
+        super(classWorld);
     }
 
     @Override
@@ -66,7 +67,9 @@ public class MavenEncCling extends 
ClingSupport<EncryptOptions, EncryptInvokerRe
     }
 
     @Override
-    protected Parser<EncryptInvokerRequest> createParser() {
-        return new DefaultEncryptParser();
+    protected EncryptInvokerRequest parseArguments(String[] args) throws 
ParserException, IOException {
+        return new DefaultEncryptParser()
+                .parse(ParserRequest.mvnenc(args, new ProtoLogger(), new 
JLineMessageBuilderFactory())
+                        .build());
     }
 }
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java
index 612b4b9fcb..1aa26ca7be 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java
@@ -28,22 +28,19 @@ import java.util.Optional;
 import org.apache.maven.api.annotations.Nonnull;
 import org.apache.maven.api.annotations.Nullable;
 import org.apache.maven.api.cli.InvokerRequest;
-import org.apache.maven.api.cli.Logger;
 import org.apache.maven.api.cli.Options;
+import org.apache.maven.api.cli.ParserRequest;
 import org.apache.maven.api.cli.extensions.CoreExtension;
-import org.apache.maven.api.services.MessageBuilderFactory;
 
 import static java.util.Objects.requireNonNull;
 
 public abstract class BaseInvokerRequest<T extends Options> implements 
InvokerRequest<T> {
-    private final String command;
+    private final ParserRequest parserRequest;
     private final Path cwd;
     private final Path installationDirectory;
     private final Path userHomeDirectory;
     private final Map<String, String> userProperties;
     private final Map<String, String> systemProperties;
-    private final Logger logger;
-    private final MessageBuilderFactory messageBuilderFactory;
     private final Path topDirectory;
     private final Path rootDirectory;
 
@@ -54,28 +51,24 @@ public abstract class BaseInvokerRequest<T extends Options> 
implements InvokerRe
 
     @SuppressWarnings("ParameterNumber")
     public BaseInvokerRequest(
-            @Nonnull String command,
+            @Nonnull ParserRequest parserRequest,
             @Nonnull Path cwd,
             @Nonnull Path installationDirectory,
             @Nonnull Path userHomeDirectory,
             @Nonnull Map<String, String> userProperties,
             @Nonnull Map<String, String> systemProperties,
-            @Nonnull Logger logger,
-            @Nonnull MessageBuilderFactory messageBuilderFactory,
             @Nonnull Path topDirectory,
             @Nullable Path rootDirectory,
             @Nullable InputStream in,
             @Nullable OutputStream out,
             @Nullable OutputStream err,
             @Nullable List<CoreExtension> coreExtensions) {
-        this.command = requireNonNull(command);
+        this.parserRequest = requireNonNull(parserRequest);
         this.cwd = requireNonNull(cwd);
         this.installationDirectory = requireNonNull(installationDirectory);
         this.userHomeDirectory = requireNonNull(userHomeDirectory);
         this.userProperties = requireNonNull(userProperties);
         this.systemProperties = requireNonNull(systemProperties);
-        this.logger = requireNonNull(logger);
-        this.messageBuilderFactory = requireNonNull(messageBuilderFactory);
         this.topDirectory = requireNonNull(topDirectory);
         this.rootDirectory = rootDirectory;
 
@@ -86,8 +79,8 @@ public abstract class BaseInvokerRequest<T extends Options> 
implements InvokerRe
     }
 
     @Override
-    public String command() {
-        return command;
+    public ParserRequest parserRequest() {
+        return parserRequest;
     }
 
     @Override
@@ -115,16 +108,6 @@ public abstract class BaseInvokerRequest<T extends 
Options> implements InvokerRe
         return systemProperties;
     }
 
-    @Override
-    public Logger logger() {
-        return logger;
-    }
-
-    @Override
-    public MessageBuilderFactory messageBuilderFactory() {
-        return messageBuilderFactory;
-    }
-
     @Override
     public Path topDirectory() {
         return topDirectory;
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java
index 842d2005aa..e6fde18337 100644
--- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java
+++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java
@@ -23,10 +23,9 @@ import javax.xml.stream.XMLStreamException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
-import java.nio.file.FileSystem;
-import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -39,7 +38,6 @@ import java.util.Set;
 import java.util.function.Function;
 
 import org.apache.maven.api.Constants;
-import org.apache.maven.api.annotations.Nullable;
 import org.apache.maven.api.cli.InvokerRequest;
 import org.apache.maven.api.cli.Options;
 import org.apache.maven.api.cli.Parser;
@@ -69,41 +67,20 @@ public abstract class BaseParser<O extends Options, R 
extends InvokerRequest<O>>
 
         // the basics
         HashMap<String, String> overrides = new HashMap<>();
-        FileSystem fileSystem = requireNonNull(getFileSystem(parserRequest));
-        Path cwd = requireNonNull(getCwd(parserRequest, fileSystem, 
overrides));
-        Path installationDirectory = 
requireNonNull(getInstallationDirectory(parserRequest, fileSystem, overrides));
-        Path userHomeDirectory = 
requireNonNull(getUserHomeDirectory(parserRequest, fileSystem, overrides));
+        Path cwd = requireNonNull(getCwd(parserRequest, overrides));
+        Path installationDirectory = 
requireNonNull(getInstallationDirectory(parserRequest, overrides));
+        Path userHomeDirectory = 
requireNonNull(getUserHomeDirectory(parserRequest, overrides));
 
         // top/root
-        Path topDirectory = 
getCanonicalPath(requireNonNull(getTopDirectory(parserRequest, cwd)));
-        RootLocator rootLocator =
-                ServiceLoader.load(RootLocator.class).iterator().next();
-        @Nullable Path rootDirectory = rootLocator.findRoot(topDirectory);
-
-        // TODO: multiModuleProjectDirectory vs rootDirectory?
-        // fallback if no root? otherwise make sure they are same?
-        Path mmpd = System.getProperty("maven.multiModuleProjectDirectory") == 
null
-                ? null
-                : getCanonicalPath(cwd.resolve(requireNonNull(
-                        
System.getProperty("maven.multiModuleProjectDirectory"),
-                        "maven.multiModuleProjectDirectory is not set")));
-        if (rootDirectory == null) {
-            parserRequest.logger().warn(rootLocator.getNoRootMessage());
-            rootDirectory = requireNonNull(
-                    mmpd, "maven.multiModuleProjectDirectory is not set and 
rootDirectory was not discovered");
-        } else {
-            rootDirectory = getCanonicalPath(rootDirectory);
-            if (mmpd != null && !Objects.equals(rootDirectory, mmpd)) {
-                parserRequest.logger().warn("Project root directory and 
multiModuleProjectDirectory are not aligned");
-            }
-        }
+        Path topDirectory = requireNonNull(getTopDirectory(parserRequest, 
cwd));
+        Path rootDirectory = requireNonNull(getRootDirectory(parserRequest, 
cwd, topDirectory));
 
         // options
         List<O> parsedOptions = parseCliOptions(rootDirectory, 
parserRequest.args());
 
         // warn about deprecated options
         parsedOptions.forEach(o -> o.warnAboutDeprecatedOptions(
-                new PrintWriter(parserRequest.out() != null ? 
parserRequest.out() : System.out, true)));
+                parserRequest, new PrintWriter(parserRequest.out() != null ? 
parserRequest.out() : System.out, true)));
 
         // assemble options if needed
         O options = assembleOptions(parsedOptions);
@@ -113,7 +90,8 @@ public abstract class BaseParser<O extends Options, R 
extends InvokerRequest<O>>
         Map<String, String> paths = new HashMap<>();
         paths.put("session.topDirectory", topDirectory.toString());
         paths.put("session.rootDirectory", rootDirectory.toString());
-        Map<String, String> userProperties = 
populateUserProperties(systemProperties, fileSystem, paths, options);
+        Map<String, String> userProperties =
+                populateUserProperties(systemProperties, 
installationDirectory, paths, options);
 
         // options: interpolate
         Options interpolatedOptions = options.interpolate(Arrays.asList(paths, 
systemProperties, userProperties));
@@ -121,13 +99,13 @@ public abstract class BaseParser<O extends Options, R 
extends InvokerRequest<O>>
         // core extensions
         ArrayList<CoreExtension> extensions = new ArrayList<>();
         String installationExtensionsFile = 
userProperties.get(Constants.MAVEN_INSTALLATION_EXTENSIONS);
-        
extensions.addAll(readCoreExtensionsDescriptor(installationExtensionsFile, 
fileSystem));
+        
extensions.addAll(readCoreExtensionsDescriptor(installationExtensionsFile, 
installationDirectory));
 
         String projectExtensionsFile = 
userProperties.get(Constants.MAVEN_PROJECT_EXTENSIONS);
-        extensions.addAll(readCoreExtensionsDescriptor(projectExtensionsFile, 
fileSystem));
+        extensions.addAll(readCoreExtensionsDescriptor(projectExtensionsFile, 
cwd));
 
         String userExtensionsFile = 
userProperties.get(Constants.MAVEN_USER_EXTENSIONS);
-        extensions.addAll(readCoreExtensionsDescriptor(userExtensionsFile, 
fileSystem));
+        extensions.addAll(readCoreExtensionsDescriptor(userExtensionsFile, 
userHomeDirectory));
 
         return getInvokerRequest(
                 parserRequest,
@@ -155,23 +133,18 @@ public abstract class BaseParser<O extends Options, R 
extends InvokerRequest<O>>
             ArrayList<CoreExtension> extensions,
             Options options);
 
-    protected FileSystem getFileSystem(ParserRequest parserRequest) throws 
ParserException, IOException {
-        return parserRequest.cwd() != null ? 
parserRequest.cwd().getFileSystem() : FileSystems.getDefault();
-    }
-
-    protected Path getCwd(ParserRequest parserRequest, FileSystem fileSystem, 
Map<String, String> overrides)
-            throws ParserException {
+    protected Path getCwd(ParserRequest parserRequest, Map<String, String> 
overrides) throws ParserException {
         if (parserRequest.cwd() != null) {
             Path result = getCanonicalPath(parserRequest.cwd());
             overrides.put("user.dir", result.toString());
             return result;
         } else {
-            return 
getCanonicalPath(fileSystem.getPath(System.getProperty("user.dir")));
+            return getCanonicalPath(Paths.get(System.getProperty("user.dir")));
         }
     }
 
-    protected Path getInstallationDirectory(
-            ParserRequest parserRequest, FileSystem fileSystem, Map<String, 
String> overrides) throws ParserException {
+    protected Path getInstallationDirectory(ParserRequest parserRequest, 
Map<String, String> overrides)
+            throws ParserException {
         Path result;
         if (parserRequest.mavenHome() != null) {
             result = getCanonicalPath(parserRequest.mavenHome());
@@ -181,21 +154,21 @@ public abstract class BaseParser<O extends Options, R 
extends InvokerRequest<O>>
             if (mavenHome == null) {
                 throw new ParserException("local mode requires " + 
Constants.MAVEN_HOME + " Java System Property set");
             }
-            result = getCanonicalPath(fileSystem.getPath(mavenHome));
+            result = getCanonicalPath(Paths.get(mavenHome));
         }
         // TODO: we still do this but would be cool if this becomes unneeded
         System.setProperty(Constants.MAVEN_HOME, result.toString());
         return result;
     }
 
-    protected Path getUserHomeDirectory(
-            ParserRequest parserRequest, FileSystem fileSystem, Map<String, 
String> overrides) throws ParserException {
+    protected Path getUserHomeDirectory(ParserRequest parserRequest, 
Map<String, String> overrides)
+            throws ParserException {
         if (parserRequest.userHome() != null) {
             Path result = getCanonicalPath(parserRequest.userHome());
             overrides.put("user.home", result.toString());
             return result;
         } else {
-            return 
getCanonicalPath(fileSystem.getPath(System.getProperty("user.home")));
+            return 
getCanonicalPath(Paths.get(System.getProperty("user.home")));
         }
     }
 
@@ -226,7 +199,32 @@ public abstract class BaseParser<O extends Options, R 
extends InvokerRequest<O>>
                 isAltFile = arg.equals("-f") || arg.equals("--file");
             }
         }
-        return topDirectory;
+        return getCanonicalPath(topDirectory);
+    }
+
+    protected Path getRootDirectory(ParserRequest parserRequest, Path cwd, 
Path topDirectory) throws ParserException {
+        RootLocator rootLocator =
+                ServiceLoader.load(RootLocator.class).iterator().next();
+        Path rootDirectory = rootLocator.findRoot(topDirectory);
+
+        // TODO: multiModuleProjectDirectory vs rootDirectory?
+        // fallback if no root? otherwise make sure they are same?
+        Path mmpd = System.getProperty("maven.multiModuleProjectDirectory") == 
null
+                ? null
+                : getCanonicalPath(cwd.resolve(requireNonNull(
+                        
System.getProperty("maven.multiModuleProjectDirectory"),
+                        "maven.multiModuleProjectDirectory is not set")));
+        if (rootDirectory == null) {
+            parserRequest.logger().warn(rootLocator.getNoRootMessage());
+            rootDirectory = requireNonNull(
+                    mmpd, "maven.multiModuleProjectDirectory is not set and 
rootDirectory was not discovered");
+        } else {
+            rootDirectory = getCanonicalPath(rootDirectory);
+            if (mmpd != null && !Objects.equals(rootDirectory, mmpd)) {
+                parserRequest.logger().warn("Project root directory and 
multiModuleProjectDirectory are not aligned");
+            }
+        }
+        return getCanonicalPath(rootDirectory);
     }
 
     protected Map<String, String> populateSystemProperties(Map<String, String> 
overrides) throws ParserException {
@@ -260,7 +258,10 @@ public abstract class BaseParser<O extends Options, R 
extends InvokerRequest<O>>
     }
 
     protected Map<String, String> populateUserProperties(
-            Map<String, String> systemProperties, FileSystem fileSystem, 
Map<String, String> paths, Options options)
+            Map<String, String> systemProperties,
+            Path installationDirectory,
+            Map<String, String> paths,
+            Options options)
             throws ParserException, IOException {
         Properties userProperties = new Properties();
 
@@ -281,13 +282,15 @@ public abstract class BaseParser<O extends Options, R 
extends InvokerRequest<O>>
 
         Path mavenConf;
         if (systemProperties.get(MAVEN_INSTALLATION_CONF) != null) {
-            mavenConf = 
fileSystem.getPath(systemProperties.get(MAVEN_INSTALLATION_CONF));
+            mavenConf = 
installationDirectory.resolve(systemProperties.get(MAVEN_INSTALLATION_CONF));
         } else if (systemProperties.get("maven.conf") != null) {
-            mavenConf = fileSystem.getPath(systemProperties.get("maven.conf"));
+            mavenConf = 
installationDirectory.resolve(systemProperties.get("maven.conf"));
         } else if (systemProperties.get(MAVEN_HOME) != null) {
-            mavenConf = fileSystem.getPath(systemProperties.get(MAVEN_HOME), 
"conf");
+            mavenConf = installationDirectory
+                    .resolve(systemProperties.get(MAVEN_HOME))
+                    .resolve("conf");
         } else {
-            mavenConf = fileSystem.getPath("");
+            mavenConf = installationDirectory.resolve("");
         }
         Path propertiesFile = mavenConf.resolve("maven.properties");
         MavenPropertiesLoader.loadProperties(userProperties, propertiesFile, 
callback, false);
@@ -304,15 +307,16 @@ public abstract class BaseParser<O extends Options, R 
extends InvokerRequest<O>>
         return toMap(userProperties);
     }
 
-    protected abstract List<O> parseCliOptions(Path rootDirectory, String[] 
args) throws ParserException, IOException;
+    protected abstract List<O> parseCliOptions(Path rootDirectory, 
List<String> args)
+            throws ParserException, IOException;
 
     protected abstract O assembleOptions(List<O> parsedOptions);
 
-    protected List<CoreExtension> readCoreExtensionsDescriptor(String 
extensionsFile, FileSystem fileSystem)
+    protected List<CoreExtension> readCoreExtensionsDescriptor(String 
extensionsFile, Path cwd)
             throws ParserException, IOException {
         try {
             if (extensionsFile != null) {
-                Path extensionsPath = fileSystem.getPath(extensionsFile);
+                Path extensionsPath = cwd.resolve(extensionsFile);
                 if (Files.exists(extensionsPath)) {
                     try (InputStream is = 
Files.newInputStream(extensionsPath)) {
                         return new CoreExtensionsStaxReader().read(is, 
true).getExtensions();
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java
index f1e99259b3..ecd98216b0 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java
@@ -31,6 +31,7 @@ import org.apache.commons.cli.HelpFormatter;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.ParseException;
 import org.apache.maven.api.cli.Options;
+import org.apache.maven.api.cli.ParserRequest;
 import org.apache.maven.cli.CleanArgument;
 import org.apache.maven.jline.MessageUtils;
 
@@ -201,7 +202,7 @@ public abstract class CommonsCliOptions implements Options {
     }
 
     @Override
-    public void warnAboutDeprecatedOptions(PrintWriter printWriter) {
+    public void warnAboutDeprecatedOptions(ParserRequest request, PrintWriter 
printWriter) {
         if (cliManager.getUsedDeprecatedOptions().isEmpty()) {
             return;
         }
@@ -217,15 +218,18 @@ public abstract class CommonsCliOptions implements 
Options {
                 sb.append("and will be removed in a future version");
             }
             if (option.getDeprecated().getSince() != null) {
-                sb.append("since Maven 
").append(option.getDeprecated().getSince());
+                sb.append("since ")
+                        .append(request.commandName())
+                        .append(" ")
+                        .append(option.getDeprecated().getSince());
             }
             printWriter.println(sb);
         }
     }
 
     @Override
-    public void displayHelp(String command, PrintWriter printStream) {
-        cliManager.displayHelp(command, printStream);
+    public void displayHelp(ParserRequest request, PrintWriter printStream) {
+        cliManager.displayHelp(request.command(), printStream);
     }
 
     protected static class CLIManager {
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java
index f8ef3f62ec..e702c76dde 100644
--- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java
+++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java
@@ -27,6 +27,7 @@ import java.util.Optional;
 import java.util.function.Function;
 
 import org.apache.maven.api.cli.Options;
+import org.apache.maven.api.cli.ParserRequest;
 
 /**
  * Options that are "layered" by precedence order.
@@ -132,11 +133,11 @@ public abstract class LayeredOptions<O extends Options> 
implements Options {
     }
 
     @Override
-    public void warnAboutDeprecatedOptions(PrintWriter printWriter) {}
+    public void warnAboutDeprecatedOptions(ParserRequest request, PrintWriter 
printWriter) {}
 
     @Override
-    public void displayHelp(String command, PrintWriter printWriter) {
-        options.get(0).displayHelp(command, printWriter);
+    public void displayHelp(ParserRequest request, PrintWriter printWriter) {
+        options.get(0).displayHelp(request, printWriter);
     }
 
     protected <T> Optional<T> returnFirstPresentOrEmpty(Function<O, 
Optional<T>> getter) {
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java
index 616ff89e05..9c426776c3 100644
--- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java
+++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java
@@ -95,6 +95,8 @@ public abstract class LookupInvoker<
         public final ProtoLookup protoLookup;
         public final R invokerRequest;
         public final Function<String, Path> cwdResolver;
+        public final Function<String, Path> installationResolver;
+        public final Function<String, Path> userResolver;
         public final InputStream stdIn;
         public final PrintWriter stdOut;
         public final PrintWriter stdErr;
@@ -104,10 +106,17 @@ public abstract class LookupInvoker<
             this.protoLookup = invoker.protoLookup;
             this.invokerRequest = requireNonNull(invokerRequest);
             this.cwdResolver = s -> 
invokerRequest.cwd().resolve(s).normalize().toAbsolutePath();
+            this.installationResolver = s -> invokerRequest
+                    .installationDirectory()
+                    .resolve(s)
+                    .normalize()
+                    .toAbsolutePath();
+            this.userResolver = s ->
+                    
invokerRequest.userHomeDirectory().resolve(s).normalize().toAbsolutePath();
             this.stdIn = invokerRequest.in().orElse(System.in);
             this.stdOut = new 
PrintWriter(invokerRequest.out().orElse(System.out), true);
             this.stdErr = new 
PrintWriter(invokerRequest.err().orElse(System.err), true);
-            this.logger = invokerRequest.logger();
+            this.logger = invokerRequest.parserRequest().logger();
         }
 
         public Logger logger;
@@ -149,7 +158,7 @@ public abstract class LookupInvoker<
                 logging(context);
 
                 if (invokerRequest.options().help().isPresent()) {
-                    
invokerRequest.options().displayHelp(context.invokerRequest.command(), 
context.stdOut);
+                    
invokerRequest.options().displayHelp(context.invokerRequest.parserRequest(), 
context.stdOut);
                     return 0;
                 }
                 if (invokerRequest.options().showVersionAndExit().isPresent()) 
{
@@ -178,9 +187,11 @@ public abstract class LookupInvoker<
             throws InvokerException {
         boolean showStackTrace = 
context.invokerRequest.options().showErrors().orElse(false);
         if (showStackTrace) {
-            context.logger.error("Error executing Maven.", e);
+            context.logger.error(
+                    "Error executing " + 
context.invokerRequest.parserRequest().commandName() + ".", e);
         } else {
-            context.logger.error("Error executing Maven.");
+            context.logger.error(
+                    "Error executing " + 
context.invokerRequest.parserRequest().commandName() + ".");
             context.logger.error(e.getMessage());
             for (Throwable cause = e.getCause(); cause != null && cause != 
cause.getCause(); cause = cause.getCause()) {
                 context.logger.error("Caused by: " + cause.getMessage());
@@ -338,7 +349,7 @@ public abstract class LookupInvoker<
         } else {
             String userSettingsFileStr = 
context.invokerRequest.userProperties().get(Constants.MAVEN_USER_SETTINGS);
             if (userSettingsFileStr != null) {
-                userSettingsFile = 
context.cwdResolver.apply(userSettingsFileStr);
+                userSettingsFile = 
context.userResolver.apply(userSettingsFileStr);
             }
         }
 
@@ -374,7 +385,7 @@ public abstract class LookupInvoker<
             String installationSettingsFileStr =
                     
context.invokerRequest.userProperties().get(Constants.MAVEN_INSTALLATION_SETTINGS);
             if (installationSettingsFileStr != null) {
-                installationSettingsFile = 
context.cwdResolver.apply(installationSettingsFileStr);
+                installationSettingsFile = 
context.installationResolver.apply(installationSettingsFileStr);
             }
         }
 
@@ -463,18 +474,18 @@ public abstract class LookupInvoker<
                         + "usually located at 
${session.rootDirectory}/.mvn/maven.properties.");
             }
         }
+        if (userDefinedLocalRepo != null) {
+            return context.cwdResolver.apply(userDefinedLocalRepo);
+        }
         // settings
-        if (userDefinedLocalRepo == null) {
-            userDefinedLocalRepo = 
context.effectiveSettings.getLocalRepository();
+        userDefinedLocalRepo = context.effectiveSettings.getLocalRepository();
+        if (userDefinedLocalRepo != null) {
+            return context.userResolver.apply(userDefinedLocalRepo);
         }
         // defaults
-        if (userDefinedLocalRepo == null) {
-            userDefinedLocalRepo = context.cwdResolver
-                    
.apply(context.invokerRequest.userProperties().get(Constants.MAVEN_USER_CONF))
-                    .resolve("repository")
-                    .toString();
-        }
-        return context.cwdResolver.apply(userDefinedLocalRepo);
+        return context.userResolver
+                
.apply(context.invokerRequest.userProperties().get(Constants.MAVEN_USER_CONF))
+                .resolve("repository");
     }
 
     protected void populateRequest(C context, MavenExecutionRequest request) 
throws Exception {
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java
index 55b3ad5280..d8a6b982f4 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java
@@ -105,7 +105,7 @@ public abstract class DefaultMavenInvoker<
     }
 
     @Override
-    protected void prepare(C localContext) throws Exception {
+    protected void prepare(C context) throws Exception {
         // explicitly fill in "defaults"?
         DefaultMavenExecutionRequest mavenExecutionRequest = new 
DefaultMavenExecutionRequest();
         mavenExecutionRequest.setRepositoryCache(new DefaultRepositoryCache());
@@ -120,7 +120,7 @@ public abstract class DefaultMavenInvoker<
         mavenExecutionRequest.setDegreeOfConcurrency(1);
         mavenExecutionRequest.setBuilderId("singlethreaded");
 
-        localContext.mavenExecutionRequest = mavenExecutionRequest;
+        context.mavenExecutionRequest = mavenExecutionRequest;
     }
 
     @Override
@@ -146,11 +146,11 @@ public abstract class DefaultMavenInvoker<
     }
 
     @Override
-    protected void postCommands(C localContext) throws Exception {
-        super.postCommands(localContext);
+    protected void postCommands(C context) throws Exception {
+        super.postCommands(context);
 
-        R invokerRequest = localContext.invokerRequest;
-        Logger logger = localContext.logger;
+        R invokerRequest = context.invokerRequest;
+        Logger logger = context.logger;
         if (invokerRequest.options().relaxedChecksums().orElse(false)) {
             logger.info("Disabling strict checksum verification on all 
artifact downloads.");
         } else if (invokerRequest.options().strictChecksums().orElse(false)) {
@@ -172,12 +172,12 @@ public abstract class DefaultMavenInvoker<
         }
     }
 
-    protected void toolchains(C localContext) throws Exception {
+    protected void toolchains(C context) throws Exception {
         Path userToolchainsFile = null;
 
-        if 
(localContext.invokerRequest.options().altUserToolchains().isPresent()) {
-            userToolchainsFile = localContext.cwdResolver.apply(
-                    
localContext.invokerRequest.options().altUserToolchains().get());
+        if (context.invokerRequest.options().altUserToolchains().isPresent()) {
+            userToolchainsFile = context.cwdResolver.apply(
+                    
context.invokerRequest.options().altUserToolchains().get());
 
             if (!Files.isRegularFile(userToolchainsFile)) {
                 throw new FileNotFoundException(
@@ -185,20 +185,17 @@ public abstract class DefaultMavenInvoker<
             }
         } else {
             String userToolchainsFileStr =
-                    
localContext.invokerRequest.userProperties().get(Constants.MAVEN_USER_TOOLCHAINS);
+                    
context.invokerRequest.userProperties().get(Constants.MAVEN_USER_TOOLCHAINS);
             if (userToolchainsFileStr != null) {
-                userToolchainsFile = 
localContext.cwdResolver.apply(userToolchainsFileStr);
+                userToolchainsFile = 
context.cwdResolver.apply(userToolchainsFileStr);
             }
         }
 
         Path installationToolchainsFile = null;
 
-        if 
(localContext.invokerRequest.options().altInstallationToolchains().isPresent()) 
{
-            installationToolchainsFile = 
localContext.cwdResolver.apply(localContext
-                    .invokerRequest
-                    .options()
-                    .altInstallationToolchains()
-                    .get());
+        if 
(context.invokerRequest.options().altInstallationToolchains().isPresent()) {
+            installationToolchainsFile = context.cwdResolver.apply(
+                    
context.invokerRequest.options().altInstallationToolchains().get());
 
             if (!Files.isRegularFile(installationToolchainsFile)) {
                 throw new FileNotFoundException(
@@ -206,15 +203,15 @@ public abstract class DefaultMavenInvoker<
             }
         } else {
             String installationToolchainsFileStr =
-                    
localContext.invokerRequest.userProperties().get(Constants.MAVEN_INSTALLATION_TOOLCHAINS);
+                    
context.invokerRequest.userProperties().get(Constants.MAVEN_INSTALLATION_TOOLCHAINS);
             if (installationToolchainsFileStr != null) {
-                installationToolchainsFile = 
localContext.cwdResolver.apply(installationToolchainsFileStr);
+                installationToolchainsFile = 
context.cwdResolver.apply(installationToolchainsFileStr);
             }
         }
 
-        localContext.mavenExecutionRequest.setInstallationToolchainsFile(
+        context.mavenExecutionRequest.setInstallationToolchainsFile(
                 installationToolchainsFile != null ? 
installationToolchainsFile.toFile() : null);
-        localContext.mavenExecutionRequest.setUserToolchainsFile(
+        context.mavenExecutionRequest.setUserToolchainsFile(
                 userToolchainsFile != null ? userToolchainsFile.toFile() : 
null);
 
         DefaultToolchainsBuildingRequest toolchainsRequest = new 
DefaultToolchainsBuildingRequest();
@@ -225,35 +222,35 @@ public abstract class DefaultMavenInvoker<
             toolchainsRequest.setUserToolchainsSource(new 
FileSource(userToolchainsFile));
         }
 
-        localContext.eventSpyDispatcher.onEvent(toolchainsRequest);
+        context.eventSpyDispatcher.onEvent(toolchainsRequest);
 
-        localContext.logger.debug("Reading installation toolchains from '"
+        context.logger.debug("Reading installation toolchains from '"
                 + (toolchainsRequest.getGlobalToolchainsSource() != null
                         ? 
toolchainsRequest.getGlobalToolchainsSource().getLocation()
                         : installationToolchainsFile)
                 + "'");
-        localContext.logger.debug("Reading user toolchains from '"
+        context.logger.debug("Reading user toolchains from '"
                 + (toolchainsRequest.getUserToolchainsSource() != null
                         ? 
toolchainsRequest.getUserToolchainsSource().getLocation()
                         : userToolchainsFile)
                 + "'");
 
-        ToolchainsBuildingResult toolchainsResult = 
localContext.toolchainsBuilder.build(toolchainsRequest);
+        ToolchainsBuildingResult toolchainsResult = 
context.toolchainsBuilder.build(toolchainsRequest);
 
-        localContext.eventSpyDispatcher.onEvent(toolchainsResult);
+        context.eventSpyDispatcher.onEvent(toolchainsResult);
 
-        localContext.mavenExecutionRequestPopulator.populateFromToolchains(
-                localContext.mavenExecutionRequest, 
toolchainsResult.getEffectiveToolchains());
+        context.mavenExecutionRequestPopulator.populateFromToolchains(
+                context.mavenExecutionRequest, 
toolchainsResult.getEffectiveToolchains());
 
         if (!toolchainsResult.getProblems().isEmpty()) {
-            localContext.logger.warn("");
-            localContext.logger.warn("Some problems were encountered while 
building the effective toolchains");
+            context.logger.warn("");
+            context.logger.warn("Some problems were encountered while building 
the effective toolchains");
 
             for (Problem problem : toolchainsResult.getProblems()) {
-                localContext.logger.warn(problem.getMessage() + " @ " + 
problem.getLocation());
+                context.logger.warn(problem.getMessage() + " @ " + 
problem.getLocation());
             }
 
-            localContext.logger.warn("");
+            context.logger.warn("");
         }
     }
 
@@ -325,21 +322,21 @@ public abstract class DefaultMavenInvoker<
         }
     }
 
-    protected Path determinePom(C localContext) {
-        Path current = localContext.invokerRequest.cwd();
-        if 
(localContext.invokerRequest.options().alternatePomFile().isPresent()) {
-            current = localContext.cwdResolver.apply(
-                    
localContext.invokerRequest.options().alternatePomFile().get());
+    protected Path determinePom(C context) {
+        Path current = context.invokerRequest.cwd();
+        if (context.invokerRequest.options().alternatePomFile().isPresent()) {
+            current = context.cwdResolver.apply(
+                    context.invokerRequest.options().alternatePomFile().get());
         }
-        if (localContext.modelProcessor != null) {
-            return localContext.modelProcessor.locateExistingPom(current);
+        if (context.modelProcessor != null) {
+            return context.modelProcessor.locateExistingPom(current);
         } else {
             return Files.isRegularFile(current) ? current : null;
         }
     }
 
-    protected String determineReactorFailureBehaviour(C localContext) {
-        MavenOptions mavenOptions = localContext.invokerRequest.options();
+    protected String determineReactorFailureBehaviour(C context) {
+        MavenOptions mavenOptions = context.invokerRequest.options();
         if (mavenOptions.failFast().isPresent()) {
             return MavenExecutionRequest.REACTOR_FAIL_FAST;
         } else if (mavenOptions.failAtEnd().isPresent()) {
@@ -351,8 +348,8 @@ public abstract class DefaultMavenInvoker<
         }
     }
 
-    protected String determineGlobalChecksumPolicy(C localContext) {
-        MavenOptions mavenOptions = localContext.invokerRequest.options();
+    protected String determineGlobalChecksumPolicy(C context) {
+        MavenOptions mavenOptions = context.invokerRequest.options();
         if (mavenOptions.strictChecksums().orElse(false)) {
             return MavenExecutionRequest.CHECKSUM_POLICY_FAIL;
         } else if (mavenOptions.relaxedChecksums().orElse(false)) {
@@ -362,18 +359,17 @@ public abstract class DefaultMavenInvoker<
         }
     }
 
-    protected ExecutionListener determineExecutionListener(C localContext) {
-        ExecutionListener executionListener =
-                new 
ExecutionEventLogger(localContext.invokerRequest.messageBuilderFactory());
-        if (localContext.eventSpyDispatcher != null) {
-            return 
localContext.eventSpyDispatcher.chainListener(executionListener);
+    protected ExecutionListener determineExecutionListener(C context) {
+        ExecutionListener executionListener = new 
ExecutionEventLogger(context.invokerRequest.messageBuilderFactory());
+        if (context.eventSpyDispatcher != null) {
+            return context.eventSpyDispatcher.chainListener(executionListener);
         } else {
             return executionListener;
         }
     }
 
-    protected String determineMakeBehavior(C localContext) {
-        MavenOptions mavenOptions = localContext.invokerRequest.options();
+    protected String determineMakeBehavior(C context) {
+        MavenOptions mavenOptions = context.invokerRequest.options();
         if (mavenOptions.alsoMake().isPresent()
                 && mavenOptions.alsoMakeDependents().isEmpty()) {
             return MavenExecutionRequest.REACTOR_MAKE_UPSTREAM;
@@ -388,8 +384,8 @@ public abstract class DefaultMavenInvoker<
         }
     }
 
-    protected void performProjectActivation(C localContext, ProjectActivation 
projectActivation) {
-        MavenOptions mavenOptions = localContext.invokerRequest.options();
+    protected void performProjectActivation(C context, ProjectActivation 
projectActivation) {
+        MavenOptions mavenOptions = context.invokerRequest.options();
         if (mavenOptions.projects().isPresent()
                 && !mavenOptions.projects().get().isEmpty()) {
             List<String> optionValues = mavenOptions.projects().get();
@@ -416,8 +412,8 @@ public abstract class DefaultMavenInvoker<
         }
     }
 
-    protected void performProfileActivation(C localContext, ProfileActivation 
profileActivation) {
-        MavenOptions mavenOptions = localContext.invokerRequest.options();
+    protected void performProfileActivation(C context, ProfileActivation 
profileActivation) {
+        MavenOptions mavenOptions = context.invokerRequest.options();
         if (mavenOptions.activatedProfiles().isPresent()
                 && !mavenOptions.activatedProfiles().get().isEmpty()) {
             List<String> optionValues = mavenOptions.activatedProfiles().get();
@@ -444,62 +440,61 @@ public abstract class DefaultMavenInvoker<
         }
     }
 
-    protected int doExecute(C localContext) throws Exception {
-        MavenExecutionRequest request = localContext.mavenExecutionRequest;
+    protected int doExecute(C context) throws Exception {
+        MavenExecutionRequest request = context.mavenExecutionRequest;
 
         // why? No way to disable caching?
-        if (localContext.mavenExecutionRequest.getRepositoryCache() == null) {
-            localContext.mavenExecutionRequest.setRepositoryCache(new 
DefaultRepositoryCache());
+        if (context.mavenExecutionRequest.getRepositoryCache() == null) {
+            context.mavenExecutionRequest.setRepositoryCache(new 
DefaultRepositoryCache());
         }
 
-        localContext.eventSpyDispatcher.onEvent(request);
-
-        MavenExecutionResult result = localContext.maven.execute(request);
+        context.eventSpyDispatcher.onEvent(request);
 
-        localContext.eventSpyDispatcher.onEvent(result);
-
-        localContext.eventSpyDispatcher.close();
+        MavenExecutionResult result;
+        try {
+            result = context.maven.execute(request);
+            context.eventSpyDispatcher.onEvent(result);
+        } finally {
+            context.eventSpyDispatcher.close();
+        }
 
         if (result.hasExceptions()) {
             ExceptionHandler handler = new DefaultExceptionHandler();
-
             Map<String, String> references = new LinkedHashMap<>();
-
             List<MavenProject> failedProjects = new ArrayList<>();
 
             for (Throwable exception : result.getExceptions()) {
                 ExceptionSummary summary = handler.handleException(exception);
-
-                logSummary(localContext, summary, references, "");
+                logSummary(context, summary, references, "");
 
                 if (exception instanceof LifecycleExecutionException) {
                     failedProjects.add(((LifecycleExecutionException) 
exception).getProject());
                 }
             }
 
-            localContext.logger.error("");
+            context.logger.error("");
 
-            if 
(!localContext.invokerRequest.options().showErrors().orElse(false)) {
-                localContext.logger.error("To see the full stack trace of the 
errors, re-run Maven with the '"
+            if (!context.invokerRequest.options().showErrors().orElse(false)) {
+                context.logger.error("To see the full stack trace of the 
errors, re-run Maven with the '"
                         + MessageUtils.builder().strong("-e") + "' switch");
             }
-            if 
(!localContext.invokerRequest.options().verbose().orElse(false)) {
-                localContext.logger.error("Re-run Maven using the '"
+            if (!context.invokerRequest.options().verbose().orElse(false)) {
+                context.logger.error("Re-run Maven using the '"
                         + MessageUtils.builder().strong("-X") + "' switch to 
enable verbose output");
             }
 
             if (!references.isEmpty()) {
-                localContext.logger.error("");
-                localContext.logger.error("For more information about the 
errors and possible solutions"
+                context.logger.error("");
+                context.logger.error("For more information about the errors 
and possible solutions"
                         + ", please read the following articles:");
 
                 for (Map.Entry<String, String> entry : references.entrySet()) {
-                    
localContext.logger.error(MessageUtils.builder().strong(entry.getValue()) + " " 
+ entry.getKey());
+                    
context.logger.error(MessageUtils.builder().strong(entry.getValue()) + " " + 
entry.getKey());
                 }
             }
 
             if (result.canResume()) {
-                logBuildResumeHint(localContext, "mvn [args] -r");
+                logBuildResumeHint(context, "mvn [args] -r");
             } else if (!failedProjects.isEmpty()) {
                 List<MavenProject> sortedProjects = 
result.getTopologicallySortedProjects();
 
@@ -509,12 +504,12 @@ public abstract class DefaultMavenInvoker<
                 MavenProject firstFailedProject = failedProjects.get(0);
                 if (!firstFailedProject.equals(sortedProjects.get(0))) {
                     String resumeFromSelector = 
getResumeFromSelector(sortedProjects, firstFailedProject);
-                    logBuildResumeHint(localContext, "mvn [args] -rf " + 
resumeFromSelector);
+                    logBuildResumeHint(context, "mvn [args] -rf " + 
resumeFromSelector);
                 }
             }
 
-            if 
(localContext.invokerRequest.options().failNever().orElse(false)) {
-                localContext.logger.info("Build failures were ignored.");
+            if (context.invokerRequest.options().failNever().orElse(false)) {
+                context.logger.info("Build failures were ignored.");
                 return 0;
             } else {
                 return 1;
@@ -524,10 +519,10 @@ public abstract class DefaultMavenInvoker<
         }
     }
 
-    protected void logBuildResumeHint(C localContext, String resumeBuildHint) {
-        localContext.logger.error("");
-        localContext.logger.error("After correcting the problems, you can 
resume the build with the command");
-        localContext.logger.error(
+    protected void logBuildResumeHint(C context, String resumeBuildHint) {
+        context.logger.error("");
+        context.logger.error("After correcting the problems, you can resume 
the build with the command");
+        context.logger.error(
                 MessageUtils.builder().a("  
").strong(resumeBuildHint).toString());
     }
 
@@ -567,7 +562,7 @@ public abstract class DefaultMavenInvoker<
 
     protected static final String ANSI_RESET = "\u001B\u005Bm";
 
-    protected void logSummary(C localContext, ExceptionSummary summary, 
Map<String, String> references, String indent) {
+    protected void logSummary(C context, ExceptionSummary summary, Map<String, 
String> references, String indent) {
         String referenceKey = "";
 
         if (summary.getReference() != null && 
!summary.getReference().isEmpty()) {
@@ -607,11 +602,11 @@ public abstract class DefaultMavenInvoker<
             line = indent + line + ("".equals(nextColor) ? "" : ANSI_RESET);
 
             if ((i == lines.length - 1)
-                    && 
(localContext.invokerRequest.options().showErrors().orElse(false)
+                    && 
(context.invokerRequest.options().showErrors().orElse(false)
                             || (summary.getException() instanceof 
InternalErrorException))) {
-                localContext.logger.error(line, summary.getException());
+                context.logger.error(line, summary.getException());
             } else {
-                localContext.logger.error(line);
+                context.logger.error(line);
             }
 
             currentColor = nextColor;
@@ -620,7 +615,7 @@ public abstract class DefaultMavenInvoker<
         indent += "  ";
 
         for (ExceptionSummary child : summary.getChildren()) {
-            logSummary(localContext, child, references, indent);
+            logSummary(context, child, references, indent);
         }
     }
 }
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvokerRequest.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvokerRequest.java
index 833dd532bc..1ae5cf7b77 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvokerRequest.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvokerRequest.java
@@ -25,11 +25,10 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.maven.api.annotations.Nonnull;
-import org.apache.maven.api.cli.Logger;
+import org.apache.maven.api.cli.ParserRequest;
 import org.apache.maven.api.cli.extensions.CoreExtension;
 import org.apache.maven.api.cli.mvn.MavenInvokerRequest;
 import org.apache.maven.api.cli.mvn.MavenOptions;
-import org.apache.maven.api.services.MessageBuilderFactory;
 import org.apache.maven.cling.invoker.BaseInvokerRequest;
 
 import static java.util.Objects.requireNonNull;
@@ -45,14 +44,12 @@ public class DefaultMavenInvokerRequest<O extends 
MavenOptions> extends BaseInvo
 
     @SuppressWarnings("ParameterNumber")
     public DefaultMavenInvokerRequest(
-            String command,
+            ParserRequest parserRequest,
             Path cwd,
             Path installationDirectory,
             Path userHomeDirectory,
             Map<String, String> userProperties,
             Map<String, String> systemProperties,
-            Logger logger,
-            MessageBuilderFactory messageBuilderFactory,
             Path topDirectory,
             Path rootDirectory,
             InputStream in,
@@ -61,14 +58,12 @@ public class DefaultMavenInvokerRequest<O extends 
MavenOptions> extends BaseInvo
             List<CoreExtension> coreExtensions,
             O options) {
         super(
-                command,
+                parserRequest,
                 cwd,
                 installationDirectory,
                 userHomeDirectory,
                 userProperties,
                 systemProperties,
-                logger,
-                messageBuilderFactory,
                 topDirectory,
                 rootDirectory,
                 in,
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenParser.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenParser.java
index 45f673c5e0..49f3f56747 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenParser.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenParser.java
@@ -53,7 +53,7 @@ public abstract class DefaultMavenParser<O extends 
MavenOptions, R extends Maven
             Options options);
 
     @Override
-    protected List<O> parseCliOptions(Path rootDirectory, String[] args) 
throws ParserException, IOException {
+    protected List<O> parseCliOptions(Path rootDirectory, List<String> args) 
throws ParserException, IOException {
         ArrayList<O> result = new ArrayList<>();
         // CLI args
         result.add(parseMavenCliOptions(args));
@@ -65,14 +65,14 @@ public abstract class DefaultMavenParser<O extends 
MavenOptions, R extends Maven
         return result;
     }
 
-    protected O parseMavenCliOptions(String[] args) throws ParserException {
+    protected O parseMavenCliOptions(List<String> args) throws ParserException 
{
         return parseArgs(Options.SOURCE_CLI, args);
     }
 
     protected O parseMavenConfigOptions(Path configFile) throws 
ParserException, IOException {
         try (Stream<String> lines = Files.lines(configFile, 
Charset.defaultCharset())) {
-            String[] args =
-                    lines.filter(arg -> !arg.isEmpty() && 
!arg.startsWith("#")).toArray(String[]::new);
+            List<String> args =
+                    lines.filter(arg -> !arg.isEmpty() && 
!arg.startsWith("#")).toList();
             O options = parseArgs("maven.config", args);
             if (options.goals().isPresent()) {
                 // This file can only contain options, not args (goals or 
phases)
@@ -83,5 +83,5 @@ public abstract class DefaultMavenParser<O extends 
MavenOptions, R extends Maven
         }
     }
 
-    protected abstract O parseArgs(String source, String[] args) throws 
ParserException;
+    protected abstract O parseArgs(String source, List<String> args) throws 
ParserException;
 }
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvoker.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvoker.java
index 2ec1e556a5..bd303fa673 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvoker.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvoker.java
@@ -35,6 +35,7 @@ import static java.util.Objects.requireNonNull;
  * Forked invoker implementation, it spawns a subprocess with Maven.
  */
 public class DefaultForkedMavenInvoker implements ForkedMavenInvoker {
+    @SuppressWarnings("MethodLength")
     @Override
     public int invoke(ForkedMavenInvokerRequest invokerRequest) throws 
InvokerException {
         requireNonNull(invokerRequest);
@@ -44,7 +45,10 @@ public class DefaultForkedMavenInvoker implements 
ForkedMavenInvoker {
         cmdAndArguments.add(invokerRequest
                 .installationDirectory()
                 .resolve("bin")
-                .resolve(Os.IS_WINDOWS ? invokerRequest.command() + ".cmd" : 
invokerRequest.command())
+                .resolve(
+                        Os.IS_WINDOWS
+                                ? invokerRequest.parserRequest().command() + 
".cmd"
+                                : invokerRequest.parserRequest().command())
                 .toString());
 
         MavenOptions mavenOptions = invokerRequest.options();
@@ -54,42 +58,138 @@ public class DefaultForkedMavenInvoker implements 
ForkedMavenInvoker {
                 cmdAndArguments.add("-D" + entry.getKey() + "=" + 
entry.getValue());
             }
         }
-        if (mavenOptions.alternatePomFile().isPresent()) {
-            cmdAndArguments.add("-f");
-            cmdAndArguments.add(mavenOptions.alternatePomFile().get());
-        }
-        if (mavenOptions.offline().orElse(false)) {
-            cmdAndArguments.add("-o");
-        }
         if (mavenOptions.showVersionAndExit().orElse(false)) {
-            cmdAndArguments.add("-v");
+            cmdAndArguments.add("--version");
         }
         if (mavenOptions.showVersion().orElse(false)) {
-            cmdAndArguments.add("-V");
+            cmdAndArguments.add("--show-version");
         }
         if (mavenOptions.quiet().orElse(false)) {
-            cmdAndArguments.add("-q");
+            cmdAndArguments.add("--quiet");
         }
         if (mavenOptions.verbose().orElse(false)) {
-            cmdAndArguments.add("-X");
+            cmdAndArguments.add("--verbose");
         }
         if (mavenOptions.showErrors().orElse(false)) {
-            cmdAndArguments.add("-e");
+            cmdAndArguments.add("--errors");
         }
-        if (mavenOptions.nonRecursive().orElse(false)) {
-            cmdAndArguments.add("-N");
-        }
-        if (mavenOptions.updateSnapshots().orElse(false)) {
-            cmdAndArguments.add("-U");
+        if (mavenOptions.failOnSeverity().isPresent()) {
+            cmdAndArguments.add("--fail-on-severity");
+            cmdAndArguments.add(mavenOptions.failOnSeverity().get());
         }
         if (mavenOptions.nonInteractive().orElse(false)) {
-            cmdAndArguments.add("-B");
+            cmdAndArguments.add("--non-interactive");
+        }
+        if (mavenOptions.forceInteractive().orElse(false)) {
+            cmdAndArguments.add("--force-interactive");
+        }
+        if (mavenOptions.altUserSettings().isPresent()) {
+            cmdAndArguments.add("--settings");
+            cmdAndArguments.add(mavenOptions.altUserSettings().get());
+        }
+        if (mavenOptions.altProjectSettings().isPresent()) {
+            cmdAndArguments.add("--project-settings");
+            cmdAndArguments.add(mavenOptions.altProjectSettings().get());
+        }
+        if (mavenOptions.altInstallationSettings().isPresent()) {
+            cmdAndArguments.add("--install-settings");
+            cmdAndArguments.add(mavenOptions.altInstallationSettings().get());
+        }
+        if (mavenOptions.altUserToolchains().isPresent()) {
+            cmdAndArguments.add("--toolchains");
+            cmdAndArguments.add(mavenOptions.altUserToolchains().get());
+        }
+        if (mavenOptions.altInstallationToolchains().isPresent()) {
+            cmdAndArguments.add("--install-toolchains");
+            
cmdAndArguments.add(mavenOptions.altInstallationToolchains().get());
         }
         if (mavenOptions.logFile().isPresent()) {
-            cmdAndArguments.add("-l");
+            cmdAndArguments.add("--log-file");
             cmdAndArguments.add(mavenOptions.logFile().get());
         }
-        // TODO: etc
+        if (mavenOptions.color().isPresent()) {
+            cmdAndArguments.add("--color");
+            cmdAndArguments.add(mavenOptions.color().get());
+        }
+        if (mavenOptions.help().orElse(false)) {
+            cmdAndArguments.add("--help");
+        }
+        if (mavenOptions.alternatePomFile().isPresent()) {
+            cmdAndArguments.add("--file");
+            cmdAndArguments.add(mavenOptions.alternatePomFile().get());
+        }
+        if (mavenOptions.offline().orElse(false)) {
+            cmdAndArguments.add("--offline");
+        }
+        if (mavenOptions.nonRecursive().orElse(false)) {
+            cmdAndArguments.add("--non-recursive");
+        }
+        if (mavenOptions.updateSnapshots().orElse(false)) {
+            cmdAndArguments.add("--update-snapshots");
+        }
+        if (mavenOptions.activatedProfiles().isPresent()) {
+            cmdAndArguments.add("--activate-profiles");
+            cmdAndArguments.add(
+                    String.join(",", mavenOptions.activatedProfiles().get()));
+        }
+        if (mavenOptions.suppressSnapshotUpdates().orElse(false)) {
+            cmdAndArguments.add("--no-snapshot-updates");
+        }
+        if (mavenOptions.strictChecksums().orElse(false)) {
+            cmdAndArguments.add("--strict-checksums");
+        }
+        if (mavenOptions.relaxedChecksums().orElse(false)) {
+            cmdAndArguments.add("--lax-checksums");
+        }
+        if (mavenOptions.failFast().orElse(false)) {
+            cmdAndArguments.add("--fail-fast");
+        }
+        if (mavenOptions.failAtEnd().orElse(false)) {
+            cmdAndArguments.add("--fail-at-end");
+        }
+        if (mavenOptions.failNever().orElse(false)) {
+            cmdAndArguments.add("--fail-never");
+        }
+        if (mavenOptions.resume().orElse(false)) {
+            cmdAndArguments.add("--resume");
+        }
+        if (mavenOptions.resumeFrom().isPresent()) {
+            cmdAndArguments.add("--resume-from");
+            cmdAndArguments.add(mavenOptions.resumeFrom().get());
+        }
+        if (mavenOptions.projects().isPresent()) {
+            cmdAndArguments.add("--projects");
+            cmdAndArguments.add(String.join(",", 
mavenOptions.projects().get()));
+        }
+        if (mavenOptions.alsoMake().orElse(false)) {
+            cmdAndArguments.add("--also-make");
+        }
+        if (mavenOptions.alsoMakeDependents().orElse(false)) {
+            cmdAndArguments.add("--also-make-dependents");
+        }
+        if (mavenOptions.threads().isPresent()) {
+            cmdAndArguments.add("--threads");
+            cmdAndArguments.add(mavenOptions.threads().get());
+        }
+        if (mavenOptions.builder().isPresent()) {
+            cmdAndArguments.add("--builder");
+            cmdAndArguments.add(mavenOptions.builder().get());
+        }
+        if (mavenOptions.noTransferProgress().orElse(false)) {
+            cmdAndArguments.add("--no-transfer-progress");
+        }
+        if (mavenOptions.cacheArtifactNotFound().isPresent()) {
+            cmdAndArguments.add("--cache-artifact-not-found");
+            
cmdAndArguments.add(mavenOptions.cacheArtifactNotFound().get().toString());
+        }
+        if (mavenOptions.strictArtifactDescriptorPolicy().isPresent()) {
+            cmdAndArguments.add("--strict-artifact-descriptor-policy");
+            cmdAndArguments.add(
+                    
mavenOptions.strictArtifactDescriptorPolicy().get().toString());
+        }
+        if (mavenOptions.ignoreTransitiveRepositories().isPresent()) {
+            cmdAndArguments.add("--ignore-transitive-repositories");
+        }
 
         // last the goals
         
cmdAndArguments.addAll(mavenOptions.goals().orElse(Collections.emptyList()));
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvokerRequest.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvokerRequest.java
index b08bcd7ab5..d62c2b77b4 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvokerRequest.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvokerRequest.java
@@ -25,11 +25,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
-import org.apache.maven.api.cli.Logger;
+import org.apache.maven.api.cli.ParserRequest;
 import org.apache.maven.api.cli.extensions.CoreExtension;
 import org.apache.maven.api.cli.mvn.MavenOptions;
 import org.apache.maven.api.cli.mvn.forked.ForkedMavenInvokerRequest;
-import org.apache.maven.api.services.MessageBuilderFactory;
 import org.apache.maven.cling.invoker.mvn.DefaultMavenInvokerRequest;
 
 /**
@@ -41,14 +40,12 @@ public class DefaultForkedMavenInvokerRequest extends 
DefaultMavenInvokerRequest
 
     @SuppressWarnings("ParameterNumber")
     public DefaultForkedMavenInvokerRequest(
-            String command,
+            ParserRequest parserRequest,
             Path cwd,
             Path installationDirectory,
             Path userHomeDirectory,
             Map<String, String> userProperties,
             Map<String, String> systemProperties,
-            Logger logger,
-            MessageBuilderFactory messageBuilderFactory,
             Path topDirectory,
             Path rootDirectory,
             InputStream in,
@@ -58,14 +55,12 @@ public class DefaultForkedMavenInvokerRequest extends 
DefaultMavenInvokerRequest
             List<String> jvmArguments,
             MavenOptions options) {
         super(
-                command,
+                parserRequest,
                 cwd,
                 installationDirectory,
                 userHomeDirectory,
                 userProperties,
                 systemProperties,
-                logger,
-                messageBuilderFactory,
                 topDirectory,
                 rootDirectory,
                 in,
@@ -78,6 +73,6 @@ public class DefaultForkedMavenInvokerRequest extends 
DefaultMavenInvokerRequest
 
     @Override
     public Optional<List<String>> jvmArguments() {
-        return Optional.of(jvmArguments);
+        return Optional.ofNullable(jvmArguments);
     }
 }
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenParser.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenParser.java
index c1d873f6b1..194e6841b4 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenParser.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenParser.java
@@ -20,7 +20,6 @@ package org.apache.maven.cling.invoker.mvn.forked;
 
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -53,14 +52,12 @@ public class DefaultForkedMavenParser extends 
DefaultMavenParser<MavenOptions, F
             ArrayList<CoreExtension> extensions,
             Options options) {
         return new DefaultForkedMavenInvokerRequest(
-                parserRequest.command(),
+                parserRequest,
                 cwd,
                 installationDirectory,
                 userHomeDirectory,
                 userProperties,
                 systemProperties,
-                parserRequest.logger(),
-                parserRequest.messageBuilderFactory(),
                 topDirectory,
                 rootDirectory,
                 parserRequest.in(),
@@ -74,15 +71,15 @@ public class DefaultForkedMavenParser extends 
DefaultMavenParser<MavenOptions, F
     protected List<String> getJvmArguments(Path rootDirectory) {
         if (rootDirectory != null) {
             // TODO: do this
-            return Collections.emptyList();
+            return null;
         }
         return null;
     }
 
     @Override
-    protected MavenOptions parseArgs(String source, String[] args) throws 
ParserException {
+    protected MavenOptions parseArgs(String source, List<String> args) throws 
ParserException {
         try {
-            return CommonsCliMavenOptions.parse(source, args);
+            return CommonsCliMavenOptions.parse(source, args.toArray(new 
String[0]));
         } catch (ParseException e) {
             throw new ParserException("Failed to parse source " + source, 
e.getCause());
         }
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/local/DefaultLocalMavenParser.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/local/DefaultLocalMavenParser.java
index 028f124f9b..d3fde3193c 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/local/DefaultLocalMavenParser.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/local/DefaultLocalMavenParser.java
@@ -52,14 +52,12 @@ public class DefaultLocalMavenParser extends 
DefaultMavenParser<MavenOptions, Ma
             ArrayList<CoreExtension> extensions,
             Options options) {
         return new DefaultMavenInvokerRequest<>(
-                parserRequest.command(),
+                parserRequest,
                 cwd,
                 installationDirectory,
                 userHomeDirectory,
                 userProperties,
                 systemProperties,
-                parserRequest.logger(),
-                parserRequest.messageBuilderFactory(),
                 topDirectory,
                 rootDirectory,
                 parserRequest.in(),
@@ -70,9 +68,9 @@ public class DefaultLocalMavenParser extends 
DefaultMavenParser<MavenOptions, Ma
     }
 
     @Override
-    protected MavenOptions parseArgs(String source, String[] args) throws 
ParserException {
+    protected MavenOptions parseArgs(String source, List<String> args) throws 
ParserException {
         try {
-            return CommonsCliMavenOptions.parse(source, args);
+            return CommonsCliMavenOptions.parse(source, args.toArray(new 
String[0]));
         } catch (ParseException e) {
             throw new ParserException("Failed to parse source " + source, 
e.getCause());
         }
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvoker.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvoker.java
index 66f7f852be..4063d0de83 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvoker.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvoker.java
@@ -54,7 +54,7 @@ public class DefaultResidentMavenInvoker
             super.close();
         }
 
-        public LocalContext createShadow() {
+        public LocalContext copy(ResidentMavenInvokerRequest invokerRequest) {
             LocalContext shadow = new 
LocalContext((DefaultResidentMavenInvoker) invoker, invokerRequest);
 
             shadow.logger = logger;
@@ -122,10 +122,12 @@ public class DefaultResidentMavenInvoker
                         throw new InvokerException("Failed to init master 
context", e);
                     }
                 })
-                .createShadow();
+                .copy(invokerRequest);
     }
 
     protected String getContextId(ResidentMavenInvokerRequest invokerRequest) {
+        // TODO: in a moment Maven stop pushing user properties to system 
properties (and maybe something more)
+        // and allow multiple instances per JVM, this may become a pool?
         return "resident";
     }
 
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerRequest.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerRequest.java
index 744003467d..5d2dc60bbc 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerRequest.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerRequest.java
@@ -25,11 +25,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 
-import org.apache.maven.api.cli.Logger;
+import org.apache.maven.api.cli.ParserRequest;
 import org.apache.maven.api.cli.extensions.CoreExtension;
 import org.apache.maven.api.cli.mvn.resident.ResidentMavenInvokerRequest;
 import org.apache.maven.api.cli.mvn.resident.ResidentMavenOptions;
-import org.apache.maven.api.services.MessageBuilderFactory;
 import org.apache.maven.cling.invoker.mvn.DefaultMavenInvokerRequest;
 
 /**
@@ -41,14 +40,12 @@ public class DefaultResidentMavenInvokerRequest extends 
DefaultMavenInvokerReque
 
     @SuppressWarnings("ParameterNumber")
     public DefaultResidentMavenInvokerRequest(
-            String command,
+            ParserRequest parserRequest,
             Path cwd,
             Path installationDirectory,
             Path userHomeDirectory,
             Map<String, String> userProperties,
             Map<String, String> systemProperties,
-            Logger logger,
-            MessageBuilderFactory messageBuilderFactory,
             Path topDirectory,
             Path rootDirectory,
             InputStream in,
@@ -58,14 +55,12 @@ public class DefaultResidentMavenInvokerRequest extends 
DefaultMavenInvokerReque
             List<String> jvmArguments,
             ResidentMavenOptions options) {
         super(
-                command,
+                parserRequest,
                 cwd,
                 installationDirectory,
                 userHomeDirectory,
                 userProperties,
                 systemProperties,
-                logger,
-                messageBuilderFactory,
                 topDirectory,
                 rootDirectory,
                 in,
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenParser.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenParser.java
index c9876dcf20..affe9c32c4 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenParser.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenParser.java
@@ -51,14 +51,12 @@ public class DefaultResidentMavenParser extends 
DefaultMavenParser<ResidentMaven
             ArrayList<CoreExtension> extensions,
             Options options) {
         return new DefaultResidentMavenInvokerRequest(
-                parserRequest.command(),
+                parserRequest,
                 cwd,
                 installationDirectory,
                 userHomeDirectory,
                 userProperties,
                 systemProperties,
-                parserRequest.logger(),
-                parserRequest.messageBuilderFactory(),
                 topDirectory,
                 rootDirectory,
                 parserRequest.in(),
@@ -78,9 +76,9 @@ public class DefaultResidentMavenParser extends 
DefaultMavenParser<ResidentMaven
     }
 
     @Override
-    protected ResidentMavenOptions parseArgs(String source, String[] args) 
throws ParserException {
+    protected ResidentMavenOptions parseArgs(String source, List<String> args) 
throws ParserException {
         try {
-            return CommonsCliResidentMavenOptions.parse(source, args);
+            return CommonsCliResidentMavenOptions.parse(source, 
args.toArray(new String[0]));
         } catch (ParseException e) {
             throw new ParserException("Failed to parse source " + source, 
e.getCause());
         }
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvokerRequest.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvokerRequest.java
index 2ecddac8ca..de8b52eb6f 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvokerRequest.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvokerRequest.java
@@ -25,12 +25,11 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.maven.api.annotations.Nonnull;
-import org.apache.maven.api.cli.Logger;
 import org.apache.maven.api.cli.Options;
+import org.apache.maven.api.cli.ParserRequest;
 import org.apache.maven.api.cli.extensions.CoreExtension;
 import org.apache.maven.api.cli.mvnenc.EncryptInvokerRequest;
 import org.apache.maven.api.cli.mvnenc.EncryptOptions;
-import org.apache.maven.api.services.MessageBuilderFactory;
 import org.apache.maven.cling.invoker.BaseInvokerRequest;
 
 import static java.util.Objects.requireNonNull;
@@ -40,14 +39,12 @@ public class DefaultEncryptInvokerRequest extends 
BaseInvokerRequest<EncryptOpti
 
     @SuppressWarnings("ParameterNumber")
     public DefaultEncryptInvokerRequest(
-            String command,
+            ParserRequest parserRequest,
             Path cwd,
             Path installationDirectory,
             Path userHomeDirectory,
             Map<String, String> userProperties,
             Map<String, String> systemProperties,
-            Logger logger,
-            MessageBuilderFactory messageBuilderFactory,
             Path topDirectory,
             Path rootDirectory,
             InputStream in,
@@ -56,14 +53,12 @@ public class DefaultEncryptInvokerRequest extends 
BaseInvokerRequest<EncryptOpti
             List<CoreExtension> coreExtensions,
             Options options) {
         super(
-                command,
+                parserRequest,
                 cwd,
                 installationDirectory,
                 userHomeDirectory,
                 userProperties,
                 systemProperties,
-                logger,
-                messageBuilderFactory,
                 topDirectory,
                 rootDirectory,
                 in,
diff --git 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptParser.java
 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptParser.java
index bcaeb48fe4..4032bc7568 100644
--- 
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptParser.java
+++ 
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptParser.java
@@ -49,14 +49,12 @@ public class DefaultEncryptParser extends 
BaseParser<EncryptOptions, EncryptInvo
             ArrayList<CoreExtension> extensions,
             Options options) {
         return new DefaultEncryptInvokerRequest(
-                parserRequest.command(),
+                parserRequest,
                 cwd,
                 installationDirectory,
                 userHomeDirectory,
                 userProperties,
                 systemProperties,
-                parserRequest.logger(),
-                parserRequest.messageBuilderFactory(),
                 topDirectory,
                 rootDirectory,
                 parserRequest.in(),
@@ -67,13 +65,13 @@ public class DefaultEncryptParser extends 
BaseParser<EncryptOptions, EncryptInvo
     }
 
     @Override
-    protected List<EncryptOptions> parseCliOptions(Path rootDirectory, 
String[] args) throws ParserException {
+    protected List<EncryptOptions> parseCliOptions(Path rootDirectory, 
List<String> args) throws ParserException {
         return Collections.singletonList(parseEncryptCliOptions(args));
     }
 
-    protected CommonsCliEncryptOptions parseEncryptCliOptions(String[] args) 
throws ParserException {
+    protected CommonsCliEncryptOptions parseEncryptCliOptions(List<String> 
args) throws ParserException {
         try {
-            return CommonsCliEncryptOptions.parse(args);
+            return CommonsCliEncryptOptions.parse(args.toArray(new String[0]));
         } catch (ParseException e) {
             throw new ParserException("Failed to parse command line options: " 
+ e.getMessage(), e);
         }
diff --git 
a/maven-cli/src/test/java/org/apache/maven/cling/invoker/forked/DefaultForkedMavenInvokerTest.java
 
b/maven-cli/src/test/java/org/apache/maven/cling/invoker/forked/DefaultForkedMavenInvokerTest.java
deleted file mode 100644
index f4c4dc585c..0000000000
--- 
a/maven-cli/src/test/java/org/apache/maven/cling/invoker/forked/DefaultForkedMavenInvokerTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.cling.invoker.forked;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-import org.apache.maven.cling.invoker.ProtoLogger;
-import org.apache.maven.cling.invoker.mvn.forked.DefaultForkedMavenInvoker;
-import org.apache.maven.cling.invoker.mvn.forked.DefaultForkedMavenParser;
-import org.apache.maven.jline.JLineMessageBuilderFactory;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.io.TempDir;
-
-@Disabled("Works ONLY with Maven4!")
-public class DefaultForkedMavenInvokerTest {
-    @Test
-    void smoke(@TempDir Path tempDir) throws Exception {
-        try (DefaultForkedMavenInvoker invoker = new 
DefaultForkedMavenInvoker()) {
-            Files.createDirectory(tempDir.resolve(".mvn"));
-            Path log = tempDir.resolve("build.log").toAbsolutePath();
-            int exitcode = invoker.invoke(new DefaultForkedMavenParser()
-                    .parse(
-                            "mvn",
-                            new String[] {"-l", log.toString(), "clean"},
-                            new ProtoLogger(),
-                            new JLineMessageBuilderFactory()));
-            System.out.println("exit code: " + exitcode);
-            System.out.println("log:");
-            System.out.println(Files.readString(log));
-        }
-    }
-}
diff --git 
a/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java
 
b/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java
new file mode 100644
index 0000000000..da6b4deb0b
--- /dev/null
+++ 
b/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java
@@ -0,0 +1,117 @@
+/*
+ * 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.cling.invoker.mvn;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.maven.api.cli.Invoker;
+import org.apache.maven.api.cli.Parser;
+import org.apache.maven.api.cli.ParserRequest;
+import org.apache.maven.api.cli.mvn.MavenInvokerRequest;
+import org.apache.maven.api.cli.mvn.MavenOptions;
+import org.apache.maven.cling.invoker.ProtoLogger;
+import org.apache.maven.jline.JLineMessageBuilderFactory;
+import org.junit.jupiter.api.Assumptions;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public abstract class MavenInvokerTestSupport<O extends MavenOptions, R 
extends MavenInvokerRequest<O>> {
+
+    protected void invoke(Path cwd, Collection<String> goals) throws Exception 
{
+        // works only in recent Maven4
+        Assumptions.assumeTrue(Files.isRegularFile(
+                
Paths.get(System.getProperty("maven.home")).resolve("conf").resolve("maven.properties")));
+
+        Files.createDirectory(cwd.resolve(".mvn"));
+
+        String pomString =
+                """
+                    <?xml version="1.0" encoding="UTF-8"?>
+                    <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 
https://maven.apache.org/maven-v4_0_0.xsd";>
+
+                        <modelVersion>4.0.0</modelVersion>
+
+                        <groupId>org.apache.maven.samples</groupId>
+                        <artifactId>sample</artifactId>
+                        <version>1.0.0</version>
+
+                        <dependencyManagement>
+                          <dependencies>
+                            <dependency>
+                              <groupId>org.junit</groupId>
+                              <artifactId>junit-bom</artifactId>
+                              <version>5.11.1</version>
+                              <type>pom</type>
+                              <scope>import</scope>
+                            </dependency>
+                          </dependencies>
+                        </dependencyManagement>
+
+                        <dependencies>
+                          <dependency>
+                            <groupId>org.junit.jupiter</groupId>
+                            <artifactId>junit-jupiter-api</artifactId>
+                            <scope>test</scope>
+                          </dependency>
+                        </dependencies>
+
+                    </project>
+                    """;
+        Path pom = cwd.resolve("pom.xml").toAbsolutePath();
+        Files.writeString(pom, pomString);
+
+        String appJavaString =
+                """
+                package org.apache.maven.samples.sample;
+
+                public class App {
+                    public static void main(String... args) {
+                        System.out.println("Hello World!");
+                    }
+                }
+                """;
+        Path appJava = 
cwd.resolve("src/main/java/org/apache/maven/samples/sample/App.java");
+        Files.createDirectories(appJava.getParent());
+        Files.writeString(appJava, appJavaString);
+
+        Parser<R> parser = createParser();
+        try (Invoker<R> invoker = createInvoker()) {
+            for (String goal : goals) {
+                Path logFile = cwd.resolve(goal + 
"-build.log").toAbsolutePath();
+                int exitCode = invoker.invoke(parser.parse(ParserRequest.mvn(
+                                List.of("-l", logFile.toString(), goal),
+                                new ProtoLogger(),
+                                new JLineMessageBuilderFactory())
+                        .cwd(cwd)
+                        .build()));
+                String log = Files.readString(logFile);
+                assertEquals(0, exitCode, log);
+            }
+        }
+    }
+
+    protected abstract Invoker<R> createInvoker();
+
+    protected abstract Parser<R> createParser();
+}
diff --git 
a/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvokerTest.java
 
b/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvokerTest.java
new file mode 100644
index 0000000000..f03c161d4c
--- /dev/null
+++ 
b/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/DefaultForkedMavenInvokerTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.cling.invoker.mvn.forked;
+
+import java.nio.file.Path;
+import java.util.Arrays;
+
+import org.apache.maven.api.cli.Invoker;
+import org.apache.maven.api.cli.Parser;
+import org.apache.maven.api.cli.mvn.MavenOptions;
+import org.apache.maven.api.cli.mvn.forked.ForkedMavenInvokerRequest;
+import org.apache.maven.cling.invoker.mvn.MavenInvokerTestSupport;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.CleanupMode;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Forked UT: it cannot use jimFS as it runs in child process.
+ */
+public class DefaultForkedMavenInvokerTest extends 
MavenInvokerTestSupport<MavenOptions, ForkedMavenInvokerRequest> {
+
+    @Override
+    protected Invoker<ForkedMavenInvokerRequest> createInvoker() {
+        return new DefaultForkedMavenInvoker();
+    }
+
+    @Override
+    protected Parser<ForkedMavenInvokerRequest> createParser() {
+        return new DefaultForkedMavenParser();
+    }
+
+    @Test
+    void defaultFs(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) 
throws Exception {
+        invoke(tempDir, Arrays.asList("clean", "verify"));
+    }
+}
diff --git a/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java 
b/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/DefaultLocalMavenInvokerTest.java
similarity index 52%
copy from maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
copy to 
maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/DefaultLocalMavenInvokerTest.java
index 60be0e4fa5..96fed74d35 100644
--- a/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
+++ 
b/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/DefaultLocalMavenInvokerTest.java
@@ -16,57 +16,53 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.maven.cling;
+package org.apache.maven.cling.invoker.mvn.local;
 
-import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Path;
+import java.util.Arrays;
 
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
 import org.apache.maven.api.cli.Invoker;
 import org.apache.maven.api.cli.Parser;
 import org.apache.maven.api.cli.mvn.MavenInvokerRequest;
 import org.apache.maven.api.cli.mvn.MavenOptions;
 import org.apache.maven.cling.invoker.ProtoLookup;
-import org.apache.maven.cling.invoker.mvn.local.DefaultLocalMavenInvoker;
-import org.apache.maven.cling.invoker.mvn.local.DefaultLocalMavenParser;
+import org.apache.maven.cling.invoker.mvn.MavenInvokerTestSupport;
 import org.codehaus.plexus.classworlds.ClassWorld;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.CleanupMode;
+import org.junit.jupiter.api.io.TempDir;
 
 /**
- * Maven CLI "new-gen".
+ * Local UT.
  */
-public class MavenCling extends ClingSupport<MavenOptions, 
MavenInvokerRequest<MavenOptions>> {
-    public static final String NAME = "mvn";
-
-    /**
-     * "Normal" Java entry point. Note: Maven uses ClassWorld Launcher and 
this entry point is NOT used under normal
-     * circumstances.
-     */
-    public static void main(String[] args) throws IOException {
-        int exitCode = new MavenCling(NAME).run(args);
-        System.exit(exitCode);
-    }
-
-    /**
-     * ClassWorld Launcher "enhanced" entry point: returning exitCode and 
accepts Class World.
-     */
-    public static int main(String[] args, ClassWorld world) throws IOException 
{
-        return new MavenCling(NAME, world).run(args);
-    }
-
-    public MavenCling(String command) {
-        super(command);
-    }
-
-    public MavenCling(String command, ClassWorld classWorld) {
-        super(command, classWorld);
-    }
-
+public class DefaultLocalMavenInvokerTest
+        extends MavenInvokerTestSupport<MavenOptions, 
MavenInvokerRequest<MavenOptions>> {
     @Override
     protected Invoker<MavenInvokerRequest<MavenOptions>> createInvoker() {
-        return new DefaultLocalMavenInvoker(
-                ProtoLookup.builder().addMapping(ClassWorld.class, 
classWorld).build());
+        return new DefaultLocalMavenInvoker(ProtoLookup.builder()
+                .addMapping(ClassWorld.class, new ClassWorld("plexus.core", 
ClassLoader.getSystemClassLoader()))
+                .build());
     }
 
     @Override
     protected Parser<MavenInvokerRequest<MavenOptions>> createParser() {
         return new DefaultLocalMavenParser();
     }
+
+    @Test
+    void defaultFs(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) 
throws Exception {
+        invoke(tempDir, Arrays.asList("clean", "verify"));
+    }
+
+    @Disabled("Until we move off fully from File")
+    @Test
+    void jimFs() throws Exception {
+        try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) {
+            invoke(fs.getPath("/"), Arrays.asList("clean", "verify"));
+        }
+    }
 }
diff --git 
a/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerTest.java
 
b/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerTest.java
index feb1ecea70..f90794a66a 100644
--- 
a/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerTest.java
+++ 
b/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerTest.java
@@ -18,94 +18,52 @@
  */
 package org.apache.maven.cling.invoker.mvn.resident;
 
-import java.nio.file.Files;
+import java.nio.file.FileSystem;
 import java.nio.file.Path;
+import java.util.Arrays;
 
-import org.apache.maven.api.cli.ParserRequest;
-import org.apache.maven.api.cli.mvn.resident.ResidentMavenParser;
-import org.apache.maven.cling.invoker.ProtoLogger;
+import com.google.common.jimfs.Configuration;
+import com.google.common.jimfs.Jimfs;
+import org.apache.maven.api.cli.Invoker;
+import org.apache.maven.api.cli.Parser;
+import org.apache.maven.api.cli.mvn.resident.ResidentMavenInvokerRequest;
+import org.apache.maven.api.cli.mvn.resident.ResidentMavenOptions;
 import org.apache.maven.cling.invoker.ProtoLookup;
-import org.apache.maven.jline.JLineMessageBuilderFactory;
+import org.apache.maven.cling.invoker.mvn.MavenInvokerTestSupport;
 import org.codehaus.plexus.classworlds.ClassWorld;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.CleanupMode;
 import org.junit.jupiter.api.io.TempDir;
 
-@Disabled("Works ONLY with Maven4!")
-public class DefaultResidentMavenInvokerTest {
-    @Test
-    void smoke(@TempDir Path tempDir) throws Exception {
-        try (ClassWorld classWorld =
-                        new ClassWorld("plexus.core", 
Thread.currentThread().getContextClassLoader());
-                DefaultResidentMavenInvoker invoker = new 
DefaultResidentMavenInvoker(ProtoLookup.builder()
-                        .addMapping(ClassWorld.class, classWorld)
-                        .build())) {
-            ResidentMavenParser parser = new DefaultResidentMavenParser();
-            Files.createDirectory(tempDir.resolve(".mvn"));
-            Path log = tempDir.resolve("build.log").toAbsolutePath();
-
-            String pomString =
-                    """
-                    <?xml version="1.0" encoding="UTF-8"?>
-                    <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 
https://maven.apache.org/maven-v4_0_0.xsd";>
-
-                        <modelVersion>4.0.0</modelVersion>
-
-                        <groupId>org.apache.maven.samples</groupId>
-                        <artifactId>resident-invoker</artifactId>
-                        <version>1.0.0</version>
-
-                        <dependencyManagement>
-                          <dependencies>
-                            <dependency>
-                              <groupId>org.junit</groupId>
-                              <artifactId>junit-bom</artifactId>
-                              <version>5.11.1</version>
-                              <type>pom</type>
-                              <scope>import</scope>
-                            </dependency>
-                          </dependencies>
-                        </dependencyManagement>
-
-                        <dependencies>
-                          <dependency>
-                            <dependency>
-                              <groupId>org.junit.jupiter</groupId>
-                              <artifactId>junit-jupiter-api</artifactId>
-                              <scope>test</scope>
-                            </dependency>
-                          </dependency>
-                        </dependencies>
+/**
+ * Resident UT.
+ */
+public class DefaultResidentMavenInvokerTest
+        extends MavenInvokerTestSupport<ResidentMavenOptions, 
ResidentMavenInvokerRequest> {
 
-                    </project>
-                    """;
-            Path pom = tempDir.resolve("pom.xml").toAbsolutePath();
-            Files.writeString(pom, pomString);
+    @Override
+    protected Invoker<ResidentMavenInvokerRequest> createInvoker() {
+        return new DefaultResidentMavenInvoker(ProtoLookup.builder()
+                .addMapping(ClassWorld.class, new ClassWorld("plexus.core", 
ClassLoader.getSystemClassLoader()))
+                .build());
+    }
 
-            int exitCode;
+    @Override
+    protected Parser<ResidentMavenInvokerRequest> createParser() {
+        return new DefaultResidentMavenParser();
+    }
 
-            exitCode = invoker.invoke(parser.parse(ParserRequest.builder(
-                            "mvn",
-                            new String[] {"-l", log.toString(), "clean"},
-                            new ProtoLogger(),
-                            new JLineMessageBuilderFactory())
-                    .cwd(tempDir)
-                    .build()));
-            System.out.println("1st exit code: " + exitCode);
-            System.out.println("log:");
-            System.out.println(Files.readString(log));
+    @Test
+    void defaultFs(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) 
throws Exception {
+        invoke(tempDir, Arrays.asList("clean", "verify"));
+    }
 
-            exitCode = invoker.invoke(parser.parse(ParserRequest.builder(
-                            "mvn",
-                            new String[] {"-l", log.toString(), "clean"},
-                            new ProtoLogger(),
-                            new JLineMessageBuilderFactory())
-                    .cwd(tempDir)
-                    .build()));
-            System.out.println("2nd exit code: " + exitCode);
-            System.out.println("log:");
-            System.out.println(Files.readString(log));
+    @Disabled("Until we move off fully from File")
+    @Test
+    void jimFs() throws Exception {
+        try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) {
+            invoke(fs.getPath("/"), Arrays.asList("clean", "verify"));
         }
     }
 }

Reply via email to