[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
This commit is contained in:
Tamas Cservenak 2024-12-17 10:50:45 +01:00 committed by GitHub
parent d9dcac85d9
commit 49825e6dba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 1296 additions and 664 deletions

View File

@ -86,6 +86,7 @@ under the License.
<includes>
<include>mvn</include>
<include>mvnenc</include>
<include>mvnsh</include>
<include>mvnDebug</include>
<include>mvnencDebug</include>
<!-- This is so that CI systems can periodically run the profiler -->

View File

@ -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

View File

@ -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

View File

@ -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 "$@"

View File

@ -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 %*

View File

@ -182,6 +182,14 @@ public interface Options {
@Nonnull
Optional<String> 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<Boolean> offline();
/**
* Indicates whether to show help information.
*

View File

@ -205,6 +205,34 @@ public interface ParserRequest {
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<String> 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.
*

View File

@ -36,4 +36,7 @@ public final class 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";
}

View File

@ -48,14 +48,6 @@ public interface MavenOptions extends Options {
@Nonnull
Optional<String> 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<Boolean> offline();
/**
* Indicates whether Maven should operate in non-recursive mode (i.e., not build child modules).
*

View File

@ -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<Map<String, String>> properties);
}

View File

@ -26,8 +26,8 @@ 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.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 class MavenCling extends ClingSupport {
@Override
protected Invoker createInvoker() {
return new LocalMavenInvoker(
return new MavenInvoker(
ProtoLookup.builder().addMapping(ClassWorld.class, classWorld).build());
}

View File

@ -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());
}
}

View File

@ -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 java.util.stream.Collectors;
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.ArtifactResult;
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;

View File

@ -202,6 +202,14 @@ public abstract class CommonsCliOptions implements Options {
return Optional.empty();
}
@Override
public Optional<Boolean> offline() {
if (commandLine.hasOption(CLIManager.OFFLINE)) {
return Optional.of(Boolean.TRUE);
}
return Optional.empty();
}
@Override
public Optional<Boolean> help() {
if (commandLine.hasOption(CLIManager.HELP)) {
@ -267,11 +275,13 @@ public abstract class CommonsCliOptions implements Options {
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 @@ public abstract class CommonsCliOptions implements 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 @@ public abstract class CommonsCliOptions implements 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).")

View File

@ -132,6 +132,11 @@ public abstract class LayeredOptions<O extends Options> implements Options {
return returnFirstPresentOrEmpty(Options::color);
}
@Override
public Optional<Boolean> offline() {
return returnFirstPresentOrEmpty(Options::offline);
}
@Override
public Optional<Boolean> help() {
return returnFirstPresentOrEmpty(Options::help);

View File

@ -35,6 +35,7 @@ import org.apache.maven.api.cli.Logger;
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<String, Path> cwdResolver;
public final Function<String, Path> installationResolver;
public final Function<String, Path> 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<String, String> user = new HashMap<>(invokerRequest.userProperties());
@ -84,8 +91,10 @@ public class LookupContext implements AutoCloseable {
public Boolean coloredOutput;
public Terminal terminal;
public Consumer<String> writer;
public ContainerCapsule containerCapsule;
public Lookup lookup;
public EventSpyDispatcher eventSpyDispatcher;
public BuildEventListener buildEventListener;
@ -93,7 +102,6 @@ public class LookupContext implements AutoCloseable {
public Path installationSettingsPath;
public Path projectSettingsPath;
public Path userSettingsPath;
public boolean interactive;
public Path localRepositoryPath;
public Settings effectiveSettings;
@ -124,11 +132,18 @@ public class LookupContext implements AutoCloseable {
}
}
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;
}

View File

@ -24,6 +24,7 @@ import java.io.PrintWriter;
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 java.util.function.Function;
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.invoker.spi.PropertyContributorsHolder;
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.ProjectBuildLogAppender;
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 @@ import static org.apache.maven.cling.invoker.Utils.toProperties;
* @param <C> The context type.
*/
public abstract class LookupInvoker<C extends LookupContext> implements Invoker {
protected final ProtoLookup protoLookup;
protected final Lookup protoLookup;
public LookupInvoker(ProtoLookup protoLookup) {
@Nullable
protected final Consumer<LookupContext> contextConsumer;
public LookupInvoker(Lookup protoLookup, @Nullable Consumer<LookupContext> 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 abstract class LookupInvoker<C extends LookupContext> implements Invoker
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 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
}
}
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,6 +257,7 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
}
protected void createTerminal(C context) {
if (context.terminal == null) {
MessageUtils.systemInstall(
builder -> {
builder.streams(
@ -275,9 +282,15 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
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 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
}
protected void container(C context) throws Exception {
if (context.lookup == null) {
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);
} else {
context.containerCapsule.updateLogging(context);
}
}
protected ContainerCapsuleFactory<C> createContainerCapsuleFactory() {
@ -434,9 +445,22 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
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<String, Object> 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,8 +489,10 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
}
protected void settings(C context) throws Exception {
if (context.effectiveSettings == null) {
settings(context, true, context.lookup.lookup(SettingsBuilder.class));
}
}
/**
* This method is invoked twice during "normal" LookupInvoker level startup: once when (if present) extensions
@ -553,6 +579,9 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
.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 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
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());

View File

@ -79,14 +79,6 @@ public class CommonsCliMavenOptions extends CommonsCliOptions implements MavenOp
return Optional.empty();
}
@Override
public Optional<Boolean> offline() {
if (commandLine.hasOption(CLIManager.OFFLINE)) {
return Optional.of(Boolean.TRUE);
}
return Optional.empty();
}
@Override
public Optional<Boolean> nonRecursive() {
if (commandLine.hasOption(CLIManager.NON_RECURSIVE)) {
@ -263,7 +255,6 @@ public class CommonsCliMavenOptions extends CommonsCliOptions implements MavenOp
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 @@ public class CommonsCliMavenOptions extends CommonsCliOptions implements MavenOp
.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(

View File

@ -54,11 +54,6 @@ public class LayeredMavenOptions<O extends MavenOptions> extends LayeredOptions<
return returnFirstPresentOrEmpty(MavenOptions::alternatePomFile);
}
@Override
public Optional<Boolean> offline() {
return returnFirstPresentOrEmpty(MavenOptions::offline);
}
@Override
public Optional<Boolean> nonRecursive() {
return returnFirstPresentOrEmpty(MavenOptions::nonRecursive);

View File

@ -21,21 +21,25 @@ package org.apache.maven.cling.invoker.mvn;
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;
public void doCloseContainer() {
try {
super.doCloseContainer();
} finally {
maven = null;
super.closeContainer();
}
}
}

View File

@ -22,10 +22,10 @@ import java.io.FileNotFoundException;
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.InternalErrorException;
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.lifecycle.LifecycleExecutionException;
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 <C> The context type.
* The Maven invoker, that expects whole Maven on classpath and invokes it.
*/
public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker<C> {
public MavenInvoker(ProtoLookup protoLookup) {
super(protoLookup);
public class MavenInvoker extends LookupInvoker<MavenContext> {
public MavenInvoker(Lookup protoLookup) {
this(protoLookup, null);
}
public MavenInvoker(Lookup protoLookup, @Nullable Consumer<LookupContext> contextConsumer) {
super(protoLookup, contextConsumer);
}
@Override
protected int execute(C context) throws Exception {
protected MavenContext createContext(InvokerRequest invokerRequest) throws InvokerException {
return new MavenContext(invokerRequest);
}
@Override
protected int execute(MavenContext context) throws Exception {
MavenExecutionRequest request = prepareMavenExecutionRequest();
toolchains(context, request);
populateRequest(context, context.lookup, request);
@ -113,26 +116,15 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
@Override
protected void lookup(C context) throws Exception {
context.eventSpyDispatcher = context.lookup.lookup(EventSpyDispatcher.class);
protected void lookup(MavenContext context) throws Exception {
if (context.maven == null) {
super.lookup(context);
context.maven = context.lookup.lookup(Maven.class);
}
@Override
protected void init(C context) throws Exception {
super.init(context);
InvokerRequest invokerRequest = context.invokerRequest;
Map<String, Object> 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);
}
@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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
}
@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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
@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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
}
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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
}
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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
}
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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
}
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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
}
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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
}
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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
}
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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
}
}
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 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
protected static final String ANSI_RESET = "\u001B\u005Bm";
protected void logSummary(C context, ExceptionSummary summary, Map<String, String> references, String indent) {
protected void logSummary(
MavenContext context, ExceptionSummary summary, Map<String, String> references, String indent) {
String referenceKey = "";
if (summary.getReference() != null && !summary.getReference().isEmpty()) {

View File

@ -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<String> 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<String, String> 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 {}
}

View File

@ -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;
}
}

View File

@ -23,29 +23,32 @@ import java.util.concurrent.ConcurrentHashMap;
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<ResidentMavenContext> {
public class ResidentMavenInvoker extends MavenInvoker {
private final ConcurrentHashMap<String, ResidentMavenContext> residentContext;
private final ConcurrentHashMap<String, MavenContext> 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<InvokerException> 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 class ResidentMavenInvoker extends MavenInvoker<ResidentMavenContext> {
}
@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;
}
}

View File

@ -30,8 +30,12 @@ import org.jline.utils.AttributedStyle;
@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<String, Goal> goals;

View File

@ -20,11 +20,14 @@ package org.apache.maven.cling.invoker.mvnenc;
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 @@ import org.jline.utils.Colors;
*/
public class EncryptInvoker extends LookupInvoker<EncryptContext> {
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<LookupContext> contextConsumer) {
super(protoLookup, contextConsumer);
}
@Override
@ -52,16 +59,15 @@ public class EncryptInvoker extends LookupInvoker<EncryptContext> {
}
@Override
protected void lookup(EncryptContext context) {
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();

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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.ConsolePrompt;
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 class Init extends InteractiveGoalSupport {
super(messageBuilderFactory, secDispatcher);
}
@SuppressWarnings("MethodLength")
@Override
public int doExecute(EncryptContext context) throws Exception {
EncryptOptions options = (EncryptOptions) context.invokerRequest.options();
@ -87,20 +88,23 @@ public class Init extends InteractiveGoalSupport {
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<String, PromptResultItemIF> result = prompt.prompt(
context.header, dispatcherPrompt(prompt.getPromptBuilder()).build());
if (result == null) {
try (ConsolePrompt prompt = new ConsolePrompt(context.reader, context.terminal, promptConfig)) {
Map<String, PromptResultItemIF> dispatcherResult = new HashMap<>();
Map<String, PromptResultItemIF> dispatcherConfigResult = new HashMap<>();
Map<String, PromptResultItemIF> confirmChoice = new HashMap<>();
prompt.prompt(
context.header, dispatcherPrompt(prompt.getPromptBuilder()).build(), dispatcherResult);
if (dispatcherResult.isEmpty()) {
throw new InterruptedException();
}
if (NONE.equals(result.get("defaultDispatcher").getResult())) {
if (NONE.equals(dispatcherResult.get("defaultDispatcher").getResult())) {
context.terminal
.writer()
.println(messageBuilderFactory
@ -108,56 +112,46 @@ public class Init extends InteractiveGoalSupport {
.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());
} else {
config.setDefaultDispatcher(
dispatcherResult.get("defaultDispatcher").getResult());
DispatcherMeta meta = secDispatcher.availableDispatchers().stream()
.filter(d -> Objects.equals(config.getDefaultDispatcher(), d.name()))
.findFirst()
.orElseThrow();
if (!meta.fields().isEmpty()) {
result = prompt.prompt(
prompt.prompt(
context.header,
configureDispatcher(context, meta, prompt.getPromptBuilder())
.build());
if (result == null) {
.build(),
dispatcherConfigResult);
if (dispatcherConfigResult.isEmpty()) {
throw new InterruptedException();
}
List<Map.Entry<String, PromptResultItemIF>> editables = result.entrySet().stream()
List<Map.Entry<String, PromptResultItemIF>> editables = dispatcherConfigResult.entrySet().stream()
.filter(e -> e.getValue().getResult().contains("$"))
.toList();
if (!editables.isEmpty()) {
context.addInHeader("");
context.addInHeader("Please customize the editable value:");
Map<String, PromptResultItemIF> editMap;
Map<String, PromptResultItemIF> editMap = new HashMap<>(editables.size());
for (Map.Entry<String, PromptResultItemIF> editable : editables) {
String template = editable.getValue().getResult();
String prefix = template.substring(0, template.indexOf("$"));
editMap = prompt.prompt(
prompt.prompt(
context.header,
prompt.getPromptBuilder()
.createInputPrompt()
.name("edit")
.message(template)
.addCompleter(new Completer() {
@Override
public void complete(
LineReader reader, ParsedLine line, List<Candidate> candidates) {
if (!line.line().startsWith(prefix)) {
candidates.add(
new Candidate(prefix, prefix, null, null, null, null, false));
}
}
})
.addPrompt()
.build());
if (editMap == null) {
.build(),
editMap);
if (editMap.isEmpty()) {
throw new InterruptedException();
}
result.put(editable.getKey(), editMap.get("edit"));
dispatcherConfigResult.put(editable.getKey(), editMap.get("edit"));
}
}
@ -166,13 +160,15 @@ public class Init extends InteractiveGoalSupport {
for (DispatcherMeta.Field field : meta.fields()) {
ConfigProperty property = new ConfigProperty();
property.setName(field.getKey());
property.setValue(result.get(field.getKey()).getResult());
property.setValue(
dispatcherConfigResult.get(field.getKey()).getResult());
dispatcherConfig.addProperty(property);
}
if (!dispatcherConfig.getProperties().isEmpty()) {
config.addConfiguration(dispatcherConfig);
}
}
}
if (yes) {
secDispatcher.writeConfiguration(config);
@ -187,9 +183,9 @@ public class Init extends InteractiveGoalSupport {
}
}
result = prompt.prompt(
context.header, confirmPrompt(prompt.getPromptBuilder()).build());
ConfirmResult confirm = (ConfirmResult) result.get("confirm");
prompt.prompt(
context.header, confirmPrompt(prompt.getPromptBuilder()).build(), confirmChoice);
ConfirmResult confirm = (ConfirmResult) confirmChoice.get("confirm");
if (confirm.getConfirmed() == ConfirmChoice.ConfirmationValue.YES) {
context.terminal
.writer()
@ -205,7 +201,15 @@ public class Init extends InteractiveGoalSupport {
.builder()
.warning("Values not accepted; not saving configuration.")
.build());
return BAD_OPERATION;
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;
}
}

View File

@ -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<Map<String, String>> 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<String> values = option.getValuesList();
for (ListIterator<String> 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<Map<String, String>> properties) {
return interpolate(this, properties);
}
protected static class CLIManager extends CommonsCliOptions.CLIManager {
@Override
protected String commandLineSyntax(String command) {
return command + " [options]";
}
}
}

View File

@ -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);
}

View File

@ -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<CommandRegistry> 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<Exception> 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;
}
}
}

View File

@ -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<LookupContext> {
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<String, ShellCommandRegistryFactory> factories =
context.lookup.lookupMap(ShellCommandRegistryFactory.class);
for (Map.Entry<String, ShellCommandRegistryFactory> 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<Binding> 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());
}
}
}
}

View File

@ -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<String, String> userProperties,
Map<String, String> systemProperties,
Path topDirectory,
Path rootDirectory,
InputStream in,
OutputStream out,
OutputStream err,
List<CoreExtension> coreExtensions,
List<String> 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;
}
}

