From 49825e6dbad98a8a9ed037b61e9d324658a7994a Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 17 Dec 2024 10:50:45 +0100 Subject: [PATCH] [MNG-8437] mvnsh (#1982) Maven shell Changes: * (unrelated) contains fix for property handling in embedded executor and in lookup invoker * pulled `-o` (offline) option to "generic" Options from MavenOptions * introduce mvnsh (scripts, options, CLIng main class) * simplified invokers (only one context needed for maven now) * invokers made "reentrant", it all depends HOW context is created Related PRs: * mvnd changes https://github.com/apache/maven-mvnd/pull/1228 --- https://issues.apache.org/jira/browse/MNG-8437 --- apache-maven/src/assembly/component.xml | 1 + apache-maven/src/assembly/maven/bin/mvn | 3 + apache-maven/src/assembly/maven/bin/mvn.cmd | 2 + apache-maven/src/assembly/maven/bin/mvnsh | 30 +++ apache-maven/src/assembly/maven/bin/mvnsh.cmd | 39 ++++ .../org/apache/maven/api/cli/Options.java | 8 + .../apache/maven/api/cli/ParserRequest.java | 28 +++ .../java/org/apache/maven/api/cli/Tools.java | 3 + .../maven/api/cli/mvn/MavenOptions.java | 8 - .../maven/api/cli/mvnsh/ShellOptions.java | 44 ++++ .../org/apache/maven/cling/MavenCling.java | 4 +- .../apache/maven/cling/MavenShellCling.java | 74 ++++++ .../BootstrapCoreExtensionManager.java | 7 +- .../cling/invoker/CommonsCliOptions.java | 18 ++ .../maven/cling/invoker/LayeredOptions.java | 5 + .../maven/cling/invoker/LookupContext.java | 21 +- .../maven/cling/invoker/LookupInvoker.java | 120 ++++++---- .../invoker/mvn/CommonsCliMavenOptions.java | 13 -- .../invoker/mvn/LayeredMavenOptions.java | 5 - .../maven/cling/invoker/mvn/MavenContext.java | 18 +- .../maven/cling/invoker/mvn/MavenInvoker.java | 98 ++++---- .../mvn/forked/ForkedMavenInvoker.java | 220 ------------------ .../mvn/resident/ResidentMavenContext.java | 54 ----- .../mvn/resident/ResidentMavenInvoker.java | 55 +++-- .../cling/invoker/mvnenc/EncryptContext.java | 8 +- .../cling/invoker/mvnenc/EncryptInvoker.java | 34 +-- .../cling/invoker/mvnenc/goals/Decrypt.java | 7 +- .../cling/invoker/mvnenc/goals/Diag.java | 7 +- .../cling/invoker/mvnenc/goals/Encrypt.java | 7 +- .../cling/invoker/mvnenc/goals/Init.java | 218 ++++++++--------- .../invoker/mvnsh/CommonsCliShellOptions.java | 87 +++++++ .../mvnsh/ShellCommandRegistryFactory.java | 26 +++ .../mvnsh/ShellCommandRegistryHolder.java | 62 +++++ .../cling/invoker/mvnsh/ShellInvoker.java | 181 ++++++++++++++ .../invoker/mvnsh/ShellInvokerRequest.java | 78 +++++++ .../cling/invoker/mvnsh/ShellParser.java | 68 ++++++ .../BuiltinShellCommandRegistryFactory.java | 186 +++++++++++++++ .../package-info.java} | 20 +- .../maven/cling/invoker/package-info.java | 12 + ...InvokerTest.java => MavenInvokerTest.java} | 8 +- .../mvn/forked/ForkedMavenInvokerTest.java | 53 ----- .../embedded/EmbeddedMavenExecutor.java | 9 +- impl/maven-jline/pom.xml | 4 + pom.xml | 5 + 44 files changed, 1295 insertions(+), 663 deletions(-) create mode 100644 apache-maven/src/assembly/maven/bin/mvnsh create mode 100644 apache-maven/src/assembly/maven/bin/mvnsh.cmd create mode 100644 api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvnsh/ShellOptions.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/MavenShellCling.java delete mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvoker.java delete mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenContext.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/CommonsCliShellOptions.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellCommandRegistryFactory.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellCommandRegistryHolder.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvoker.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java create mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/builtin/BuiltinShellCommandRegistryFactory.java rename impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/{mvn/local/LocalMavenInvoker.java => mvnsh/package-info.java} (52%) rename impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/{local/LocalMavenInvokerTest.java => MavenInvokerTest.java} (87%) delete mode 100644 impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvokerTest.java diff --git a/apache-maven/src/assembly/component.xml b/apache-maven/src/assembly/component.xml index 94ae1f86ee93..fefc8b49bf13 100644 --- a/apache-maven/src/assembly/component.xml +++ b/apache-maven/src/assembly/component.xml @@ -86,6 +86,7 @@ under the License. mvn mvnenc + mvnsh mvnDebug mvnencDebug diff --git a/apache-maven/src/assembly/maven/bin/mvn b/apache-maven/src/assembly/maven/bin/mvn index 5888451f5f14..6fd203348f43 100755 --- a/apache-maven/src/assembly/maven/bin/mvn +++ b/apache-maven/src/assembly/maven/bin/mvn @@ -207,6 +207,9 @@ handle_args() { --enc) MAVEN_MAIN_CLASS="org.apache.maven.cling.MavenEncCling" ;; + --shell) + MAVEN_MAIN_CLASS="org.apache.maven.cling.MavenShellCling" + ;; *) ;; esac diff --git a/apache-maven/src/assembly/maven/bin/mvn.cmd b/apache-maven/src/assembly/maven/bin/mvn.cmd index dcc413ce4b9b..d64073c400f8 100644 --- a/apache-maven/src/assembly/maven/bin/mvn.cmd +++ b/apache-maven/src/assembly/maven/bin/mvn.cmd @@ -200,6 +200,8 @@ if "%~1"=="--debug" ( set "MAVEN_OPTS=-agentpath:%YJPLIB%=onexit=snapshot,onexit=memory,tracing,onlylocal %MAVEN_OPTS%" ) else if "%~1"=="--enc" ( set "MAVEN_MAIN_CLASS=org.apache.maven.cling.MavenEncCling" +) else if "%~1"=="--shell" ( + set "MAVEN_MAIN_CLASS=org.apache.maven.cling.MavenShellCling" ) exit /b 0 diff --git a/apache-maven/src/assembly/maven/bin/mvnsh b/apache-maven/src/assembly/maven/bin/mvnsh new file mode 100644 index 000000000000..e3f847cdf5b0 --- /dev/null +++ b/apache-maven/src/assembly/maven/bin/mvnsh @@ -0,0 +1,30 @@ +#!/bin/sh + +# 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. + +# ----------------------------------------------------------------------------- +# Apache Maven Encrypt Script +# +# Environment Variable Prerequisites +# +# JAVA_HOME (Optional) Points to a Java installation. +# MAVEN_OPTS (Optional) Java runtime options used when Maven is executed. +# MAVEN_SKIP_RC (Optional) Flag to disable loading of mavenrc files. +# ----------------------------------------------------------------------------- + +"`dirname "$0"`/mvn" --shell "$@" diff --git a/apache-maven/src/assembly/maven/bin/mvnsh.cmd b/apache-maven/src/assembly/maven/bin/mvnsh.cmd new file mode 100644 index 000000000000..1b8ac48f3de6 --- /dev/null +++ b/apache-maven/src/assembly/maven/bin/mvnsh.cmd @@ -0,0 +1,39 @@ +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. + +@REM ----------------------------------------------------------------------------- +@REM Apache Maven Encrypt Script +@REM +@REM Environment Variable Prerequisites +@REM +@REM JAVA_HOME (Optional) Points to a Java installation. +@REM MAVEN_BATCH_ECHO (Optional) Set to 'on' to enable the echoing of the batch commands. +@REM MAVEN_BATCH_PAUSE (Optional) set to 'on' to wait for a key stroke before ending. +@REM MAVEN_OPTS (Optional) Java runtime options used when Maven is executed. +@REM MAVEN_SKIP_RC (Optional) Flag to disable loading of mavenrc files. +@REM ----------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%"=="on" echo %MAVEN_BATCH_ECHO% + +@setlocal + +@call "%~dp0"mvn.cmd --shell %* 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 fe5629d0d728..3b5a355d323b 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 @@ -182,6 +182,14 @@ public interface Options { @Nonnull Optional color(); + /** + * Indicates whether Maven should operate in offline mode. + * + * @return an {@link Optional} containing true if offline mode is enabled, false if disabled, or empty if not specified + */ + @Nonnull + Optional offline(); + /** * Indicates whether to show help information. * 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 a218f23eb952..e462b9842eba 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 @@ -205,6 +205,34 @@ static Builder mvnenc( return builder(Tools.MVNENC_CMD, Tools.MVNENC_NAME, args, logger, messageBuilderFactory); } + /** + * Creates a new Builder instance for constructing a Maven Shell 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 mvnsh( + @Nonnull String[] args, @Nonnull Logger logger, @Nonnull MessageBuilderFactory messageBuilderFactory) { + return mvnsh(Arrays.asList(args), logger, messageBuilderFactory); + } + + /** + * Creates a new Builder instance for constructing a Maven Shell 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 mvnsh( + @Nonnull List args, @Nonnull Logger logger, @Nonnull MessageBuilderFactory messageBuilderFactory) { + return builder(Tools.MVNSHELL_CMD, Tools.MVNSHELL_NAME, args, logger, messageBuilderFactory); + } + /** * Creates a new Builder instance for constructing a ParserRequest. * diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Tools.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Tools.java index b237dc54145b..ce566d271010 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Tools.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Tools.java @@ -36,4 +36,7 @@ private Tools() {} public static final String MVNENC_CMD = "mvnenc"; public static final String MVNENC_NAME = "Maven Password Encrypting Tool"; + + public static final String MVNSHELL_CMD = "mvnsh"; + public static final String MVNSHELL_NAME = "Maven Shell Tool"; } diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvn/MavenOptions.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvn/MavenOptions.java index 4ce75036b8e6..1603e2747f4b 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvn/MavenOptions.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvn/MavenOptions.java @@ -48,14 +48,6 @@ public interface MavenOptions extends Options { @Nonnull Optional alternatePomFile(); - /** - * Indicates whether Maven should operate in offline mode. - * - * @return an {@link Optional} containing true if offline mode is enabled, false if disabled, or empty if not specified - */ - @Nonnull - Optional offline(); - /** * Indicates whether Maven should operate in non-recursive mode (i.e., not build child modules). * diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvnsh/ShellOptions.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvnsh/ShellOptions.java new file mode 100644 index 000000000000..a55d4fd33325 --- /dev/null +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvnsh/ShellOptions.java @@ -0,0 +1,44 @@ +/* + * 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.api.cli.mvnsh; + +import java.util.Collection; +import java.util.Map; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.cli.Options; + +/** + * Defines the options specific to the Maven Shell tool. + * This interface extends the general {@link Options} interface, adding shell-specific configuration options. + * + * @since 4.0.0 + */ +@Experimental +public interface ShellOptions extends Options { + /** + * Returns a new instance of ShellOptions with values interpolated using the given properties. + * + * @param properties a collection of property maps to use for interpolation + * @return a new EncryptOptions instance with interpolated values + */ + @Nonnull + ShellOptions interpolate(Collection> properties); +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java index d4869f2186f3..4bc245b55877 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java @@ -26,8 +26,8 @@ import org.apache.maven.api.cli.ParserRequest; import org.apache.maven.cling.invoker.ProtoLogger; import org.apache.maven.cling.invoker.ProtoLookup; +import org.apache.maven.cling.invoker.mvn.MavenInvoker; import org.apache.maven.cling.invoker.mvn.MavenParser; -import org.apache.maven.cling.invoker.mvn.local.LocalMavenInvoker; import org.apache.maven.jline.JLineMessageBuilderFactory; import org.codehaus.plexus.classworlds.ClassWorld; @@ -61,7 +61,7 @@ public MavenCling(ClassWorld classWorld) { @Override protected Invoker createInvoker() { - return new LocalMavenInvoker( + return new MavenInvoker( ProtoLookup.builder().addMapping(ClassWorld.class, classWorld).build()); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenShellCling.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenShellCling.java new file mode 100644 index 000000000000..b88f6173efcb --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/MavenShellCling.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.cling; + +import java.io.IOException; + +import org.apache.maven.api.cli.Invoker; +import org.apache.maven.api.cli.InvokerRequest; +import org.apache.maven.api.cli.ParserException; +import org.apache.maven.api.cli.ParserRequest; +import org.apache.maven.cling.invoker.ProtoLogger; +import org.apache.maven.cling.invoker.ProtoLookup; +import org.apache.maven.cling.invoker.mvnsh.ShellInvoker; +import org.apache.maven.cling.invoker.mvnsh.ShellParser; +import org.apache.maven.jline.JLineMessageBuilderFactory; +import org.codehaus.plexus.classworlds.ClassWorld; + +/** + * Maven shell. + */ +public class MavenShellCling extends ClingSupport { + /** + * "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 MavenShellCling().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 MavenShellCling(world).run(args); + } + + public MavenShellCling() { + super(); + } + + public MavenShellCling(ClassWorld classWorld) { + super(classWorld); + } + + @Override + protected Invoker createInvoker() { + return new ShellInvoker( + ProtoLookup.builder().addMapping(ClassWorld.class, classWorld).build()); + } + + @Override + protected InvokerRequest parseArguments(String[] args) throws ParserException, IOException { + return new ShellParser() + .parseInvocation(ParserRequest.mvnsh(args, new ProtoLogger(), new JLineMessageBuilderFactory()) + .build()); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java index 064c325ebcf7..b5eb37166bf6 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java @@ -18,9 +18,6 @@ */ package org.apache.maven.cling.extensions; -import javax.inject.Inject; -import javax.inject.Named; - import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -33,7 +30,10 @@ import org.apache.maven.RepositoryUtils; import org.apache.maven.api.Service; import org.apache.maven.api.Session; +import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.cli.extensions.CoreExtension; +import org.apache.maven.api.di.Inject; +import org.apache.maven.api.di.Named; import org.apache.maven.api.model.Plugin; import org.apache.maven.api.services.ArtifactCoordinatesFactory; import org.apache.maven.api.services.ArtifactManager; @@ -80,7 +80,6 @@ import org.eclipse.aether.resolution.DependencyResult; import org.eclipse.aether.util.filter.ExclusionsDependencyFilter; import org.eclipse.aether.util.version.GenericVersionScheme; -import org.eclipse.sisu.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java index 190b7d5862e4..0f8464d61b40 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java @@ -202,6 +202,14 @@ public Optional color() { return Optional.empty(); } + @Override + public Optional offline() { + if (commandLine.hasOption(CLIManager.OFFLINE)) { + return Optional.of(Boolean.TRUE); + } + return Optional.empty(); + } + @Override public Optional help() { if (commandLine.hasOption(CLIManager.HELP)) { @@ -267,11 +275,13 @@ protected static class CLIManager { public static final String LOG_FILE = "l"; public static final String RAW_STREAMS = "raw-streams"; public static final String COLOR = "color"; + public static final String OFFLINE = "o"; public static final String HELP = "h"; // parameters handled by script public static final String DEBUG = "debug"; public static final String ENC = "enc"; + public static final String SHELL = "shell"; public static final String YJP = "yjp"; // deprecated ones @@ -378,6 +388,10 @@ protected void prepareOptions(org.apache.commons.cli.Options options) { .optionalArg(true) .desc("Defines the color mode of the output. Supported are 'auto', 'always', 'never'.") .build()); + options.addOption(Option.builder(OFFLINE) + .longOpt("offline") + .desc("Work offline") + .build()); // Parameters handled by script options.addOption(Option.builder() @@ -388,6 +402,10 @@ protected void prepareOptions(org.apache.commons.cli.Options options) { .longOpt(ENC) .desc("Launch the Maven Encryption tool (script option).") .build()); + options.addOption(Option.builder() + .longOpt(SHELL) + .desc("Launch the Maven Shell tool (script option).") + .build()); options.addOption(Option.builder() .longOpt(YJP) .desc("Launch the JVM with Yourkit profiler (script option).") diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java index c5b9a50ffaf4..4d9d7b6c09ee 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java @@ -132,6 +132,11 @@ public Optional color() { return returnFirstPresentOrEmpty(Options::color); } + @Override + public Optional offline() { + return returnFirstPresentOrEmpty(Options::offline); + } + @Override public Optional help() { return returnFirstPresentOrEmpty(Options::help); diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupContext.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupContext.java index 8c7fd1df5f6c..d487b72f663c 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupContext.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupContext.java @@ -35,6 +35,7 @@ import org.apache.maven.api.services.Lookup; import org.apache.maven.api.settings.Settings; import org.apache.maven.cling.logging.Slf4jConfiguration; +import org.apache.maven.eventspy.internal.EventSpyDispatcher; import org.apache.maven.logging.BuildEventListener; import org.jline.terminal.Terminal; import org.slf4j.ILoggerFactory; @@ -47,14 +48,20 @@ public class LookupContext implements AutoCloseable { public final Function cwdResolver; public final Function installationResolver; public final Function userResolver; + public final boolean containerCapsuleManaged; - protected LookupContext(InvokerRequest invokerRequest) { + public LookupContext(InvokerRequest invokerRequest) { + this(invokerRequest, true); + } + + public LookupContext(InvokerRequest invokerRequest, boolean containerCapsuleManaged) { 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.containerCapsuleManaged = containerCapsuleManaged; this.logger = invokerRequest.parserRequest().logger(); Map user = new HashMap<>(invokerRequest.userProperties()); @@ -84,8 +91,10 @@ protected LookupContext(InvokerRequest invokerRequest) { public Boolean coloredOutput; public Terminal terminal; public Consumer writer; + public ContainerCapsule containerCapsule; public Lookup lookup; + public EventSpyDispatcher eventSpyDispatcher; public BuildEventListener buildEventListener; @@ -93,7 +102,6 @@ protected LookupContext(InvokerRequest invokerRequest) { public Path installationSettingsPath; public Path projectSettingsPath; public Path userSettingsPath; - public boolean interactive; public Path localRepositoryPath; public Settings effectiveSettings; @@ -124,11 +132,18 @@ public void close() throws InvokerException { } } - protected void closeContainer() { + public final void closeContainer() { + if (containerCapsuleManaged) { + doCloseContainer(); + } + } + + public void doCloseContainer() { if (containerCapsule != null) { try { containerCapsule.close(); } finally { + eventSpyDispatcher = null; lookup = null; containerCapsule = null; } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index 348492f06939..95d1ce9cc457 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -24,6 +24,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -34,6 +35,7 @@ import org.apache.maven.api.Constants; import org.apache.maven.api.ProtoSession; +import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.cli.Invoker; import org.apache.maven.api.cli.InvokerException; import org.apache.maven.api.cli.InvokerRequest; @@ -65,6 +67,7 @@ import org.apache.maven.cling.logging.Slf4jConfiguration; import org.apache.maven.cling.logging.Slf4jConfigurationFactory; import org.apache.maven.cling.utils.CLIReportingUtils; +import org.apache.maven.eventspy.internal.EventSpyDispatcher; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.internal.impl.SettingsUtilsV4; import org.apache.maven.jline.FastTerminal; @@ -75,6 +78,7 @@ import org.apache.maven.logging.SimpleBuildEventListener; import org.apache.maven.logging.api.LogLevelRecorder; import org.apache.maven.slf4j.MavenSimpleLogger; +import org.codehaus.plexus.PlexusContainer; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; import org.jline.terminal.impl.AbstractPosixTerminal; @@ -92,19 +96,27 @@ * @param The context type. */ public abstract class LookupInvoker implements Invoker { - protected final ProtoLookup protoLookup; + protected final Lookup protoLookup; - public LookupInvoker(ProtoLookup protoLookup) { + @Nullable + protected final Consumer contextConsumer; + + public LookupInvoker(Lookup protoLookup, @Nullable Consumer contextConsumer) { this.protoLookup = requireNonNull(protoLookup); + this.contextConsumer = contextConsumer; } @Override public int invoke(InvokerRequest invokerRequest) throws InvokerException { requireNonNull(invokerRequest); - Properties oldProps = (Properties) System.getProperties().clone(); + Properties oldProps = new Properties(); + oldProps.putAll(System.getProperties()); ClassLoader oldCL = Thread.currentThread().getContextClassLoader(); try (C context = createContext(invokerRequest)) { + if (contextConsumer != null) { + contextConsumer.accept(context); + } try { if (context.containerCapsule != null && context.containerCapsule.currentThreadClassLoader().isPresent()) { @@ -128,8 +140,6 @@ public int invoke(InvokerRequest invokerRequest) throws InvokerException { protected int doInvoke(C context) throws Exception { pushCoreProperties(context); pushUserProperties(context); - validate(context); - prepare(context); configureLogging(context); createTerminal(context); activateLogging(context); @@ -192,10 +202,6 @@ protected void pushUserProperties(C context) throws Exception { } } - protected void validate(C context) throws Exception {} - - protected void prepare(C context) throws Exception {} - protected void configureLogging(C context) throws Exception { // LOG COLOR Options mavenOptions = context.invokerRequest.options(); @@ -251,33 +257,40 @@ protected BuildEventListener doDetermineBuildEventListener(C context) { } protected void createTerminal(C context) { - MessageUtils.systemInstall( - builder -> { - builder.streams( - context.invokerRequest.in().orElse(null), - context.invokerRequest.out().orElse(null)); - builder.systemOutput(TerminalBuilder.SystemOutput.ForcedSysOut); - // The exec builder suffers from https://github.com/jline/jline3/issues/1098 - // We could re-enable it when fixed to provide support for non-standard architectures, - // for which JLine does not provide any native library. - builder.exec(false); - if (context.coloredOutput != null) { - builder.color(context.coloredOutput); - } - }, - terminal -> doConfigureWithTerminal(context, terminal)); - - context.terminal = MessageUtils.getTerminal(); - // JLine is quite slow to start due to the native library unpacking and loading - // so boot it asynchronously - context.closeables.add(MessageUtils::systemUninstall); - MessageUtils.registerShutdownHook(); // safety belt - if (context.coloredOutput != null) { - MessageUtils.setColorEnabled(context.coloredOutput); + if (context.terminal == null) { + MessageUtils.systemInstall( + builder -> { + builder.streams( + context.invokerRequest.in().orElse(null), + context.invokerRequest.out().orElse(null)); + builder.systemOutput(TerminalBuilder.SystemOutput.ForcedSysOut); + // The exec builder suffers from https://github.com/jline/jline3/issues/1098 + // We could re-enable it when fixed to provide support for non-standard architectures, + // for which JLine does not provide any native library. + builder.exec(false); + if (context.coloredOutput != null) { + builder.color(context.coloredOutput); + } + }, + terminal -> doConfigureWithTerminal(context, terminal)); + + context.terminal = MessageUtils.getTerminal(); + // JLine is quite slow to start due to the native library unpacking and loading + // so boot it asynchronously + context.closeables.add(MessageUtils::systemUninstall); + MessageUtils.registerShutdownHook(); // safety belt + if (context.coloredOutput != null) { + MessageUtils.setColorEnabled(context.coloredOutput); + } + } else { + if (context.coloredOutput != null) { + MessageUtils.setColorEnabled(context.coloredOutput); + } } } protected void doConfigureWithTerminal(C context, Terminal terminal) { + context.terminal = terminal; Options options = context.invokerRequest.options(); if (options.rawStreams().isEmpty() || !options.rawStreams().get()) { MavenSimpleLogger stdout = (MavenSimpleLogger) context.loggerFactory.getLogger("stdout"); @@ -406,15 +419,13 @@ protected void preCommands(C context) throws Exception { } protected void container(C context) throws Exception { - context.containerCapsule = createContainerCapsuleFactory().createContainerCapsule(this, context); - context.closeables.add(context::closeContainer); - context.lookup = context.containerCapsule.getLookup(); - - // refresh logger in case container got customized by spy - org.slf4j.Logger l = context.loggerFactory.getLogger(this.getClass().getName()); - context.logger = (level, message, error) -> l.atLevel(org.slf4j.event.Level.valueOf(level.name())) - .setCause(error) - .log(message); + if (context.lookup == null) { + context.containerCapsule = createContainerCapsuleFactory().createContainerCapsule(this, context); + context.closeables.add(context::closeContainer); + context.lookup = context.containerCapsule.getLookup(); + } else { + context.containerCapsule.updateLogging(context); + } } protected ContainerCapsuleFactory createContainerCapsuleFactory() { @@ -434,9 +445,22 @@ protected void postContainer(C context) throws Exception { context.protoSession = protoSession; } - protected void lookup(C context) throws Exception {} + protected void lookup(C context) throws Exception { + if (context.eventSpyDispatcher == null) { + context.eventSpyDispatcher = context.lookup.lookup(EventSpyDispatcher.class); + } + } - protected void init(C context) throws Exception {} + protected void init(C context) throws Exception { + InvokerRequest invokerRequest = context.invokerRequest; + Map data = new HashMap<>(); + data.put("plexus", context.lookup.lookup(PlexusContainer.class)); + data.put("workingDirectory", invokerRequest.cwd().toString()); + data.put("systemProperties", toProperties(context.protoSession.getSystemProperties())); + data.put("userProperties", toProperties(context.protoSession.getUserProperties())); + data.put("versionProperties", CLIReportingUtils.getBuildProperties()); + context.eventSpyDispatcher.init(() -> data); + } protected void postCommands(C context) throws Exception { InvokerRequest invokerRequest = context.invokerRequest; @@ -465,7 +489,9 @@ protected void postCommands(C context) throws Exception { } protected void settings(C context) throws Exception { - settings(context, true, context.lookup.lookup(SettingsBuilder.class)); + if (context.effectiveSettings == null) { + settings(context, true, context.lookup.lookup(SettingsBuilder.class)); + } } /** @@ -553,6 +579,9 @@ protected void settings(C context, boolean emitSettingsWarnings, SettingsBuilder .build(); customizeSettingsRequest(context, settingsRequest); + if (context.eventSpyDispatcher != null) { + context.eventSpyDispatcher.onEvent(settingsRequest); + } context.logger.debug("Reading installation settings from '" + installationSettingsFile + "'"); context.logger.debug("Reading project settings from '" + projectSettingsFile + "'"); @@ -560,6 +589,9 @@ protected void settings(C context, boolean emitSettingsWarnings, SettingsBuilder SettingsBuilderResult settingsResult = settingsBuilder.build(settingsRequest); customizeSettingsResult(context, settingsResult); + if (context.eventSpyDispatcher != null) { + context.eventSpyDispatcher.onEvent(settingsResult); + } context.effectiveSettings = settingsResult.getEffectiveSettings(); context.interactive = mayDisableInteractiveMode(context, context.effectiveSettings.isInteractiveMode()); diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/CommonsCliMavenOptions.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/CommonsCliMavenOptions.java index 610450715624..33ff742d6ba4 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/CommonsCliMavenOptions.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/CommonsCliMavenOptions.java @@ -79,14 +79,6 @@ public Optional alternatePomFile() { return Optional.empty(); } - @Override - public Optional offline() { - if (commandLine.hasOption(CLIManager.OFFLINE)) { - return Optional.of(Boolean.TRUE); - } - return Optional.empty(); - } - @Override public Optional nonRecursive() { if (commandLine.hasOption(CLIManager.NON_RECURSIVE)) { @@ -263,7 +255,6 @@ public MavenOptions interpolate(Collection> properties) { protected static class CLIManager extends CommonsCliOptions.CLIManager { public static final String ALTERNATE_POM_FILE = "f"; - public static final String OFFLINE = "o"; public static final String NON_RECURSIVE = "N"; public static final String UPDATE_SNAPSHOTS = "U"; public static final String ACTIVATE_PROFILES = "P"; @@ -293,10 +284,6 @@ protected void prepareOptions(org.apache.commons.cli.Options options) { .hasArg() .desc("Force the use of an alternate POM file (or directory with pom.xml)") .build()); - options.addOption(Option.builder(OFFLINE) - .longOpt("offline") - .desc("Work offline") - .build()); options.addOption(Option.builder(NON_RECURSIVE) .longOpt("non-recursive") .desc( diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/LayeredMavenOptions.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/LayeredMavenOptions.java index b06675780e1a..79c25b8fd42c 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/LayeredMavenOptions.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/LayeredMavenOptions.java @@ -54,11 +54,6 @@ public Optional alternatePomFile() { return returnFirstPresentOrEmpty(MavenOptions::alternatePomFile); } - @Override - public Optional offline() { - return returnFirstPresentOrEmpty(MavenOptions::offline); - } - @Override public Optional nonRecursive() { return returnFirstPresentOrEmpty(MavenOptions::nonRecursive); diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java index 18f61aea36cf..9f364ba59319 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java @@ -21,21 +21,25 @@ import org.apache.maven.Maven; import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.cling.invoker.LookupContext; -import org.apache.maven.eventspy.internal.EventSpyDispatcher; @SuppressWarnings("VisibilityModifier") public class MavenContext extends LookupContext { public MavenContext(InvokerRequest invokerRequest) { - super(invokerRequest); + this(invokerRequest, true); + } + + public MavenContext(InvokerRequest invokerRequest, boolean containerCapsuleManaged) { + super(invokerRequest, containerCapsuleManaged); } - public EventSpyDispatcher eventSpyDispatcher; public Maven maven; @Override - protected void closeContainer() { - eventSpyDispatcher = null; - maven = null; - super.closeContainer(); + public void doCloseContainer() { + try { + super.doCloseContainer(); + } finally { + maven = null; + } } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java index 6a6c2a9d27f9..50291321709c 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java @@ -22,10 +22,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -33,28 +33,26 @@ import org.apache.maven.Maven; import org.apache.maven.api.Constants; import org.apache.maven.api.MonotonicClock; +import org.apache.maven.api.annotations.Nullable; +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.mvn.MavenOptions; import org.apache.maven.api.services.BuilderProblem; import org.apache.maven.api.services.Lookup; -import org.apache.maven.api.services.SettingsBuilderRequest; -import org.apache.maven.api.services.SettingsBuilderResult; import org.apache.maven.api.services.Source; import org.apache.maven.api.services.ToolchainsBuilder; import org.apache.maven.api.services.ToolchainsBuilderRequest; import org.apache.maven.api.services.ToolchainsBuilderResult; import org.apache.maven.api.services.model.ModelProcessor; import org.apache.maven.cling.event.ExecutionEventLogger; +import org.apache.maven.cling.invoker.LookupContext; import org.apache.maven.cling.invoker.LookupInvoker; -import org.apache.maven.cling.invoker.ProtoLookup; import org.apache.maven.cling.invoker.Utils; import org.apache.maven.cling.transfer.ConsoleMavenTransferListener; import org.apache.maven.cling.transfer.QuietMavenTransferListener; import org.apache.maven.cling.transfer.SimplexTransferListener; import org.apache.maven.cling.transfer.Slf4jMavenTransferListener; -import org.apache.maven.cling.utils.CLIReportingUtils; -import org.apache.maven.eventspy.internal.EventSpyDispatcher; import org.apache.maven.exception.DefaultExceptionHandler; import org.apache.maven.exception.ExceptionHandler; import org.apache.maven.exception.ExceptionSummary; @@ -70,25 +68,30 @@ import org.apache.maven.logging.LoggingExecutionListener; import org.apache.maven.logging.MavenTransferListener; import org.apache.maven.project.MavenProject; -import org.codehaus.plexus.PlexusContainer; import org.eclipse.aether.DefaultRepositoryCache; import org.eclipse.aether.transfer.TransferListener; import static java.util.Comparator.comparing; -import static org.apache.maven.cling.invoker.Utils.toProperties; /** - * The "local" Maven invoker, that expects whole Maven on classpath and invokes it. - * - * @param The context type. + * The Maven invoker, that expects whole Maven on classpath and invokes it. */ -public abstract class MavenInvoker extends LookupInvoker { - public MavenInvoker(ProtoLookup protoLookup) { - super(protoLookup); +public class MavenInvoker extends LookupInvoker { + public MavenInvoker(Lookup protoLookup) { + this(protoLookup, null); + } + + public MavenInvoker(Lookup protoLookup, @Nullable Consumer contextConsumer) { + super(protoLookup, contextConsumer); + } + + @Override + protected MavenContext createContext(InvokerRequest invokerRequest) throws InvokerException { + return new MavenContext(invokerRequest); } @Override - protected int execute(C context) throws Exception { + protected int execute(MavenContext context) throws Exception { MavenExecutionRequest request = prepareMavenExecutionRequest(); toolchains(context, request); populateRequest(context, context.lookup, request); @@ -113,26 +116,15 @@ protected MavenExecutionRequest prepareMavenExecutionRequest() throws Exception } @Override - protected void lookup(C context) throws Exception { - context.eventSpyDispatcher = context.lookup.lookup(EventSpyDispatcher.class); - context.maven = context.lookup.lookup(Maven.class); - } - - @Override - protected void init(C context) throws Exception { - super.init(context); - InvokerRequest invokerRequest = context.invokerRequest; - Map data = new HashMap<>(); - data.put("plexus", context.lookup.lookup(PlexusContainer.class)); - data.put("workingDirectory", invokerRequest.cwd().toString()); - data.put("systemProperties", toProperties(context.protoSession.getSystemProperties())); - data.put("userProperties", toProperties(context.protoSession.getUserProperties())); - data.put("versionProperties", CLIReportingUtils.getBuildProperties()); - context.eventSpyDispatcher.init(() -> data); + protected void lookup(MavenContext context) throws Exception { + if (context.maven == null) { + super.lookup(context); + context.maven = context.lookup.lookup(Maven.class); + } } @Override - protected void postCommands(C context) throws Exception { + protected void postCommands(MavenContext context) throws Exception { super.postCommands(context); InvokerRequest invokerRequest = context.invokerRequest; @@ -145,21 +137,7 @@ protected void postCommands(C context) throws Exception { } } - @Override - protected void customizeSettingsRequest(C context, SettingsBuilderRequest settingsBuilderRequest) throws Exception { - if (context.eventSpyDispatcher != null) { - context.eventSpyDispatcher.onEvent(settingsBuilderRequest); - } - } - - @Override - protected void customizeSettingsResult(C context, SettingsBuilderResult settingsBuilderResult) throws Exception { - if (context.eventSpyDispatcher != null) { - context.eventSpyDispatcher.onEvent(settingsBuilderResult); - } - } - - protected void toolchains(C context, MavenExecutionRequest request) throws Exception { + protected void toolchains(MavenContext context, MavenExecutionRequest request) throws Exception { Path userToolchainsFile = null; if (context.invokerRequest.options().altUserToolchains().isPresent()) { userToolchainsFile = context.cwdResolver.apply( @@ -240,7 +218,8 @@ protected void toolchains(C context, MavenExecutionRequest request) throws Excep } @Override - protected void populateRequest(C context, Lookup lookup, MavenExecutionRequest request) throws Exception { + protected void populateRequest(MavenContext context, Lookup lookup, MavenExecutionRequest request) + throws Exception { super.populateRequest(context, lookup, request); if (context.invokerRequest.rootDirectory().isEmpty()) { // maven requires this to be set; so default it (and see below at POM) @@ -322,7 +301,7 @@ protected void populateRequest(C context, Lookup lookup, MavenExecutionRequest r } } - protected Path determinePom(C context, Lookup lookup) { + protected Path determinePom(MavenContext context, Lookup lookup) { Path current = context.invokerRequest.cwd(); MavenOptions options = (MavenOptions) context.invokerRequest.options(); if (options.alternatePomFile().isPresent()) { @@ -337,7 +316,7 @@ protected Path determinePom(C context, Lookup lookup) { } } - protected String determineReactorFailureBehaviour(C context) { + protected String determineReactorFailureBehaviour(MavenContext context) { MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options(); if (mavenOptions.failFast().isPresent()) { return MavenExecutionRequest.REACTOR_FAIL_FAST; @@ -350,7 +329,7 @@ protected String determineReactorFailureBehaviour(C context) { } } - protected String determineGlobalChecksumPolicy(C context) { + protected String determineGlobalChecksumPolicy(MavenContext context) { MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options(); if (mavenOptions.strictChecksums().orElse(false)) { return MavenExecutionRequest.CHECKSUM_POLICY_FAIL; @@ -361,7 +340,7 @@ protected String determineGlobalChecksumPolicy(C context) { } } - protected ExecutionListener determineExecutionListener(C context) { + protected ExecutionListener determineExecutionListener(MavenContext context) { ExecutionListener listener = new ExecutionEventLogger(context.invokerRequest.messageBuilderFactory()); if (context.eventSpyDispatcher != null) { listener = context.eventSpyDispatcher.chainListener(listener); @@ -369,7 +348,7 @@ protected ExecutionListener determineExecutionListener(C context) { return new LoggingExecutionListener(listener, determineBuildEventListener(context)); } - protected TransferListener determineTransferListener(C context, boolean noTransferProgress) { + protected TransferListener determineTransferListener(MavenContext context, boolean noTransferProgress) { boolean quiet = context.invokerRequest.options().quiet().orElse(false); boolean logFile = context.invokerRequest.options().logFile().isPresent(); boolean runningOnCI = isRunningOnCI(context); @@ -390,7 +369,7 @@ protected TransferListener determineTransferListener(C context, boolean noTransf return new MavenTransferListener(delegate, determineBuildEventListener(context)); } - protected String determineMakeBehavior(C context) { + protected String determineMakeBehavior(MavenContext context) { MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options(); if (mavenOptions.alsoMake().isPresent() && mavenOptions.alsoMakeDependents().isEmpty()) { @@ -406,7 +385,7 @@ protected String determineMakeBehavior(C context) { } } - protected void performProjectActivation(C context, ProjectActivation projectActivation) { + protected void performProjectActivation(MavenContext context, ProjectActivation projectActivation) { MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options(); if (mavenOptions.projects().isPresent() && !mavenOptions.projects().get().isEmpty()) { @@ -434,7 +413,7 @@ protected void performProjectActivation(C context, ProjectActivation projectActi } } - protected void performProfileActivation(C context, ProfileActivation profileActivation) { + protected void performProfileActivation(MavenContext context, ProfileActivation profileActivation) { MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options(); if (mavenOptions.activatedProfiles().isPresent() && !mavenOptions.activatedProfiles().get().isEmpty()) { @@ -462,7 +441,7 @@ protected void performProfileActivation(C context, ProfileActivation profileActi } } - protected int doExecute(C context, MavenExecutionRequest request) throws Exception { + protected int doExecute(MavenContext context, MavenExecutionRequest request) throws Exception { context.eventSpyDispatcher.onEvent(request); MavenExecutionResult result; @@ -534,7 +513,7 @@ protected int doExecute(C context, MavenExecutionRequest request) throws Excepti } } - protected void logBuildResumeHint(C context, String resumeBuildHint) { + protected void logBuildResumeHint(MavenContext context, String resumeBuildHint) { context.logger.error(""); context.logger.error("After correcting the problems, you can resume the build with the command"); context.logger.error( @@ -577,7 +556,8 @@ protected String getResumeFromSelector(List mavenProjects, MavenPr protected static final String ANSI_RESET = "\u001B\u005Bm"; - protected void logSummary(C context, ExceptionSummary summary, Map references, String indent) { + protected void logSummary( + MavenContext context, ExceptionSummary summary, Map references, String indent) { String referenceKey = ""; if (summary.getReference() != null && !summary.getReference().isEmpty()) { diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvoker.java deleted file mode 100644 index 29ad880a2f68..000000000000 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvoker.java +++ /dev/null @@ -1,220 +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.mvn.forked; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Map; - -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.mvn.MavenOptions; -import org.apache.maven.internal.impl.model.profile.Os; - -import static java.util.Objects.requireNonNull; - -/** - * Forked invoker implementation, that spawns a subprocess with Maven from the installation directory. - */ -public class ForkedMavenInvoker implements Invoker { - @SuppressWarnings("MethodLength") - @Override - public int invoke(InvokerRequest invokerRequest) throws InvokerException { - requireNonNull(invokerRequest); - validate(invokerRequest); - - ArrayList cmdAndArguments = new ArrayList<>(); - cmdAndArguments.add(invokerRequest - .installationDirectory() - .resolve("bin") - .resolve( - Os.IS_WINDOWS - ? invokerRequest.parserRequest().command() + ".cmd" - : invokerRequest.parserRequest().command()) - .toString()); - - MavenOptions mavenOptions = (MavenOptions) invokerRequest.options(); - if (mavenOptions.userProperties().isPresent()) { - for (Map.Entry entry : - mavenOptions.userProperties().get().entrySet()) { - cmdAndArguments.add("-D" + entry.getKey() + "=" + entry.getValue()); - } - } - if (mavenOptions.showVersionAndExit().orElse(false)) { - cmdAndArguments.add("--version"); - } - if (mavenOptions.showVersion().orElse(false)) { - cmdAndArguments.add("--show-version"); - } - if (mavenOptions.quiet().orElse(false)) { - cmdAndArguments.add("--quiet"); - } - if (mavenOptions.verbose().orElse(false)) { - cmdAndArguments.add("--verbose"); - } - if (mavenOptions.showErrors().orElse(false)) { - cmdAndArguments.add("--errors"); - } - if (mavenOptions.failOnSeverity().isPresent()) { - cmdAndArguments.add("--fail-on-severity"); - cmdAndArguments.add(mavenOptions.failOnSeverity().get()); - } - if (mavenOptions.nonInteractive().orElse(false)) { - 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("--log-file"); - cmdAndArguments.add(mavenOptions.logFile().get()); - } - 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())); - - try { - ProcessBuilder pb = new ProcessBuilder() - .directory(invokerRequest.cwd().toFile()) - .command(cmdAndArguments); - - if (invokerRequest.jvmArguments().isPresent()) { - pb.environment() - .put( - "MAVEN_OPTS", - String.join(" ", invokerRequest.jvmArguments().get())); - } - - return pb.start().waitFor(); - } catch (IOException e) { - invokerRequest.logger().error("IO problem while executing command: " + cmdAndArguments, e); - return 127; - } catch (InterruptedException e) { - invokerRequest.logger().error("Interrupted while executing command: " + cmdAndArguments, e); - return 127; - } - } - - protected void validate(InvokerRequest invokerRequest) throws InvokerException {} -} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenContext.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenContext.java deleted file mode 100644 index fb1e2ed7ea83..000000000000 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenContext.java +++ /dev/null @@ -1,54 +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.mvn.resident; - -import org.apache.maven.api.cli.InvokerException; -import org.apache.maven.api.cli.InvokerRequest; -import org.apache.maven.cling.invoker.mvn.MavenContext; - -public class ResidentMavenContext extends MavenContext { - - protected ResidentMavenContext(InvokerRequest invokerRequest) { - super(invokerRequest); - } - - @Override - protected void closeContainer() { - // we are resident; we do not shut down here - } - - public void shutDown() throws InvokerException { - super.closeContainer(); - } - - public ResidentMavenContext copy(InvokerRequest invokerRequest) { - if (invokerRequest == this.invokerRequest) { - return this; - } - ResidentMavenContext shadow = new ResidentMavenContext(invokerRequest); - - // we carry over only "resident" things - shadow.containerCapsule = containerCapsule; - shadow.lookup = lookup; - shadow.eventSpyDispatcher = eventSpyDispatcher; - shadow.maven = maven; - - return shadow; - } -} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvoker.java index 41d57395e7d8..fe4c9cb9ed94 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvoker.java @@ -23,29 +23,32 @@ import org.apache.maven.api.cli.InvokerException; import org.apache.maven.api.cli.InvokerRequest; -import org.apache.maven.cling.invoker.ProtoLookup; +import org.apache.maven.api.services.Lookup; +import org.apache.maven.cling.invoker.mvn.MavenContext; import org.apache.maven.cling.invoker.mvn.MavenInvoker; /** - * Resident invoker implementation, similar to "local", but keeps Maven instance resident. This implies, that + * Resident invoker implementation, specialization of Maven Invoker, but keeps Maven instance resident. This implies, that * things like environment, system properties, extensions etc. are loaded only once. It is caller duty to ensure * that subsequent call is right for the resident instance (ie no env change or different extension needed). + * This implementation "pre-populates" MavenContext with pre-existing stuff (except for very first call) + * and does not let DI container to be closed. */ -public class ResidentMavenInvoker extends MavenInvoker { +public class ResidentMavenInvoker extends MavenInvoker { - private final ConcurrentHashMap residentContext; + private final ConcurrentHashMap residentContext; - public ResidentMavenInvoker(ProtoLookup protoLookup) { - super(protoLookup); + public ResidentMavenInvoker(Lookup protoLookup) { + super(protoLookup, null); this.residentContext = new ConcurrentHashMap<>(); } @Override public void close() throws InvokerException { ArrayList exceptions = new ArrayList<>(); - for (ResidentMavenContext context : residentContext.values()) { + for (MavenContext context : residentContext.values()) { try { - context.shutDown(); + context.doCloseContainer(); } catch (InvokerException e) { exceptions.add(e); } @@ -58,31 +61,25 @@ public void close() throws InvokerException { } @Override - protected ResidentMavenContext createContext(InvokerRequest invokerRequest) { - return residentContext - .computeIfAbsent(getContextId(invokerRequest), k -> new ResidentMavenContext(invokerRequest)) - .copy(invokerRequest); - } - - protected String getContextId(InvokerRequest invokerRequest) { + protected MavenContext createContext(InvokerRequest 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"; + // and allow multiple instances per JVM, this may become a pool? derive key based in invokerRequest? + MavenContext result = residentContext.computeIfAbsent("resident", k -> new MavenContext(invokerRequest, false)); + return copyIfDifferent(result, invokerRequest); } - @Override - protected void container(ResidentMavenContext context) throws Exception { - if (context.containerCapsule == null) { - super.container(context); - } else { - context.containerCapsule.updateLogging(context); + protected MavenContext copyIfDifferent(MavenContext mavenContext, InvokerRequest invokerRequest) { + if (invokerRequest == mavenContext.invokerRequest) { + return mavenContext; } - } + MavenContext shadow = new MavenContext(invokerRequest, false); - @Override - protected void lookup(ResidentMavenContext context) throws Exception { - if (context.maven == null) { - super.lookup(context); - } + // we carry over only "resident" things + shadow.containerCapsule = mavenContext.containerCapsule; + shadow.lookup = mavenContext.lookup; + shadow.eventSpyDispatcher = mavenContext.eventSpyDispatcher; + shadow.maven = mavenContext.maven; + + return shadow; } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptContext.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptContext.java index 7f1e97abcbaa..1e3f22514823 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptContext.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptContext.java @@ -30,8 +30,12 @@ @SuppressWarnings("VisibilityModifier") public class EncryptContext extends LookupContext { - protected EncryptContext(InvokerRequest invokerRequest) { - super(invokerRequest); + public EncryptContext(InvokerRequest invokerRequest) { + this(invokerRequest, true); + } + + public EncryptContext(InvokerRequest invokerRequest, boolean containerCapsuleManaged) { + super(invokerRequest, containerCapsuleManaged); } public Map goals; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvoker.java index 1c52752377ac..ac55d48eccb0 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvoker.java @@ -20,11 +20,14 @@ import java.io.InterruptedIOException; import java.util.ArrayList; +import java.util.function.Consumer; +import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.api.cli.mvnenc.EncryptOptions; +import org.apache.maven.api.services.Lookup; +import org.apache.maven.cling.invoker.LookupContext; import org.apache.maven.cling.invoker.LookupInvoker; -import org.apache.maven.cling.invoker.ProtoLookup; import org.apache.maven.cling.utils.CLIReportingUtils; import org.jline.reader.LineReaderBuilder; import org.jline.reader.UserInterruptException; @@ -37,13 +40,17 @@ */ public class EncryptInvoker extends LookupInvoker { - public EncryptInvoker(ProtoLookup protoLookup) { - super(protoLookup); + public static final int OK = 0; // OK + public static final int ERROR = 1; // "generic" error + public static final int BAD_OPERATION = 2; // bad user input or alike + public static final int CANCELED = 3; // user canceled + + public EncryptInvoker(Lookup protoLookup) { + this(protoLookup, null); } - @Override - protected int execute(EncryptContext context) throws Exception { - return doExecute(context); + public EncryptInvoker(Lookup protoLookup, @Nullable Consumer contextConsumer) { + super(protoLookup, contextConsumer); } @Override @@ -52,16 +59,15 @@ protected EncryptContext createContext(InvokerRequest invokerRequest) { } @Override - protected void lookup(EncryptContext context) { - context.goals = context.lookup.lookupMap(Goal.class); + protected void lookup(EncryptContext context) throws Exception { + if (context.goals == null) { + super.lookup(context); + context.goals = context.lookup.lookupMap(Goal.class); + } } - public static final int OK = 0; // OK - public static final int ERROR = 1; // "generic" error - public static final int BAD_OPERATION = 2; // bad user input or alike - public static final int CANCELED = 3; // user canceled - - protected int doExecute(EncryptContext context) throws Exception { + @Override + protected int execute(EncryptContext context) throws Exception { try { context.header = new ArrayList<>(); context.style = new AttributedStyle(); diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Decrypt.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Decrypt.java index e58ac80bcd9b..c5e42699e2fa 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Decrypt.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Decrypt.java @@ -18,10 +18,9 @@ */ package org.apache.maven.cling.invoker.mvnenc.goals; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - +import org.apache.maven.api.di.Inject; +import org.apache.maven.api.di.Named; +import org.apache.maven.api.di.Singleton; import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.cling.invoker.mvnenc.EncryptContext; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Diag.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Diag.java index f6271d15cb0d..ab845de22398 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Diag.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Diag.java @@ -18,10 +18,9 @@ */ package org.apache.maven.cling.invoker.mvnenc.goals; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - +import org.apache.maven.api.di.Inject; +import org.apache.maven.api.di.Named; +import org.apache.maven.api.di.Singleton; import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.cling.invoker.mvnenc.EncryptContext; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Encrypt.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Encrypt.java index bbe77f54a002..2bfe2c01a553 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Encrypt.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Encrypt.java @@ -18,10 +18,9 @@ */ package org.apache.maven.cling.invoker.mvnenc.goals; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - +import org.apache.maven.api.di.Inject; +import org.apache.maven.api.di.Named; +import org.apache.maven.api.di.Singleton; import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.cling.invoker.mvnenc.EncryptContext; import org.codehaus.plexus.components.secdispatcher.SecDispatcher; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Init.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Init.java index ddaeed28463f..18f15ca923b3 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Init.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Init.java @@ -18,15 +18,17 @@ */ package org.apache.maven.cling.invoker.mvnenc.goals; -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Singleton; - +import java.io.IOError; +import java.io.InterruptedIOException; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import org.apache.maven.api.cli.mvnenc.EncryptOptions; +import org.apache.maven.api.di.Inject; +import org.apache.maven.api.di.Named; +import org.apache.maven.api.di.Singleton; import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.cling.invoker.mvnenc.EncryptContext; import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; @@ -40,14 +42,12 @@ import org.jline.consoleui.prompt.PromptResultItemIF; import org.jline.consoleui.prompt.builder.ListPromptBuilder; import org.jline.consoleui.prompt.builder.PromptBuilder; -import org.jline.reader.Candidate; -import org.jline.reader.Completer; -import org.jline.reader.LineReader; -import org.jline.reader.ParsedLine; +import org.jline.reader.UserInterruptException; import org.jline.utils.Colors; import org.jline.utils.OSUtils; import static org.apache.maven.cling.invoker.mvnenc.EncryptInvoker.BAD_OPERATION; +import static org.apache.maven.cling.invoker.mvnenc.EncryptInvoker.CANCELED; import static org.apache.maven.cling.invoker.mvnenc.EncryptInvoker.OK; /** @@ -63,6 +63,7 @@ public Init(MessageBuilderFactory messageBuilderFactory, SecDispatcher secDispat super(messageBuilderFactory, secDispatcher); } + @SuppressWarnings("MethodLength") @Override public int doExecute(EncryptContext context) throws Exception { EncryptOptions options = (EncryptOptions) context.invokerRequest.options(); @@ -87,125 +88,128 @@ public int doExecute(EncryptContext context) throws Exception { promptConfig = new ConsolePrompt.UiConfig("❯", "◯ ", "◉ ", "◯ "); } promptConfig.setCancellableFirstPrompt(true); - ConsolePrompt prompt = new ConsolePrompt(context.reader, context.terminal, promptConfig); SettingsSecurity config = secDispatcher.readConfiguration(true); - // reset config config.setDefaultDispatcher(null); config.getConfigurations().clear(); - Map result = prompt.prompt( - context.header, dispatcherPrompt(prompt.getPromptBuilder()).build()); - if (result == null) { - throw new InterruptedException(); - } - if (NONE.equals(result.get("defaultDispatcher").getResult())) { - context.terminal - .writer() - .println(messageBuilderFactory - .builder() - .warning( - "Maven4 SecDispatcher disabled; Maven3 fallback may still work, use `mvnenc diag` to check") - .build()); - secDispatcher.writeConfiguration(config); - return OK; - } - config.setDefaultDispatcher(result.get("defaultDispatcher").getResult()); + try (ConsolePrompt prompt = new ConsolePrompt(context.reader, context.terminal, promptConfig)) { + Map dispatcherResult = new HashMap<>(); + Map dispatcherConfigResult = new HashMap<>(); + Map confirmChoice = new HashMap<>(); - DispatcherMeta meta = secDispatcher.availableDispatchers().stream() - .filter(d -> Objects.equals(config.getDefaultDispatcher(), d.name())) - .findFirst() - .orElseThrow(); - if (!meta.fields().isEmpty()) { - result = prompt.prompt( - context.header, - configureDispatcher(context, meta, prompt.getPromptBuilder()) - .build()); - if (result == null) { + prompt.prompt( + context.header, dispatcherPrompt(prompt.getPromptBuilder()).build(), dispatcherResult); + if (dispatcherResult.isEmpty()) { throw new InterruptedException(); } + if (NONE.equals(dispatcherResult.get("defaultDispatcher").getResult())) { + context.terminal + .writer() + .println(messageBuilderFactory + .builder() + .warning( + "Maven4 SecDispatcher disabled; Maven3 fallback may still work, use `mvnenc diag` to check") + .build()); + } else { + config.setDefaultDispatcher( + dispatcherResult.get("defaultDispatcher").getResult()); - List> editables = result.entrySet().stream() - .filter(e -> e.getValue().getResult().contains("$")) - .toList(); - if (!editables.isEmpty()) { - context.addInHeader(""); - context.addInHeader("Please customize the editable value:"); - Map editMap; - for (Map.Entry editable : editables) { - String template = editable.getValue().getResult(); - String prefix = template.substring(0, template.indexOf("$")); - editMap = prompt.prompt( + DispatcherMeta meta = secDispatcher.availableDispatchers().stream() + .filter(d -> Objects.equals(config.getDefaultDispatcher(), d.name())) + .findFirst() + .orElseThrow(); + if (!meta.fields().isEmpty()) { + prompt.prompt( context.header, - prompt.getPromptBuilder() - .createInputPrompt() - .name("edit") - .message(template) - .addCompleter(new Completer() { - @Override - public void complete( - LineReader reader, ParsedLine line, List candidates) { - if (!line.line().startsWith(prefix)) { - candidates.add( - new Candidate(prefix, prefix, null, null, null, null, false)); - } - } - }) - .addPrompt() - .build()); - if (editMap == null) { + configureDispatcher(context, meta, prompt.getPromptBuilder()) + .build(), + dispatcherConfigResult); + if (dispatcherConfigResult.isEmpty()) { throw new InterruptedException(); } - result.put(editable.getKey(), editMap.get("edit")); - } - } - Config dispatcherConfig = new Config(); - dispatcherConfig.setName(meta.name()); - for (DispatcherMeta.Field field : meta.fields()) { - ConfigProperty property = new ConfigProperty(); - property.setName(field.getKey()); - property.setValue(result.get(field.getKey()).getResult()); - dispatcherConfig.addProperty(property); - } - if (!dispatcherConfig.getProperties().isEmpty()) { - config.addConfiguration(dispatcherConfig); - } - } + List> editables = dispatcherConfigResult.entrySet().stream() + .filter(e -> e.getValue().getResult().contains("$")) + .toList(); + if (!editables.isEmpty()) { + context.addInHeader(""); + context.addInHeader("Please customize the editable value:"); + Map editMap = new HashMap<>(editables.size()); + for (Map.Entry editable : editables) { + String template = editable.getValue().getResult(); + prompt.prompt( + context.header, + prompt.getPromptBuilder() + .createInputPrompt() + .name("edit") + .message(template) + .addPrompt() + .build(), + editMap); + if (editMap.isEmpty()) { + throw new InterruptedException(); + } + dispatcherConfigResult.put(editable.getKey(), editMap.get("edit")); + } + } - if (yes) { - secDispatcher.writeConfiguration(config); - } else { - context.addInHeader(""); - context.addInHeader("Values set:"); - context.addInHeader("defaultDispatcher=" + config.getDefaultDispatcher()); - for (Config c : config.getConfigurations()) { - context.addInHeader(" dispatcherName=" + c.getName()); - for (ConfigProperty cp : c.getProperties()) { - context.addInHeader(" " + cp.getName() + "=" + cp.getValue()); + Config dispatcherConfig = new Config(); + dispatcherConfig.setName(meta.name()); + for (DispatcherMeta.Field field : meta.fields()) { + ConfigProperty property = new ConfigProperty(); + property.setName(field.getKey()); + property.setValue( + dispatcherConfigResult.get(field.getKey()).getResult()); + dispatcherConfig.addProperty(property); + } + if (!dispatcherConfig.getProperties().isEmpty()) { + config.addConfiguration(dispatcherConfig); + } } } - result = prompt.prompt( - context.header, confirmPrompt(prompt.getPromptBuilder()).build()); - ConfirmResult confirm = (ConfirmResult) result.get("confirm"); - if (confirm.getConfirmed() == ConfirmChoice.ConfirmationValue.YES) { - context.terminal - .writer() - .println(messageBuilderFactory - .builder() - .info("Writing out the configuration...") - .build()); + if (yes) { secDispatcher.writeConfiguration(config); } else { - context.terminal - .writer() - .println(messageBuilderFactory - .builder() - .warning("Values not accepted; not saving configuration.") - .build()); - return BAD_OPERATION; + context.addInHeader(""); + context.addInHeader("Values set:"); + context.addInHeader("defaultDispatcher=" + config.getDefaultDispatcher()); + for (Config c : config.getConfigurations()) { + context.addInHeader(" dispatcherName=" + c.getName()); + for (ConfigProperty cp : c.getProperties()) { + context.addInHeader(" " + cp.getName() + "=" + cp.getValue()); + } + } + + prompt.prompt( + context.header, confirmPrompt(prompt.getPromptBuilder()).build(), confirmChoice); + ConfirmResult confirm = (ConfirmResult) confirmChoice.get("confirm"); + if (confirm.getConfirmed() == ConfirmChoice.ConfirmationValue.YES) { + context.terminal + .writer() + .println(messageBuilderFactory + .builder() + .info("Writing out the configuration...") + .build()); + secDispatcher.writeConfiguration(config); + } else { + context.terminal + .writer() + .println(messageBuilderFactory + .builder() + .warning("Values not accepted; not saving configuration.") + .build()); + return CANCELED; + } + } + } catch (IOError e) { + // TODO: this should be handled properly in jline3! + if (e.getCause() instanceof InterruptedIOException) { + throw new UserInterruptException(e.getCause()); + } else { + throw e; } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/CommonsCliShellOptions.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/CommonsCliShellOptions.java new file mode 100644 index 000000000000..04b1c3675557 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/CommonsCliShellOptions.java @@ -0,0 +1,87 @@ +/* + * 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.mvnsh; + +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.apache.commons.cli.CommandLine; +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.mvnsh.ShellOptions; +import org.apache.maven.cling.invoker.CommonsCliOptions; +import org.codehaus.plexus.interpolation.BasicInterpolator; +import org.codehaus.plexus.interpolation.InterpolationException; + +import static org.apache.maven.cling.invoker.Utils.createInterpolator; + +/** + * Implementation of {@link ShellOptions} (base + shell). + */ +public class CommonsCliShellOptions extends CommonsCliOptions implements ShellOptions { + public static CommonsCliShellOptions parse(String[] args) throws ParseException { + CLIManager cliManager = new CLIManager(); + return new CommonsCliShellOptions(Options.SOURCE_CLI, cliManager, cliManager.parse(args)); + } + + protected CommonsCliShellOptions(String source, CLIManager cliManager, CommandLine commandLine) { + super(source, cliManager, commandLine); + } + + private static CommonsCliShellOptions interpolate( + CommonsCliShellOptions options, Collection> properties) { + try { + // now that we have properties, interpolate all arguments + BasicInterpolator interpolator = createInterpolator(properties); + CommandLine.Builder commandLineBuilder = new CommandLine.Builder(); + commandLineBuilder.setDeprecatedHandler(o -> {}); + for (Option option : options.commandLine.getOptions()) { + if (!CLIManager.USER_PROPERTY.equals(option.getOpt())) { + List values = option.getValuesList(); + for (ListIterator it = values.listIterator(); it.hasNext(); ) { + it.set(interpolator.interpolate(it.next())); + } + } + commandLineBuilder.addOption(option); + } + for (String arg : options.commandLine.getArgList()) { + commandLineBuilder.addArg(interpolator.interpolate(arg)); + } + return new CommonsCliShellOptions( + options.source, (CLIManager) options.cliManager, commandLineBuilder.build()); + } catch (InterpolationException e) { + throw new IllegalArgumentException("Could not interpolate CommonsCliOptions", e); + } + } + + @Override + public ShellOptions interpolate(Collection> properties) { + return interpolate(this, properties); + } + + protected static class CLIManager extends CommonsCliOptions.CLIManager { + @Override + protected String commandLineSyntax(String command) { + return command + " [options]"; + } + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellCommandRegistryFactory.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellCommandRegistryFactory.java new file mode 100644 index 000000000000..0f8fae99482b --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellCommandRegistryFactory.java @@ -0,0 +1,26 @@ +/* + * 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.mvnsh; + +import org.apache.maven.cling.invoker.LookupContext; +import org.jline.console.CommandRegistry; + +public interface ShellCommandRegistryFactory { + CommandRegistry createShellCommandRegistry(LookupContext context); +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellCommandRegistryHolder.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellCommandRegistryHolder.java new file mode 100644 index 000000000000..20251943b185 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellCommandRegistryHolder.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.cling.invoker.mvnsh; + +import java.util.ArrayList; +import java.util.List; + +import org.jline.console.CommandRegistry; + +import static java.util.Objects.requireNonNull; + +public class ShellCommandRegistryHolder implements AutoCloseable { + private final List commandRegistries; + + public ShellCommandRegistryHolder() { + this.commandRegistries = new ArrayList<>(); + } + + public void addCommandRegistry(CommandRegistry commandRegistry) { + requireNonNull(commandRegistry, "commandRegistry"); + this.commandRegistries.add(commandRegistry); + } + + public CommandRegistry[] getCommandRegistries() { + return commandRegistries.toArray(new CommandRegistry[0]); + } + + @Override + public void close() throws Exception { + ArrayList exceptions = new ArrayList<>(); + for (CommandRegistry commandRegistry : commandRegistries) { + if (commandRegistry instanceof AutoCloseable closeable) { + try { + closeable.close(); + } catch (Exception e) { + exceptions.add(e); + } + } + } + if (!exceptions.isEmpty()) { + IllegalStateException ex = new IllegalStateException("Could not close commandRegistries"); + exceptions.forEach(ex::addSuppressed); + throw ex; + } + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvoker.java new file mode 100644 index 000000000000..12abb2eb19d0 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvoker.java @@ -0,0 +1,181 @@ +/* + * 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.mvnsh; + +import java.nio.file.Path; +import java.util.Map; + +import org.apache.maven.api.cli.InvokerRequest; +import org.apache.maven.api.services.Lookup; +import org.apache.maven.cling.invoker.LookupContext; +import org.apache.maven.cling.invoker.LookupInvoker; +import org.apache.maven.cling.utils.CLIReportingUtils; +import org.jline.builtins.ConfigurationPath; +import org.jline.console.impl.Builtins; +import org.jline.console.impl.SimpleSystemRegistryImpl; +import org.jline.console.impl.SystemRegistryImpl; +import org.jline.keymap.KeyMap; +import org.jline.reader.Binding; +import org.jline.reader.EndOfFileException; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.MaskingCallback; +import org.jline.reader.Parser; +import org.jline.reader.Reference; +import org.jline.reader.UserInterruptException; +import org.jline.reader.impl.DefaultHighlighter; +import org.jline.reader.impl.DefaultParser; +import org.jline.reader.impl.history.DefaultHistory; +import org.jline.utils.AttributedStringBuilder; +import org.jline.utils.AttributedStyle; +import org.jline.utils.InfoCmp; +import org.jline.widget.TailTipWidgets; + +/** + * mvnsh invoker implementation. + */ +public class ShellInvoker extends LookupInvoker { + + public ShellInvoker(Lookup protoLookup) { + super(protoLookup, null); + } + + @Override + protected LookupContext createContext(InvokerRequest invokerRequest) { + return new LookupContext(invokerRequest); + } + + public static final int OK = 0; // OK + public static final int ERROR = 1; // "generic" error + + @Override + protected int execute(LookupContext context) throws Exception { + // set up JLine built-in commands + ConfigurationPath configPath = + new ConfigurationPath(context.invokerRequest.cwd(), context.invokerRequest.cwd()); + Builtins builtins = new Builtins(context.invokerRequest::cwd, configPath, null); + builtins.rename(Builtins.Command.TTOP, "top"); + builtins.alias("zle", "widget"); + builtins.alias("bindkey", "keymap"); + + ShellCommandRegistryHolder holder = new ShellCommandRegistryHolder(); + holder.addCommandRegistry(builtins); + + // gather commands + Map factories = + context.lookup.lookupMap(ShellCommandRegistryFactory.class); + for (Map.Entry entry : factories.entrySet()) { + holder.addCommandRegistry(entry.getValue().createShellCommandRegistry(context)); + } + + Parser parser = new DefaultParser(); + + String banner = + """ + + ░▒▓██████████████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░░▒▓███████▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░\s + ░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░\s + ░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░\s + ░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓██████▓▒░ ░▒▓████████▓▒░\s + ░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░\s + ░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░\s + ░▒▓█▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓██▓▒░ ░▒▓█▓▒░░▒▓█▓▒░░▒▓███████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░"""; + context.writer.accept(banner); + if (!context.invokerRequest.options().showVersion().orElse(false)) { + context.writer.accept(CLIReportingUtils.showVersionMinimal()); + } + context.writer.accept(""); + + try (holder) { + SimpleSystemRegistryImpl systemRegistry = + new SimpleSystemRegistryImpl(parser, context.terminal, context.invokerRequest::cwd, configPath); + systemRegistry.setCommandRegistries(holder.getCommandRegistries()); + + Path history = context.userResolver.apply(".mvnsh_history"); + LineReader reader = LineReaderBuilder.builder() + .terminal(context.terminal) + .history(new DefaultHistory()) + .highlighter(new ReplHighlighter()) + .completer(systemRegistry.completer()) + .parser(parser) + .variable(LineReader.LIST_MAX, 50) // max tab completion candidates + .variable(LineReader.HISTORY_FILE, history) + .variable(LineReader.OTHERS_GROUP_NAME, "Others") + .variable(LineReader.COMPLETION_STYLE_GROUP, "fg:blue,bold") + .variable("HELP_COLORS", "ti=1;34:co=38:ar=3:op=33:de=90") + .option(LineReader.Option.GROUP_PERSIST, true) + .build(); + builtins.setLineReader(reader); + systemRegistry.setLineReader(reader); + new TailTipWidgets(reader, systemRegistry::commandDescription, 5, TailTipWidgets.TipType.COMPLETER); + KeyMap keyMap = reader.getKeyMaps().get("main"); + keyMap.bind(new Reference("tailtip-toggle"), KeyMap.alt("s")); + + String prompt = "mvnsh> "; + String rightPrompt = null; + + // start the shell and process input until the user quits with Ctrl-D + String line; + while (true) { + try { + systemRegistry.cleanUp(); + line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null); + systemRegistry.execute(line); + } catch (UserInterruptException e) { + // Ignore + // return CANCELED; + } catch (EndOfFileException e) { + return OK; + } catch (SystemRegistryImpl.UnknownCommandException e) { + context.writer.accept(context.invokerRequest + .messageBuilderFactory() + .builder() + .error(e.getMessage()) + .build()); + } catch (Exception e) { + systemRegistry.trace(e); + context.writer.accept(context.invokerRequest + .messageBuilderFactory() + .builder() + .error("Error:" + e.getMessage()) + .build()); + if (context.invokerRequest.options().showErrors().orElse(false)) { + e.printStackTrace(context.terminal.writer()); + } + return ERROR; + } + } + } + } + + private static class ReplHighlighter extends DefaultHighlighter { + @Override + protected void commandStyle(LineReader reader, AttributedStringBuilder sb, boolean enable) { + if (enable) { + if (reader.getTerminal().getNumericCapability(InfoCmp.Capability.max_colors) >= 256) { + sb.style(AttributedStyle.DEFAULT.bold().foreground(69)); + } else { + sb.style(AttributedStyle.DEFAULT.foreground(AttributedStyle.CYAN)); + } + } else { + sb.style(AttributedStyle.DEFAULT.boldOff().foregroundOff()); + } + } + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java new file mode 100644 index 000000000000..9866170144d4 --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java @@ -0,0 +1,78 @@ +/* + * 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.mvnsh; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.cli.ParserRequest; +import org.apache.maven.api.cli.extensions.CoreExtension; +import org.apache.maven.api.cli.mvnsh.ShellOptions; +import org.apache.maven.cling.invoker.BaseInvokerRequest; + +import static java.util.Objects.requireNonNull; + +public class ShellInvokerRequest extends BaseInvokerRequest { + private final ShellOptions options; + + @SuppressWarnings("ParameterNumber") + public ShellInvokerRequest( + ParserRequest parserRequest, + Path cwd, + Path installationDirectory, + Path userHomeDirectory, + Map userProperties, + Map systemProperties, + Path topDirectory, + Path rootDirectory, + InputStream in, + OutputStream out, + OutputStream err, + List coreExtensions, + List jvmArguments, + ShellOptions options) { + super( + parserRequest, + cwd, + installationDirectory, + userHomeDirectory, + userProperties, + systemProperties, + topDirectory, + rootDirectory, + in, + out, + err, + coreExtensions, + jvmArguments); + this.options = requireNonNull(options); + } + + /** + * The mandatory Shell options. + */ + @Nonnull + public ShellOptions options() { + return options; + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java new file mode 100644 index 000000000000..105e6b018abd --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellParser.java @@ -0,0 +1,68 @@ +/* + * 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.mvnsh; + +import java.util.Collections; +import java.util.List; + +import org.apache.commons.cli.ParseException; +import org.apache.maven.api.cli.Options; +import org.apache.maven.api.cli.ParserException; +import org.apache.maven.api.cli.mvnsh.ShellOptions; +import org.apache.maven.cling.invoker.BaseParser; + +public class ShellParser extends BaseParser { + @Override + protected ShellInvokerRequest getInvokerRequest(LocalContext context) { + return new ShellInvokerRequest( + context.parserRequest, + context.cwd, + context.installationDirectory, + context.userHomeDirectory, + context.userProperties, + context.systemProperties, + context.topDirectory, + context.rootDirectory, + context.parserRequest.in(), + context.parserRequest.out(), + context.parserRequest.err(), + context.extensions, + getJvmArguments(context.rootDirectory), + (ShellOptions) context.options); + } + + @Override + protected List parseCliOptions(LocalContext context) throws ParserException { + return Collections.singletonList(parseShellCliOptions(context.parserRequest.args())); + } + + protected CommonsCliShellOptions parseShellCliOptions(List args) throws ParserException { + try { + return CommonsCliShellOptions.parse(args.toArray(new String[0])); + } catch (ParseException e) { + throw new ParserException("Failed to parse command line options: " + e.getMessage(), e); + } + } + + @Override + protected Options assembleOptions(List parsedOptions) { + // nothing to assemble, we deal with CLI only + return parsedOptions.get(0); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/builtin/BuiltinShellCommandRegistryFactory.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/builtin/BuiltinShellCommandRegistryFactory.java new file mode 100644 index 000000000000..03b82380e2cf --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/builtin/BuiltinShellCommandRegistryFactory.java @@ -0,0 +1,186 @@ +/* + * 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.mvnsh.builtin; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.maven.api.cli.ParserRequest; +import org.apache.maven.api.di.Named; +import org.apache.maven.api.di.Singleton; +import org.apache.maven.cling.invoker.LookupContext; +import org.apache.maven.cling.invoker.mvn.MavenInvoker; +import org.apache.maven.cling.invoker.mvn.MavenParser; +import org.apache.maven.cling.invoker.mvnenc.EncryptInvoker; +import org.apache.maven.cling.invoker.mvnenc.EncryptParser; +import org.apache.maven.cling.invoker.mvnsh.ShellCommandRegistryFactory; +import org.jline.builtins.Completers; +import org.jline.builtins.Options; +import org.jline.console.CmdDesc; +import org.jline.console.CommandInput; +import org.jline.console.CommandMethods; +import org.jline.console.CommandRegistry; +import org.jline.console.impl.AbstractCommandRegistry; +import org.jline.reader.Completer; +import org.jline.reader.impl.completer.ArgumentCompleter; +import org.jline.reader.impl.completer.NullCompleter; + +import static java.util.Objects.requireNonNull; +import static org.jline.console.impl.JlineCommandRegistry.compileCommandOptions; + +@Named("builtin") +@Singleton +public class BuiltinShellCommandRegistryFactory implements ShellCommandRegistryFactory { + public CommandRegistry createShellCommandRegistry(LookupContext context) { + return new BuiltinShellCommandRegistry(context); + } + + private static class BuiltinShellCommandRegistry extends AbstractCommandRegistry implements AutoCloseable { + public enum Command { + MVN, + MVNENC + } + + private final LookupContext shellContext; + private final MavenInvoker shellMavenInvoker; + private final MavenParser mavenParser; + private final EncryptInvoker shellEncryptInvoker; + private final EncryptParser encryptParser; + + private BuiltinShellCommandRegistry(LookupContext shellContext) { + this.shellContext = requireNonNull(shellContext, "shellContext"); + this.shellMavenInvoker = new MavenInvoker(shellContext.invokerRequest.lookup(), contextCopier()); + this.mavenParser = new MavenParser(); + this.shellEncryptInvoker = new EncryptInvoker(shellContext.invokerRequest.lookup(), contextCopier()); + this.encryptParser = new EncryptParser(); + Set commands = new HashSet<>(EnumSet.allOf(Command.class)); + Map commandName = new HashMap<>(); + Map commandExecute = new HashMap<>(); + for (Command c : commands) { + commandName.put(c, c.name().toLowerCase()); + } + commandExecute.put(Command.MVN, new CommandMethods(this::mvn, this::mvnCompleter)); + commandExecute.put(Command.MVNENC, new CommandMethods(this::mvnenc, this::mvnencCompleter)); + registerCommands(commandName, commandExecute); + } + + private Consumer contextCopier() { + return result -> { + result.logger = shellContext.logger; + result.loggerFactory = shellContext.loggerFactory; + result.slf4jConfiguration = shellContext.slf4jConfiguration; + result.loggerLevel = shellContext.loggerLevel; + result.coloredOutput = shellContext.coloredOutput; + result.terminal = shellContext.terminal; + result.writer = shellContext.writer; + + result.installationSettingsPath = shellContext.installationSettingsPath; + result.projectSettingsPath = shellContext.projectSettingsPath; + result.userSettingsPath = shellContext.userSettingsPath; + result.interactive = shellContext.interactive; + result.localRepositoryPath = shellContext.localRepositoryPath; + result.effectiveSettings = shellContext.effectiveSettings; + + result.containerCapsule = shellContext.containerCapsule; + result.lookup = shellContext.lookup; + result.eventSpyDispatcher = shellContext.eventSpyDispatcher; + }; + } + + @Override + public void close() throws Exception { + shellMavenInvoker.close(); + shellEncryptInvoker.close(); + } + + @Override + public List commandInfo(String command) { + return List.of(); + } + + @Override + public CmdDesc commandDescription(List args) { + return null; + } + + @Override + public String name() { + return "Builtin Maven Shell commands"; + } + + private List commandOptions(String command) { + try { + invoke(new CommandSession(), command, "--help"); + } catch (Options.HelpException e) { + return compileCommandOptions(e.getMessage()); + } catch (Exception e) { + // ignore + } + return null; + } + + private void mvn(CommandInput input) { + try { + shellMavenInvoker.invoke(mavenParser.parseInvocation(ParserRequest.mvn( + input.args(), + shellContext.invokerRequest.logger(), + shellContext.invokerRequest.messageBuilderFactory()) + .build())); + } catch (Exception e) { + saveException(e); + } + } + + private List mvnCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter( + NullCompleter.INSTANCE, + new Completers.OptionCompleter( + new Completers.FilesCompleter(shellContext.invokerRequest::cwd), this::commandOptions, 1))); + return completers; + } + + private void mvnenc(CommandInput input) { + try { + shellEncryptInvoker.invoke(encryptParser.parseInvocation(ParserRequest.mvnenc( + input.args(), + shellContext.invokerRequest.logger(), + shellContext.invokerRequest.messageBuilderFactory()) + .build())); + } catch (Exception e) { + saveException(e); + } + } + + private List mvnencCompleter(String name) { + List completers = new ArrayList<>(); + completers.add(new ArgumentCompleter( + NullCompleter.INSTANCE, + new Completers.OptionCompleter( + new Completers.FilesCompleter(shellContext.invokerRequest::cwd), this::commandOptions, 1))); + return completers; + } + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/local/LocalMavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/package-info.java similarity index 52% rename from impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/local/LocalMavenInvoker.java rename to impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/package-info.java index 3ac8cc783b46..6e14380c50b2 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/local/LocalMavenInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/package-info.java @@ -16,24 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cling.invoker.mvn.local; - -import org.apache.maven.api.cli.InvokerException; -import org.apache.maven.api.cli.InvokerRequest; -import org.apache.maven.cling.invoker.ProtoLookup; -import org.apache.maven.cling.invoker.mvn.MavenContext; -import org.apache.maven.cling.invoker.mvn.MavenInvoker; /** - * Local Maven invoker implementation, that expects all the Maven to be on classpath. + * This package contains the {@code mvnsh} tool implementation. */ -public class LocalMavenInvoker extends MavenInvoker { - public LocalMavenInvoker(ProtoLookup protoLookup) { - super(protoLookup); - } - - @Override - protected MavenContext createContext(InvokerRequest invokerRequest) throws InvokerException { - return new MavenContext(invokerRequest); - } -} +package org.apache.maven.cling.invoker.mvnsh; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/package-info.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/package-info.java index b169eba8dbf4..3156066d143d 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/package-info.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/package-info.java @@ -20,5 +20,17 @@ /** * This package contain support (mostly abstract) classes, that implement "base" of CLIng. * In packages below you find actual implementations. + * + * Hierarchy: + *
    + *
  • {@link org.apache.maven.cling.invoker.LookupInvoker} is the "basis", the common ground of all Maven Tools
  • + *
  • extended by {@link org.apache.maven.cling.invoker.mvn.MavenInvoker} is the "mvn Tool"
  • + *
  • extended by {@link org.apache.maven.cling.invoker.mvnenc.EncryptInvoker} is the "mvnenc Tool"
  • + *
  • extended by {@link org.apache.maven.cling.invoker.mvnsh.ShellInvoker} is the "mvnsh Tool"
  • + *
+ * + * There is one specialization of {@link org.apache.maven.cling.invoker.mvn.MavenInvoker}, the "resident" + * {@link org.apache.maven.cling.invoker.mvn.resident.ResidentMavenInvoker}. The difference is that this invoker + * will on close "clean up" (tear down) the instance. All invokers are re-entrant. */ package org.apache.maven.cling.invoker; diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/LocalMavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java similarity index 87% rename from impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/LocalMavenInvokerTest.java rename to impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java index 11b8a4d87816..54d6fb147b79 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/LocalMavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cling.invoker.mvn.local; +package org.apache.maven.cling.invoker.mvn; import java.nio.file.FileSystem; import java.nio.file.Path; @@ -27,8 +27,6 @@ import org.apache.maven.api.cli.Invoker; import org.apache.maven.api.cli.Parser; import org.apache.maven.cling.invoker.ProtoLookup; -import org.apache.maven.cling.invoker.mvn.MavenInvokerTestSupport; -import org.apache.maven.cling.invoker.mvn.MavenParser; import org.codehaus.plexus.classworlds.ClassWorld; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Order; @@ -40,10 +38,10 @@ * Local UT. */ @Order(200) -public class LocalMavenInvokerTest extends MavenInvokerTestSupport { +public class MavenInvokerTest extends MavenInvokerTestSupport { @Override protected Invoker createInvoker() { - return new LocalMavenInvoker(ProtoLookup.builder() + return new MavenInvoker(ProtoLookup.builder() .addMapping(ClassWorld.class, new ClassWorld("plexus.core", ClassLoader.getSystemClassLoader())) .build()); } diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvokerTest.java deleted file mode 100644 index 56c1cd2388c1..000000000000 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvokerTest.java +++ /dev/null @@ -1,53 +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.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.cling.invoker.mvn.MavenInvokerTestSupport; -import org.apache.maven.cling.invoker.mvn.MavenParser; -import org.junit.jupiter.api.Order; -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. - */ -@Order(300) -public class ForkedMavenInvokerTest extends MavenInvokerTestSupport { - - @Override - protected Invoker createInvoker() { - return new ForkedMavenInvoker(); - } - - @Override - protected Parser createParser() { - return new MavenParser(); - } - - @Test - void defaultFs(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception { - invoke(tempDir, Arrays.asList("clean", "verify")); - } -} diff --git a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java index 0bbacf9f6663..e713a73a43ad 100644 --- a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java +++ b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java @@ -126,7 +126,8 @@ public EmbeddedMavenExecutor(boolean cacheContexts) { this.originalStderr = System.err; this.originalClassLoader = Thread.currentThread().getContextClassLoader(); this.contexts = new ConcurrentHashMap<>(); - this.originalProperties = System.getProperties(); + this.originalProperties = new Properties(); + this.originalProperties.putAll(System.getProperties()); } @Override @@ -261,7 +262,6 @@ protected Context doCreate(Path mavenHome, ExecutorRequest executorRequest) { Class[] parameterTypes = {String[].class, String.class, PrintStream.class, PrintStream.class}; Method doMain = cliClass.getMethod("doMain", parameterTypes); exec = r -> { - System.setProperties(null); System.setProperties(prepareProperties(r)); try { return (int) doMain.invoke(mavenCli, new Object[] { @@ -278,7 +278,6 @@ protected Context doCreate(Path mavenHome, ExecutorRequest executorRequest) { Field ansiConsoleInstalled = ansiConsole.getDeclaredField("installed"); ansiConsoleInstalled.setAccessible(true); exec = r -> { - System.setProperties(null); System.setProperties(prepareProperties(r)); try { try { @@ -310,8 +309,10 @@ protected Context doCreate(Path mavenHome, ExecutorRequest executorRequest) { } protected Properties prepareProperties(ExecutorRequest request) { + System.setProperties(null); // this "inits" them! + Properties properties = new Properties(); - properties.putAll(System.getProperties()); + properties.putAll(System.getProperties()); // get mandatory/expected init-ed above properties.setProperty("user.dir", request.cwd().toString()); properties.setProperty("user.home", request.userHomeDirectory().toString()); diff --git a/impl/maven-jline/pom.xml b/impl/maven-jline/pom.xml index 1dd607d85c8c..da3a9ec6c871 100644 --- a/impl/maven-jline/pom.xml +++ b/impl/maven-jline/pom.xml @@ -52,6 +52,10 @@ under the License. org.jline jline-builtins + + org.jline + jline-console + org.jline jline-console-ui diff --git a/pom.xml b/pom.xml index e54f1f3ad11d..ffe62c9954a5 100644 --- a/pom.xml +++ b/pom.xml @@ -452,6 +452,11 @@ under the License. jline-builtins ${jlineVersion} + + org.jline + jline-console + ${jlineVersion} + org.jline jline-console-ui