[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> <includes>
<include>mvn</include> <include>mvn</include>
<include>mvnenc</include> <include>mvnenc</include>
<include>mvnsh</include>
<include>mvnDebug</include> <include>mvnDebug</include>
<include>mvnencDebug</include> <include>mvnencDebug</include>
<!-- This is so that CI systems can periodically run the profiler --> <!-- This is so that CI systems can periodically run the profiler -->

View File

@ -207,6 +207,9 @@ handle_args() {
--enc) --enc)
MAVEN_MAIN_CLASS="org.apache.maven.cling.MavenEncCling" MAVEN_MAIN_CLASS="org.apache.maven.cling.MavenEncCling"
;; ;;
--shell)
MAVEN_MAIN_CLASS="org.apache.maven.cling.MavenShellCling"
;;
*) *)
;; ;;
esac esac

View File

@ -200,6 +200,8 @@ if "%~1"=="--debug" (
set "MAVEN_OPTS=-agentpath:%YJPLIB%=onexit=snapshot,onexit=memory,tracing,onlylocal %MAVEN_OPTS%" set "MAVEN_OPTS=-agentpath:%YJPLIB%=onexit=snapshot,onexit=memory,tracing,onlylocal %MAVEN_OPTS%"
) else if "%~1"=="--enc" ( ) else if "%~1"=="--enc" (
set "MAVEN_MAIN_CLASS=org.apache.maven.cling.MavenEncCling" 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 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 @Nonnull
Optional<String> color(); 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. * 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); 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. * 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_CMD = "mvnenc";
public static final String MVNENC_NAME = "Maven Password Encrypting Tool"; 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 @Nonnull
Optional<String> alternatePomFile(); 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). * 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.api.cli.ParserRequest;
import org.apache.maven.cling.invoker.ProtoLogger; import org.apache.maven.cling.invoker.ProtoLogger;
import org.apache.maven.cling.invoker.ProtoLookup; 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.MavenParser;
import org.apache.maven.cling.invoker.mvn.local.LocalMavenInvoker;
import org.apache.maven.jline.JLineMessageBuilderFactory; import org.apache.maven.jline.JLineMessageBuilderFactory;
import org.codehaus.plexus.classworlds.ClassWorld; import org.codehaus.plexus.classworlds.ClassWorld;
@ -61,7 +61,7 @@ public class MavenCling extends ClingSupport {
@Override @Override
protected Invoker createInvoker() { protected Invoker createInvoker() {
return new LocalMavenInvoker( return new MavenInvoker(
ProtoLookup.builder().addMapping(ClassWorld.class, classWorld).build()); 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; package org.apache.maven.cling.extensions;
import javax.inject.Inject;
import javax.inject.Named;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -33,7 +30,10 @@ import java.util.stream.Collectors;
import org.apache.maven.RepositoryUtils; import org.apache.maven.RepositoryUtils;
import org.apache.maven.api.Service; import org.apache.maven.api.Service;
import org.apache.maven.api.Session; 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.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.model.Plugin;
import org.apache.maven.api.services.ArtifactCoordinatesFactory; import org.apache.maven.api.services.ArtifactCoordinatesFactory;
import org.apache.maven.api.services.ArtifactManager; 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.resolution.DependencyResult;
import org.eclipse.aether.util.filter.ExclusionsDependencyFilter; import org.eclipse.aether.util.filter.ExclusionsDependencyFilter;
import org.eclipse.aether.util.version.GenericVersionScheme; import org.eclipse.aether.util.version.GenericVersionScheme;
import org.eclipse.sisu.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;

View File

@ -202,6 +202,14 @@ public abstract class CommonsCliOptions implements Options {
return Optional.empty(); return Optional.empty();
} }
@Override
public Optional<Boolean> offline() {
if (commandLine.hasOption(CLIManager.OFFLINE)) {
return Optional.of(Boolean.TRUE);
}
return Optional.empty();
}
@Override @Override
public Optional<Boolean> help() { public Optional<Boolean> help() {
if (commandLine.hasOption(CLIManager.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 LOG_FILE = "l";
public static final String RAW_STREAMS = "raw-streams"; public static final String RAW_STREAMS = "raw-streams";
public static final String COLOR = "color"; public static final String COLOR = "color";
public static final String OFFLINE = "o";
public static final String HELP = "h"; public static final String HELP = "h";
// parameters handled by script // parameters handled by script
public static final String DEBUG = "debug"; public static final String DEBUG = "debug";
public static final String ENC = "enc"; public static final String ENC = "enc";
public static final String SHELL = "shell";
public static final String YJP = "yjp"; public static final String YJP = "yjp";
// deprecated ones // deprecated ones
@ -378,6 +388,10 @@ public abstract class CommonsCliOptions implements Options {
.optionalArg(true) .optionalArg(true)
.desc("Defines the color mode of the output. Supported are 'auto', 'always', 'never'.") .desc("Defines the color mode of the output. Supported are 'auto', 'always', 'never'.")
.build()); .build());
options.addOption(Option.builder(OFFLINE)
.longOpt("offline")
.desc("Work offline")
.build());
// Parameters handled by script // Parameters handled by script
options.addOption(Option.builder() options.addOption(Option.builder()
@ -388,6 +402,10 @@ public abstract class CommonsCliOptions implements Options {
.longOpt(ENC) .longOpt(ENC)
.desc("Launch the Maven Encryption tool (script option).") .desc("Launch the Maven Encryption tool (script option).")
.build()); .build());
options.addOption(Option.builder()
.longOpt(SHELL)
.desc("Launch the Maven Shell tool (script option).")
.build());
options.addOption(Option.builder() options.addOption(Option.builder()
.longOpt(YJP) .longOpt(YJP)
.desc("Launch the JVM with Yourkit profiler (script option).") .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); return returnFirstPresentOrEmpty(Options::color);
} }
@Override
public Optional<Boolean> offline() {
return returnFirstPresentOrEmpty(Options::offline);
}
@Override @Override
public Optional<Boolean> help() { public Optional<Boolean> help() {
return returnFirstPresentOrEmpty(Options::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.services.Lookup;
import org.apache.maven.api.settings.Settings; import org.apache.maven.api.settings.Settings;
import org.apache.maven.cling.logging.Slf4jConfiguration; import org.apache.maven.cling.logging.Slf4jConfiguration;
import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.logging.BuildEventListener; import org.apache.maven.logging.BuildEventListener;
import org.jline.terminal.Terminal; import org.jline.terminal.Terminal;
import org.slf4j.ILoggerFactory; import org.slf4j.ILoggerFactory;
@ -47,14 +48,20 @@ public class LookupContext implements AutoCloseable {
public final Function<String, Path> cwdResolver; public final Function<String, Path> cwdResolver;
public final Function<String, Path> installationResolver; public final Function<String, Path> installationResolver;
public final Function<String, Path> userResolver; 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.invokerRequest = requireNonNull(invokerRequest);
this.cwdResolver = s -> invokerRequest.cwd().resolve(s).normalize().toAbsolutePath(); this.cwdResolver = s -> invokerRequest.cwd().resolve(s).normalize().toAbsolutePath();
this.installationResolver = s -> this.installationResolver = s ->
invokerRequest.installationDirectory().resolve(s).normalize().toAbsolutePath(); invokerRequest.installationDirectory().resolve(s).normalize().toAbsolutePath();
this.userResolver = this.userResolver =
s -> invokerRequest.userHomeDirectory().resolve(s).normalize().toAbsolutePath(); s -> invokerRequest.userHomeDirectory().resolve(s).normalize().toAbsolutePath();
this.containerCapsuleManaged = containerCapsuleManaged;
this.logger = invokerRequest.parserRequest().logger(); this.logger = invokerRequest.parserRequest().logger();
Map<String, String> user = new HashMap<>(invokerRequest.userProperties()); Map<String, String> user = new HashMap<>(invokerRequest.userProperties());
@ -84,8 +91,10 @@ public class LookupContext implements AutoCloseable {
public Boolean coloredOutput; public Boolean coloredOutput;
public Terminal terminal; public Terminal terminal;
public Consumer<String> writer; public Consumer<String> writer;
public ContainerCapsule containerCapsule; public ContainerCapsule containerCapsule;
public Lookup lookup; public Lookup lookup;
public EventSpyDispatcher eventSpyDispatcher;
public BuildEventListener buildEventListener; public BuildEventListener buildEventListener;
@ -93,7 +102,6 @@ public class LookupContext implements AutoCloseable {
public Path installationSettingsPath; public Path installationSettingsPath;
public Path projectSettingsPath; public Path projectSettingsPath;
public Path userSettingsPath; public Path userSettingsPath;
public boolean interactive; public boolean interactive;
public Path localRepositoryPath; public Path localRepositoryPath;
public Settings effectiveSettings; 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) { if (containerCapsule != null) {
try { try {
containerCapsule.close(); containerCapsule.close();
} finally { } finally {
eventSpyDispatcher = null;
lookup = null; lookup = null;
containerCapsule = null; containerCapsule = null;
} }

View File

@ -24,6 +24,7 @@ import java.io.PrintWriter;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -34,6 +35,7 @@ import java.util.function.Function;
import org.apache.maven.api.Constants; import org.apache.maven.api.Constants;
import org.apache.maven.api.ProtoSession; 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.Invoker;
import org.apache.maven.api.cli.InvokerException; import org.apache.maven.api.cli.InvokerException;
import org.apache.maven.api.cli.InvokerRequest; 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.Slf4jConfiguration;
import org.apache.maven.cling.logging.Slf4jConfigurationFactory; import org.apache.maven.cling.logging.Slf4jConfigurationFactory;
import org.apache.maven.cling.utils.CLIReportingUtils; import org.apache.maven.cling.utils.CLIReportingUtils;
import org.apache.maven.eventspy.internal.EventSpyDispatcher;
import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.internal.impl.SettingsUtilsV4; import org.apache.maven.internal.impl.SettingsUtilsV4;
import org.apache.maven.jline.FastTerminal; 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.SimpleBuildEventListener;
import org.apache.maven.logging.api.LogLevelRecorder; import org.apache.maven.logging.api.LogLevelRecorder;
import org.apache.maven.slf4j.MavenSimpleLogger; import org.apache.maven.slf4j.MavenSimpleLogger;
import org.codehaus.plexus.PlexusContainer;
import org.jline.terminal.Terminal; import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder; import org.jline.terminal.TerminalBuilder;
import org.jline.terminal.impl.AbstractPosixTerminal; import org.jline.terminal.impl.AbstractPosixTerminal;
@ -92,19 +96,27 @@ import static org.apache.maven.cling.invoker.Utils.toProperties;
* @param <C> The context type. * @param <C> The context type.
*/ */
public abstract class LookupInvoker<C extends LookupContext> implements Invoker { 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.protoLookup = requireNonNull(protoLookup);
this.contextConsumer = contextConsumer;
} }
@Override @Override
public int invoke(InvokerRequest invokerRequest) throws InvokerException { public int invoke(InvokerRequest invokerRequest) throws InvokerException {
requireNonNull(invokerRequest); requireNonNull(invokerRequest);
Properties oldProps = (Properties) System.getProperties().clone(); Properties oldProps = new Properties();
oldProps.putAll(System.getProperties());
ClassLoader oldCL = Thread.currentThread().getContextClassLoader(); ClassLoader oldCL = Thread.currentThread().getContextClassLoader();
try (C context = createContext(invokerRequest)) { try (C context = createContext(invokerRequest)) {
if (contextConsumer != null) {
contextConsumer.accept(context);
}
try { try {
if (context.containerCapsule != null if (context.containerCapsule != null
&& context.containerCapsule.currentThreadClassLoader().isPresent()) { && context.containerCapsule.currentThreadClassLoader().isPresent()) {
@ -128,8 +140,6 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
protected int doInvoke(C context) throws Exception { protected int doInvoke(C context) throws Exception {
pushCoreProperties(context); pushCoreProperties(context);
pushUserProperties(context); pushUserProperties(context);
validate(context);
prepare(context);
configureLogging(context); configureLogging(context);
createTerminal(context); createTerminal(context);
activateLogging(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 { protected void configureLogging(C context) throws Exception {
// LOG COLOR // LOG COLOR
Options mavenOptions = context.invokerRequest.options(); Options mavenOptions = context.invokerRequest.options();
@ -251,33 +257,40 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
} }
protected void createTerminal(C context) { protected void createTerminal(C context) {
MessageUtils.systemInstall( if (context.terminal == null) {
builder -> { MessageUtils.systemInstall(
builder.streams( builder -> {
context.invokerRequest.in().orElse(null), builder.streams(
context.invokerRequest.out().orElse(null)); context.invokerRequest.in().orElse(null),
builder.systemOutput(TerminalBuilder.SystemOutput.ForcedSysOut); context.invokerRequest.out().orElse(null));
// The exec builder suffers from https://github.com/jline/jline3/issues/1098 builder.systemOutput(TerminalBuilder.SystemOutput.ForcedSysOut);
// We could re-enable it when fixed to provide support for non-standard architectures, // The exec builder suffers from https://github.com/jline/jline3/issues/1098
// for which JLine does not provide any native library. // We could re-enable it when fixed to provide support for non-standard architectures,
builder.exec(false); // for which JLine does not provide any native library.
if (context.coloredOutput != null) { builder.exec(false);
builder.color(context.coloredOutput); if (context.coloredOutput != null) {
} builder.color(context.coloredOutput);
}, }
terminal -> doConfigureWithTerminal(context, terminal)); },
terminal -> doConfigureWithTerminal(context, terminal));
context.terminal = MessageUtils.getTerminal(); context.terminal = MessageUtils.getTerminal();
// JLine is quite slow to start due to the native library unpacking and loading // JLine is quite slow to start due to the native library unpacking and loading
// so boot it asynchronously // so boot it asynchronously
context.closeables.add(MessageUtils::systemUninstall); context.closeables.add(MessageUtils::systemUninstall);
MessageUtils.registerShutdownHook(); // safety belt MessageUtils.registerShutdownHook(); // safety belt
if (context.coloredOutput != null) { if (context.coloredOutput != null) {
MessageUtils.setColorEnabled(context.coloredOutput); MessageUtils.setColorEnabled(context.coloredOutput);
}
} else {
if (context.coloredOutput != null) {
MessageUtils.setColorEnabled(context.coloredOutput);
}
} }
} }
protected void doConfigureWithTerminal(C context, Terminal terminal) { protected void doConfigureWithTerminal(C context, Terminal terminal) {
context.terminal = terminal;
Options options = context.invokerRequest.options(); Options options = context.invokerRequest.options();
if (options.rawStreams().isEmpty() || !options.rawStreams().get()) { if (options.rawStreams().isEmpty() || !options.rawStreams().get()) {
MavenSimpleLogger stdout = (MavenSimpleLogger) context.loggerFactory.getLogger("stdout"); 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 { protected void container(C context) throws Exception {
context.containerCapsule = createContainerCapsuleFactory().createContainerCapsule(this, context); if (context.lookup == null) {
context.closeables.add(context::closeContainer); context.containerCapsule = createContainerCapsuleFactory().createContainerCapsule(this, context);
context.lookup = context.containerCapsule.getLookup(); context.closeables.add(context::closeContainer);
context.lookup = context.containerCapsule.getLookup();
// refresh logger in case container got customized by spy } else {
org.slf4j.Logger l = context.loggerFactory.getLogger(this.getClass().getName()); context.containerCapsule.updateLogging(context);
context.logger = (level, message, error) -> l.atLevel(org.slf4j.event.Level.valueOf(level.name())) }
.setCause(error)
.log(message);
} }
protected ContainerCapsuleFactory<C> createContainerCapsuleFactory() { protected ContainerCapsuleFactory<C> createContainerCapsuleFactory() {
@ -434,9 +445,22 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
context.protoSession = protoSession; 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 { protected void postCommands(C context) throws Exception {
InvokerRequest invokerRequest = context.invokerRequest; InvokerRequest invokerRequest = context.invokerRequest;
@ -465,7 +489,9 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
} }
protected void settings(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 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker
.build(); .build();
customizeSettingsRequest(context, settingsRequest); customizeSettingsRequest(context, settingsRequest);
if (context.eventSpyDispatcher != null) {
context.eventSpyDispatcher.onEvent(settingsRequest);
}
context.logger.debug("Reading installation settings from '" + installationSettingsFile + "'"); context.logger.debug("Reading installation settings from '" + installationSettingsFile + "'");
context.logger.debug("Reading project settings from '" + projectSettingsFile + "'"); 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); SettingsBuilderResult settingsResult = settingsBuilder.build(settingsRequest);
customizeSettingsResult(context, settingsResult); customizeSettingsResult(context, settingsResult);
if (context.eventSpyDispatcher != null) {
context.eventSpyDispatcher.onEvent(settingsResult);
}
context.effectiveSettings = settingsResult.getEffectiveSettings(); context.effectiveSettings = settingsResult.getEffectiveSettings();
context.interactive = mayDisableInteractiveMode(context, context.effectiveSettings.isInteractiveMode()); context.interactive = mayDisableInteractiveMode(context, context.effectiveSettings.isInteractiveMode());

View File

@ -79,14 +79,6 @@ public class CommonsCliMavenOptions extends CommonsCliOptions implements MavenOp
return Optional.empty(); return Optional.empty();
} }
@Override
public Optional<Boolean> offline() {
if (commandLine.hasOption(CLIManager.OFFLINE)) {
return Optional.of(Boolean.TRUE);
}
return Optional.empty();
}
@Override @Override
public Optional<Boolean> nonRecursive() { public Optional<Boolean> nonRecursive() {
if (commandLine.hasOption(CLIManager.NON_RECURSIVE)) { if (commandLine.hasOption(CLIManager.NON_RECURSIVE)) {
@ -263,7 +255,6 @@ public class CommonsCliMavenOptions extends CommonsCliOptions implements MavenOp
protected static class CLIManager extends CommonsCliOptions.CLIManager { protected static class CLIManager extends CommonsCliOptions.CLIManager {
public static final String ALTERNATE_POM_FILE = "f"; 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 NON_RECURSIVE = "N";
public static final String UPDATE_SNAPSHOTS = "U"; public static final String UPDATE_SNAPSHOTS = "U";
public static final String ACTIVATE_PROFILES = "P"; public static final String ACTIVATE_PROFILES = "P";
@ -293,10 +284,6 @@ public class CommonsCliMavenOptions extends CommonsCliOptions implements MavenOp
.hasArg() .hasArg()
.desc("Force the use of an alternate POM file (or directory with pom.xml)") .desc("Force the use of an alternate POM file (or directory with pom.xml)")
.build()); .build());
options.addOption(Option.builder(OFFLINE)
.longOpt("offline")
.desc("Work offline")
.build());
options.addOption(Option.builder(NON_RECURSIVE) options.addOption(Option.builder(NON_RECURSIVE)
.longOpt("non-recursive") .longOpt("non-recursive")
.desc( .desc(

View File

@ -54,11 +54,6 @@ public class LayeredMavenOptions<O extends MavenOptions> extends LayeredOptions<
return returnFirstPresentOrEmpty(MavenOptions::alternatePomFile); return returnFirstPresentOrEmpty(MavenOptions::alternatePomFile);
} }
@Override
public Optional<Boolean> offline() {
return returnFirstPresentOrEmpty(MavenOptions::offline);
}
@Override @Override
public Optional<Boolean> nonRecursive() { public Optional<Boolean> nonRecursive() {
return returnFirstPresentOrEmpty(MavenOptions::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.Maven;
import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.cling.invoker.LookupContext; import org.apache.maven.cling.invoker.LookupContext;
import org.apache.maven.eventspy.internal.EventSpyDispatcher;
@SuppressWarnings("VisibilityModifier") @SuppressWarnings("VisibilityModifier")
public class MavenContext extends LookupContext { public class MavenContext extends LookupContext {
public MavenContext(InvokerRequest invokerRequest) { public MavenContext(InvokerRequest invokerRequest) {
super(invokerRequest); this(invokerRequest, true);
}
public MavenContext(InvokerRequest invokerRequest, boolean containerCapsuleManaged) {
super(invokerRequest, containerCapsuleManaged);
} }
public EventSpyDispatcher eventSpyDispatcher;
public Maven maven; public Maven maven;
@Override @Override
protected void closeContainer() { public void doCloseContainer() {
eventSpyDispatcher = null; try {
maven = null; super.doCloseContainer();
super.closeContainer(); } finally {
maven = null;
}
} }
} }

View File

@ -22,10 +22,10 @@ import java.io.FileNotFoundException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -33,28 +33,26 @@ import org.apache.maven.InternalErrorException;
import org.apache.maven.Maven; import org.apache.maven.Maven;
import org.apache.maven.api.Constants; import org.apache.maven.api.Constants;
import org.apache.maven.api.MonotonicClock; 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.InvokerRequest;
import org.apache.maven.api.cli.Logger; import org.apache.maven.api.cli.Logger;
import org.apache.maven.api.cli.mvn.MavenOptions; import org.apache.maven.api.cli.mvn.MavenOptions;
import org.apache.maven.api.services.BuilderProblem; import org.apache.maven.api.services.BuilderProblem;
import org.apache.maven.api.services.Lookup; 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.Source;
import org.apache.maven.api.services.ToolchainsBuilder; import org.apache.maven.api.services.ToolchainsBuilder;
import org.apache.maven.api.services.ToolchainsBuilderRequest; import org.apache.maven.api.services.ToolchainsBuilderRequest;
import org.apache.maven.api.services.ToolchainsBuilderResult; import org.apache.maven.api.services.ToolchainsBuilderResult;
import org.apache.maven.api.services.model.ModelProcessor; import org.apache.maven.api.services.model.ModelProcessor;
import org.apache.maven.cling.event.ExecutionEventLogger; 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.LookupInvoker;
import org.apache.maven.cling.invoker.ProtoLookup;
import org.apache.maven.cling.invoker.Utils; import org.apache.maven.cling.invoker.Utils;
import org.apache.maven.cling.transfer.ConsoleMavenTransferListener; import org.apache.maven.cling.transfer.ConsoleMavenTransferListener;
import org.apache.maven.cling.transfer.QuietMavenTransferListener; import org.apache.maven.cling.transfer.QuietMavenTransferListener;
import org.apache.maven.cling.transfer.SimplexTransferListener; import org.apache.maven.cling.transfer.SimplexTransferListener;
import org.apache.maven.cling.transfer.Slf4jMavenTransferListener; 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.DefaultExceptionHandler;
import org.apache.maven.exception.ExceptionHandler; import org.apache.maven.exception.ExceptionHandler;
import org.apache.maven.exception.ExceptionSummary; 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.LoggingExecutionListener;
import org.apache.maven.logging.MavenTransferListener; import org.apache.maven.logging.MavenTransferListener;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.PlexusContainer;
import org.eclipse.aether.DefaultRepositoryCache; import org.eclipse.aether.DefaultRepositoryCache;
import org.eclipse.aether.transfer.TransferListener; import org.eclipse.aether.transfer.TransferListener;
import static java.util.Comparator.comparing; 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. * The Maven invoker, that expects whole Maven on classpath and invokes it.
*
* @param <C> The context type.
*/ */
public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker<C> { public class MavenInvoker extends LookupInvoker<MavenContext> {
public MavenInvoker(ProtoLookup protoLookup) { public MavenInvoker(Lookup protoLookup) {
super(protoLookup); this(protoLookup, null);
}
public MavenInvoker(Lookup protoLookup, @Nullable Consumer<LookupContext> contextConsumer) {
super(protoLookup, contextConsumer);
} }
@Override @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(); MavenExecutionRequest request = prepareMavenExecutionRequest();
toolchains(context, request); toolchains(context, request);
populateRequest(context, context.lookup, request); populateRequest(context, context.lookup, request);
@ -113,26 +116,15 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
} }
@Override @Override
protected void lookup(C context) throws Exception { protected void lookup(MavenContext context) throws Exception {
context.eventSpyDispatcher = context.lookup.lookup(EventSpyDispatcher.class); if (context.maven == null) {
context.maven = context.lookup.lookup(Maven.class); super.lookup(context);
context.maven = context.lookup.lookup(Maven.class);
}
} }
@Override @Override
protected void init(C context) throws Exception { protected void postCommands(MavenContext 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 {
super.postCommands(context); super.postCommands(context);
InvokerRequest invokerRequest = context.invokerRequest; InvokerRequest invokerRequest = context.invokerRequest;
@ -145,21 +137,7 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
} }
} }
@Override protected void toolchains(MavenContext context, MavenExecutionRequest request) throws Exception {
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 {
Path userToolchainsFile = null; Path userToolchainsFile = null;
if (context.invokerRequest.options().altUserToolchains().isPresent()) { if (context.invokerRequest.options().altUserToolchains().isPresent()) {
userToolchainsFile = context.cwdResolver.apply( userToolchainsFile = context.cwdResolver.apply(
@ -240,7 +218,8 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
} }
@Override @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); super.populateRequest(context, lookup, request);
if (context.invokerRequest.rootDirectory().isEmpty()) { if (context.invokerRequest.rootDirectory().isEmpty()) {
// maven requires this to be set; so default it (and see below at POM) // 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(); Path current = context.invokerRequest.cwd();
MavenOptions options = (MavenOptions) context.invokerRequest.options(); MavenOptions options = (MavenOptions) context.invokerRequest.options();
if (options.alternatePomFile().isPresent()) { 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(); MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
if (mavenOptions.failFast().isPresent()) { if (mavenOptions.failFast().isPresent()) {
return MavenExecutionRequest.REACTOR_FAIL_FAST; 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(); MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
if (mavenOptions.strictChecksums().orElse(false)) { if (mavenOptions.strictChecksums().orElse(false)) {
return MavenExecutionRequest.CHECKSUM_POLICY_FAIL; 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()); ExecutionListener listener = new ExecutionEventLogger(context.invokerRequest.messageBuilderFactory());
if (context.eventSpyDispatcher != null) { if (context.eventSpyDispatcher != null) {
listener = context.eventSpyDispatcher.chainListener(listener); listener = context.eventSpyDispatcher.chainListener(listener);
@ -369,7 +348,7 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
return new LoggingExecutionListener(listener, determineBuildEventListener(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 quiet = context.invokerRequest.options().quiet().orElse(false);
boolean logFile = context.invokerRequest.options().logFile().isPresent(); boolean logFile = context.invokerRequest.options().logFile().isPresent();
boolean runningOnCI = isRunningOnCI(context); boolean runningOnCI = isRunningOnCI(context);
@ -390,7 +369,7 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker
return new MavenTransferListener(delegate, determineBuildEventListener(context)); return new MavenTransferListener(delegate, determineBuildEventListener(context));
} }
protected String determineMakeBehavior(C context) { protected String determineMakeBehavior(MavenContext context) {
MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options(); MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
if (mavenOptions.alsoMake().isPresent() if (mavenOptions.alsoMake().isPresent()
&& mavenOptions.alsoMakeDependents().isEmpty()) { && 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(); MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
if (mavenOptions.projects().isPresent() if (mavenOptions.projects().isPresent()
&& !mavenOptions.projects().get().isEmpty()) { && !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(); MavenOptions mavenOptions = (MavenOptions) context.invokerRequest.options();
if (mavenOptions.activatedProfiles().isPresent() if (mavenOptions.activatedProfiles().isPresent()
&& !mavenOptions.activatedProfiles().get().isEmpty()) { && !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); context.eventSpyDispatcher.onEvent(request);
MavenExecutionResult result; 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("");
context.logger.error("After correcting the problems, you can resume the build with the command"); context.logger.error("After correcting the problems, you can resume the build with the command");
context.logger.error( 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 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 = ""; String referenceKey = "";
if (summary.getReference() != null && !summary.getReference().isEmpty()) { 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.InvokerException;
import org.apache.maven.api.cli.InvokerRequest; 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; 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 * 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). * 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) { public ResidentMavenInvoker(Lookup protoLookup) {
super(protoLookup); super(protoLookup, null);
this.residentContext = new ConcurrentHashMap<>(); this.residentContext = new ConcurrentHashMap<>();
} }
@Override @Override
public void close() throws InvokerException { public void close() throws InvokerException {
ArrayList<InvokerException> exceptions = new ArrayList<>(); ArrayList<InvokerException> exceptions = new ArrayList<>();
for (ResidentMavenContext context : residentContext.values()) { for (MavenContext context : residentContext.values()) {
try { try {
context.shutDown(); context.doCloseContainer();
} catch (InvokerException e) { } catch (InvokerException e) {
exceptions.add(e); exceptions.add(e);
} }
@ -58,31 +61,25 @@ public class ResidentMavenInvoker extends MavenInvoker<ResidentMavenContext> {
} }
@Override @Override
protected ResidentMavenContext createContext(InvokerRequest invokerRequest) { protected MavenContext createContext(InvokerRequest invokerRequest) {
return residentContext
.computeIfAbsent(getContextId(invokerRequest), k -> new ResidentMavenContext(invokerRequest))
.copy(invokerRequest);
}
protected String getContextId(InvokerRequest invokerRequest) {
// TODO: in a moment Maven stop pushing user properties to system properties (and maybe something more) // 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? // and allow multiple instances per JVM, this may become a pool? derive key based in invokerRequest?
return "resident"; MavenContext result = residentContext.computeIfAbsent("resident", k -> new MavenContext(invokerRequest, false));
return copyIfDifferent(result, invokerRequest);
} }
@Override protected MavenContext copyIfDifferent(MavenContext mavenContext, InvokerRequest invokerRequest) {
protected void container(ResidentMavenContext context) throws Exception { if (invokerRequest == mavenContext.invokerRequest) {
if (context.containerCapsule == null) { return mavenContext;
super.container(context);
} else {
context.containerCapsule.updateLogging(context);
} }
} MavenContext shadow = new MavenContext(invokerRequest, false);
@Override // we carry over only "resident" things
protected void lookup(ResidentMavenContext context) throws Exception { shadow.containerCapsule = mavenContext.containerCapsule;
if (context.maven == null) { shadow.lookup = mavenContext.lookup;
super.lookup(context); shadow.eventSpyDispatcher = mavenContext.eventSpyDispatcher;
} shadow.maven = mavenContext.maven;
return shadow;
} }
} }

View File

@ -30,8 +30,12 @@ import org.jline.utils.AttributedStyle;
@SuppressWarnings("VisibilityModifier") @SuppressWarnings("VisibilityModifier")
public class EncryptContext extends LookupContext { public class EncryptContext extends LookupContext {
protected EncryptContext(InvokerRequest invokerRequest) { public EncryptContext(InvokerRequest invokerRequest) {
super(invokerRequest); this(invokerRequest, true);
}
public EncryptContext(InvokerRequest invokerRequest, boolean containerCapsuleManaged) {
super(invokerRequest, containerCapsuleManaged);
} }
public Map<String, Goal> goals; public Map<String, Goal> goals;

View File

@ -20,11 +20,14 @@ package org.apache.maven.cling.invoker.mvnenc;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.util.ArrayList; 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.InvokerRequest;
import org.apache.maven.api.cli.mvnenc.EncryptOptions; 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.LookupInvoker;
import org.apache.maven.cling.invoker.ProtoLookup;
import org.apache.maven.cling.utils.CLIReportingUtils; import org.apache.maven.cling.utils.CLIReportingUtils;
import org.jline.reader.LineReaderBuilder; import org.jline.reader.LineReaderBuilder;
import org.jline.reader.UserInterruptException; import org.jline.reader.UserInterruptException;
@ -37,13 +40,17 @@ import org.jline.utils.Colors;
*/ */
public class EncryptInvoker extends LookupInvoker<EncryptContext> { public class EncryptInvoker extends LookupInvoker<EncryptContext> {
public EncryptInvoker(ProtoLookup protoLookup) { public static final int OK = 0; // OK
super(protoLookup); 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 public EncryptInvoker(Lookup protoLookup, @Nullable Consumer<LookupContext> contextConsumer) {
protected int execute(EncryptContext context) throws Exception { super(protoLookup, contextConsumer);
return doExecute(context);
} }
@Override @Override
@ -52,16 +59,15 @@ public class EncryptInvoker extends LookupInvoker<EncryptContext> {
} }
@Override @Override
protected void lookup(EncryptContext context) { protected void lookup(EncryptContext context) throws Exception {
context.goals = context.lookup.lookupMap(Goal.class); if (context.goals == null) {
super.lookup(context);
context.goals = context.lookup.lookupMap(Goal.class);
}
} }
public static final int OK = 0; // OK @Override
public static final int ERROR = 1; // "generic" error protected int execute(EncryptContext context) throws Exception {
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 {
try { try {
context.header = new ArrayList<>(); context.header = new ArrayList<>();
context.style = new AttributedStyle(); context.style = new AttributedStyle();

View File

@ -18,10 +18,9 @@
*/ */
package org.apache.maven.cling.invoker.mvnenc.goals; package org.apache.maven.cling.invoker.mvnenc.goals;
import javax.inject.Inject; import org.apache.maven.api.di.Inject;
import javax.inject.Named; import org.apache.maven.api.di.Named;
import javax.inject.Singleton; import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.api.services.MessageBuilderFactory;
import org.apache.maven.cling.invoker.mvnenc.EncryptContext; import org.apache.maven.cling.invoker.mvnenc.EncryptContext;
import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcher;

View File

@ -18,10 +18,9 @@
*/ */
package org.apache.maven.cling.invoker.mvnenc.goals; package org.apache.maven.cling.invoker.mvnenc.goals;
import javax.inject.Inject; import org.apache.maven.api.di.Inject;
import javax.inject.Named; import org.apache.maven.api.di.Named;
import javax.inject.Singleton; import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.api.services.MessageBuilderFactory;
import org.apache.maven.cling.invoker.mvnenc.EncryptContext; import org.apache.maven.cling.invoker.mvnenc.EncryptContext;
import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcher;

View File

@ -18,10 +18,9 @@
*/ */
package org.apache.maven.cling.invoker.mvnenc.goals; package org.apache.maven.cling.invoker.mvnenc.goals;
import javax.inject.Inject; import org.apache.maven.api.di.Inject;
import javax.inject.Named; import org.apache.maven.api.di.Named;
import javax.inject.Singleton; import org.apache.maven.api.di.Singleton;
import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.api.services.MessageBuilderFactory;
import org.apache.maven.cling.invoker.mvnenc.EncryptContext; import org.apache.maven.cling.invoker.mvnenc.EncryptContext;
import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.SecDispatcher;

View File

@ -18,15 +18,17 @@
*/ */
package org.apache.maven.cling.invoker.mvnenc.goals; package org.apache.maven.cling.invoker.mvnenc.goals;
import javax.inject.Inject; import java.io.IOError;
import javax.inject.Named; import java.io.InterruptedIOException;
import javax.inject.Singleton; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.apache.maven.api.cli.mvnenc.EncryptOptions; 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.api.services.MessageBuilderFactory;
import org.apache.maven.cling.invoker.mvnenc.EncryptContext; import org.apache.maven.cling.invoker.mvnenc.EncryptContext;
import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; 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.PromptResultItemIF;
import org.jline.consoleui.prompt.builder.ListPromptBuilder; import org.jline.consoleui.prompt.builder.ListPromptBuilder;
import org.jline.consoleui.prompt.builder.PromptBuilder; import org.jline.consoleui.prompt.builder.PromptBuilder;
import org.jline.reader.Candidate; import org.jline.reader.UserInterruptException;
import org.jline.reader.Completer;
import org.jline.reader.LineReader;
import org.jline.reader.ParsedLine;
import org.jline.utils.Colors; import org.jline.utils.Colors;
import org.jline.utils.OSUtils; 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.BAD_OPERATION;
import static org.apache.maven.cling.invoker.mvnenc.EncryptInvoker.CANCELED;
import static org.apache.maven.cling.invoker.mvnenc.EncryptInvoker.OK; import static org.apache.maven.cling.invoker.mvnenc.EncryptInvoker.OK;
/** /**
@ -63,6 +63,7 @@ public class Init extends InteractiveGoalSupport {
super(messageBuilderFactory, secDispatcher); super(messageBuilderFactory, secDispatcher);
} }
@SuppressWarnings("MethodLength")
@Override @Override
public int doExecute(EncryptContext context) throws Exception { public int doExecute(EncryptContext context) throws Exception {
EncryptOptions options = (EncryptOptions) context.invokerRequest.options(); EncryptOptions options = (EncryptOptions) context.invokerRequest.options();
@ -87,125 +88,128 @@ public class Init extends InteractiveGoalSupport {
promptConfig = new ConsolePrompt.UiConfig("", "", "", ""); promptConfig = new ConsolePrompt.UiConfig("", "", "", "");
} }
promptConfig.setCancellableFirstPrompt(true); promptConfig.setCancellableFirstPrompt(true);
ConsolePrompt prompt = new ConsolePrompt(context.reader, context.terminal, promptConfig);
SettingsSecurity config = secDispatcher.readConfiguration(true); SettingsSecurity config = secDispatcher.readConfiguration(true);
// reset config // reset config
config.setDefaultDispatcher(null); config.setDefaultDispatcher(null);
config.getConfigurations().clear(); config.getConfigurations().clear();
Map<String, PromptResultItemIF> result = prompt.prompt( try (ConsolePrompt prompt = new ConsolePrompt(context.reader, context.terminal, promptConfig)) {
context.header, dispatcherPrompt(prompt.getPromptBuilder()).build()); Map<String, PromptResultItemIF> dispatcherResult = new HashMap<>();
if (result == null) { Map<String, PromptResultItemIF> dispatcherConfigResult = new HashMap<>();
throw new InterruptedException(); Map<String, PromptResultItemIF> confirmChoice = new HashMap<>();
}
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());
DispatcherMeta meta = secDispatcher.availableDispatchers().stream() prompt.prompt(
.filter(d -> Objects.equals(config.getDefaultDispatcher(), d.name())) context.header, dispatcherPrompt(prompt.getPromptBuilder()).build(), dispatcherResult);
.findFirst() if (dispatcherResult.isEmpty()) {
.orElseThrow();
if (!meta.fields().isEmpty()) {
result = prompt.prompt(
context.header,
configureDispatcher(context, meta, prompt.getPromptBuilder())
.build());
if (result == null) {
throw new InterruptedException(); 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<Map.Entry<String, PromptResultItemIF>> editables = result.entrySet().stream() DispatcherMeta meta = secDispatcher.availableDispatchers().stream()
.filter(e -> e.getValue().getResult().contains("$")) .filter(d -> Objects.equals(config.getDefaultDispatcher(), d.name()))
.toList(); .findFirst()
if (!editables.isEmpty()) { .orElseThrow();
context.addInHeader(""); if (!meta.fields().isEmpty()) {
context.addInHeader("Please customize the editable value:"); prompt.prompt(
Map<String, PromptResultItemIF> editMap;
for (Map.Entry<String, PromptResultItemIF> editable : editables) {
String template = editable.getValue().getResult();
String prefix = template.substring(0, template.indexOf("$"));
editMap = prompt.prompt(
context.header, context.header,
prompt.getPromptBuilder() configureDispatcher(context, meta, prompt.getPromptBuilder())
.createInputPrompt() .build(),
.name("edit") dispatcherConfigResult);
.message(template) if (dispatcherConfigResult.isEmpty()) {
.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) {
throw new InterruptedException(); throw new InterruptedException();
} }
result.put(editable.getKey(), editMap.get("edit"));
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 = new HashMap<>(editables.size());
for (Map.Entry<String, PromptResultItemIF> 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"));
}
}
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);
}
} }
} }
Config dispatcherConfig = new Config(); if (yes) {
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);
}
}
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());
}
}
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());
secDispatcher.writeConfiguration(config); secDispatcher.writeConfiguration(config);
} else { } else {
context.terminal context.addInHeader("");
.writer() context.addInHeader("Values set:");
.println(messageBuilderFactory context.addInHeader("defaultDispatcher=" + config.getDefaultDispatcher());
.builder() for (Config c : config.getConfigurations()) {
.warning("Values not accepted; not saving configuration.") context.addInHeader(" dispatcherName=" + c.getName());
.build()); for (ConfigProperty cp : c.getProperties()) {
return BAD_OPERATION; 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;
} }
} }

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 * specific language governing permissions and limitations
* under the License. * 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> { package org.apache.maven.cling.invoker.mvnsh;
public LocalMavenInvoker(ProtoLookup protoLookup) {
super(protoLookup);
}
@Override
protected MavenContext createContext(InvokerRequest invokerRequest) throws InvokerException {
return new MavenContext(invokerRequest);
}
}

View File

@ -20,5 +20,17 @@
/** /**
* This package contain support (mostly abstract) classes, that implement "base" of CLIng. * This package contain support (mostly abstract) classes, that implement "base" of CLIng.
* In packages below you find actual implementations. * 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; package org.apache.maven.cling.invoker;

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * 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.FileSystem;
import java.nio.file.Path; 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.Invoker;
import org.apache.maven.api.cli.Parser; import org.apache.maven.api.cli.Parser;
import org.apache.maven.cling.invoker.ProtoLookup; 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.codehaus.plexus.classworlds.ClassWorld;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
@ -40,10 +38,10 @@ import org.junit.jupiter.api.io.TempDir;
* Local UT. * Local UT.
*/ */
@Order(200) @Order(200)
public class LocalMavenInvokerTest extends MavenInvokerTestSupport { public class MavenInvokerTest extends MavenInvokerTestSupport {
@Override @Override
protected Invoker createInvoker() { protected Invoker createInvoker() {
return new LocalMavenInvoker(ProtoLookup.builder() return new MavenInvoker(ProtoLookup.builder()
.addMapping(ClassWorld.class, new ClassWorld("plexus.core", ClassLoader.getSystemClassLoader())) .addMapping(ClassWorld.class, new ClassWorld("plexus.core", ClassLoader.getSystemClassLoader()))
.build()); .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.originalStderr = System.err;
this.originalClassLoader = Thread.currentThread().getContextClassLoader(); this.originalClassLoader = Thread.currentThread().getContextClassLoader();
this.contexts = new ConcurrentHashMap<>(); this.contexts = new ConcurrentHashMap<>();
this.originalProperties = System.getProperties(); this.originalProperties = new Properties();
this.originalProperties.putAll(System.getProperties());
} }
@Override @Override
@ -261,7 +262,6 @@ public class EmbeddedMavenExecutor implements Executor {
Class<?>[] parameterTypes = {String[].class, String.class, PrintStream.class, PrintStream.class}; Class<?>[] parameterTypes = {String[].class, String.class, PrintStream.class, PrintStream.class};
Method doMain = cliClass.getMethod("doMain", parameterTypes); Method doMain = cliClass.getMethod("doMain", parameterTypes);
exec = r -> { exec = r -> {
System.setProperties(null);
System.setProperties(prepareProperties(r)); System.setProperties(prepareProperties(r));
try { try {
return (int) doMain.invoke(mavenCli, new Object[] { return (int) doMain.invoke(mavenCli, new Object[] {
@ -278,7 +278,6 @@ public class EmbeddedMavenExecutor implements Executor {
Field ansiConsoleInstalled = ansiConsole.getDeclaredField("installed"); Field ansiConsoleInstalled = ansiConsole.getDeclaredField("installed");
ansiConsoleInstalled.setAccessible(true); ansiConsoleInstalled.setAccessible(true);
exec = r -> { exec = r -> {
System.setProperties(null);
System.setProperties(prepareProperties(r)); System.setProperties(prepareProperties(r));
try { try {
try { try {
@ -310,8 +309,10 @@ public class EmbeddedMavenExecutor implements Executor {
} }
protected Properties prepareProperties(ExecutorRequest request) { protected Properties prepareProperties(ExecutorRequest request) {
System.setProperties(null); // this "inits" them!
Properties properties = new Properties(); 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.dir", request.cwd().toString());
properties.setProperty("user.home", request.userHomeDirectory().toString()); properties.setProperty("user.home", request.userHomeDirectory().toString());

View File

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

View File

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