View File

@ -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<Options> parseCliOptions(LocalContext context) throws ParserException {
return Collections.singletonList(parseShellCliOptions(context.parserRequest.args()));
}
protected CommonsCliShellOptions parseShellCliOptions(List<String> 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<Options> parsedOptions) {
// nothing to assemble, we deal with CLI only
return parsedOptions.get(0);
}
}

View File

@ -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<Command> commands = new HashSet<>(EnumSet.allOf(Command.class));
Map<Command, String> commandName = new HashMap<>();
Map<Command, CommandMethods> 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<LookupContext> 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<String> commandInfo(String command) {
return List.of();
}
@Override
public CmdDesc commandDescription(List<String> args) {
return null;
}
@Override
public String name() {
return "Builtin Maven Shell commands";
}
private List<Completers.OptDesc> 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<Completer> mvnCompleter(String name) {
List<Completer> 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<Completer> mvnencCompleter(String name) {
List<Completer> completers = new ArrayList<>();
completers.add(new ArgumentCompleter(
NullCompleter.INSTANCE,
new Completers.OptionCompleter(
new Completers.FilesCompleter(shellContext.invokerRequest::cwd), this::commandOptions, 1)));
return completers;
}
}
}

View File

@ -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<MavenContext> {
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;

View File

@ -20,5 +20,17 @@
/**
* This package contain support (mostly abstract) classes, that implement "base" of CLIng.
* In packages below you find actual implementations.
*
* Hierarchy:
* <ul>
* <li>{@link org.apache.maven.cling.invoker.LookupInvoker} is the "basis", the common ground of all Maven Tools</li>
* <li>extended by {@link org.apache.maven.cling.invoker.mvn.MavenInvoker} is the "mvn Tool"</li>
* <li>extended by {@link org.apache.maven.cling.invoker.mvnenc.EncryptInvoker} is the "mvnenc Tool"</li>
* <li>extended by {@link org.apache.maven.cling.invoker.mvnsh.ShellInvoker} is the "mvnsh Tool"</li>
* </ul>
*
* 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;

View File

@ -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 com.google.common.jimfs.Jimfs;
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 @@ import org.junit.jupiter.api.io.TempDir;
* 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());
}

View File

@ -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"));
}
}

View File

@ -126,7 +126,8 @@ public class EmbeddedMavenExecutor implements Executor {
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 @@ public class EmbeddedMavenExecutor implements Executor {
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 @@ public class EmbeddedMavenExecutor implements Executor {
Field ansiConsoleInstalled = ansiConsole.getDeclaredField("installed");
ansiConsoleInstalled.setAccessible(true);
exec = r -> {
System.setProperties(null);
System.setProperties(prepareProperties(r));
try {
try {
@ -310,8 +309,10 @@ public class EmbeddedMavenExecutor implements Executor {
}
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());

View File

@ -52,6 +52,10 @@ under the License.
<groupId>org.jline</groupId>
<artifactId>jline-builtins</artifactId>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-console</artifactId>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-console-ui</artifactId>

View File

@ -452,6 +452,11 @@ under the License.
<artifactId>jline-builtins</artifactId>
<version>${jlineVersion}</version>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-console</artifactId>
<version>${jlineVersion}</version>
</dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-console-ui</artifactId>