This is an automated email from the ASF dual-hosted git repository. cstamas pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push: new 7ad2578e77 [MNG-8386] Pull out executor (#1932) 7ad2578e77 is described below commit 7ad2578e77da3639ac8f295aa40377272fa44c4d Author: Tamas Cservenak <ta...@cservenak.net> AuthorDate: Fri Nov 22 15:09:32 2024 +0100 [MNG-8386] Pull out executor (#1932) Yet another CLIng cleanup. Changes: * pull out Executor, it does not belong to CLIng (new maven-executor module created with no deps) * resident and maven invoker fixes (proper handling of resources now), no more SO/OOMs * enabled UTs in maven-cli (that revealed the issues) * small bug fixes discovered in cli, improved executor to reveal maven version --- https://issues.apache.org/jira/browse/MNG-8386 --- .../org/apache/maven/api/cli/ExecutorRequest.java | 82 ------ .../org/apache/maven/api/cli/InvokerRequest.java | 45 +++- .../main/java/org/apache/maven/api/cli/Parser.java | 12 - impl/maven-cli/pom.xml | 19 ++ .../maven/cling/invoker/BaseExecutorRequest.java | 77 ------ .../maven/cling/invoker/BaseInvokerRequest.java | 39 ++- .../org/apache/maven/cling/invoker/BaseParser.java | 20 -- .../apache/maven/cling/invoker/LookupContext.java | 13 +- .../apache/maven/cling/invoker/LookupInvoker.java | 50 +--- .../java/org/apache/maven/cling/invoker/Utils.java | 9 - .../maven/cling/invoker/mvn/MavenContext.java | 8 +- .../maven/cling/invoker/mvn/MavenInvoker.java | 25 +- .../invoker/mvn/forked/ForkedMavenExecutor.java | 73 ----- .../invoker/mvn/resident/ResidentMavenContext.java | 15 +- .../transfer/AbstractMavenTransferListener.java | 5 - .../transfer/ConsoleMavenTransferListener.java | 6 - .../invoker/mvn/MavenExecutorTestSupport.java | 64 ----- .../cling/invoker/mvn/MavenInvokerTestSupport.java | 49 +++- .../maven/cling/invoker/mvn/MavenTestSupport.java | 69 ----- .../mvn/embedded/EmbeddedMavenExecutorTest.java | 61 ----- .../invoker/mvn/forked/ForkedMavenInvokerTest.java | 5 +- ...InvokerTest.java => LocalMavenInvokerTest.java} | 6 +- ...okerTest.java => ResidentMavenInvokerTest.java} | 6 +- impl/maven-executor/pom.xml | 103 +++++++ .../java/org/apache/maven/api/cli/Executor.java | 20 ++ .../apache/maven/api/cli/ExecutorException.java | 3 +- .../org/apache/maven/api/cli/ExecutorRequest.java | 299 +++++++++++++++++++++ .../executor}/embedded/EmbeddedMavenExecutor.java | 26 +- .../cling/executor/forked/ForkedMavenExecutor.java | 128 +++++++++ .../cling/executor/MavenExecutorTestSupport.java | 187 +++++++++++++ .../embedded/EmbeddedMavenExecutorTest.java} | 26 +- .../executor/forked/ForkedMavenExecutorTest.java} | 26 +- impl/pom.xml | 1 + pom.xml | 1 + src/graph/ReactorGraph.java | 2 +- 35 files changed, 990 insertions(+), 590 deletions(-) diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ExecutorRequest.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ExecutorRequest.java deleted file mode 100644 index 811fe87686..0000000000 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ExecutorRequest.java +++ /dev/null @@ -1,82 +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.api.cli; - -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; - -import org.apache.maven.api.annotations.Experimental; -import org.apache.maven.api.annotations.Immutable; -import org.apache.maven.api.annotations.Nonnull; - -/** - * Represents a request to execute Maven with command-line arguments. - * This interface encapsulates all the necessary information needed to execute - * Maven command with arguments. The arguments were not parsed, they are just passed over - * to executed tool. - * - * @since 4.0.0 - */ -@Immutable -@Experimental -public interface ExecutorRequest { - /** - * The parser request this instance was created from. - */ - @Nonnull - ParserRequest parserRequest(); - - /** - * Returns the current working directory for the Maven execution. - * This is typically the directory from which Maven was invoked. - * - * @return the current working directory path - */ - @Nonnull - Path cwd(); - - /** - * Returns the Maven installation directory. - * This is usually set by the Maven launcher script using the "maven.home" system property. - * - * @return the Maven installation directory path - */ - @Nonnull - Path installationDirectory(); - - /** - * Returns the user's home directory. - * This is typically obtained from the "user.home" system property. - * - * @return the user's home directory path - */ - @Nonnull - Path userHomeDirectory(); - - /** - * Returns the list of extra JVM arguments to be passed to the forked process. - * These arguments allow for customization of the JVM environment in which tool will run. - * This property is used ONLY by executors and invokers that spawn a new JVM. - * - * @return an Optional containing the list of extra JVM arguments, or empty if not specified - */ - @Nonnull - Optional<List<String>> jvmArguments(); -} diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java index ed019663c6..36ad78d8c6 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java @@ -40,7 +40,50 @@ import org.apache.maven.api.services.MessageBuilderFactory; */ @Immutable @Experimental -public interface InvokerRequest extends ExecutorRequest { +public interface InvokerRequest { + /** + * The parser request this instance was created from. + */ + @Nonnull + ParserRequest parserRequest(); + + /** + * Returns the current working directory for the Maven execution. + * This is typically the directory from which Maven was invoked. + * + * @return the current working directory path + */ + @Nonnull + Path cwd(); + + /** + * Returns the Maven installation directory. + * This is usually set by the Maven launcher script using the "maven.home" system property. + * + * @return the Maven installation directory path + */ + @Nonnull + Path installationDirectory(); + + /** + * Returns the user's home directory. + * This is typically obtained from the "user.home" system property. + * + * @return the user's home directory path + */ + @Nonnull + Path userHomeDirectory(); + + /** + * Returns the list of extra JVM arguments to be passed to the forked process. + * These arguments allow for customization of the JVM environment in which tool will run. + * This property is used ONLY by executors and invokers that spawn a new JVM. + * + * @return an Optional containing the list of extra JVM arguments, or empty if not specified + */ + @Nonnull + Optional<List<String>> jvmArguments(); + /** * Shorthand for {@link Logger} to use. */ diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Parser.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Parser.java index 6b791cbaae..0db563a2d5 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Parser.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Parser.java @@ -30,18 +30,6 @@ import org.apache.maven.api.annotations.Nonnull; */ @Experimental public interface Parser { - /** - * Parses the given ParserRequest to create an {@link ExecutorRequest}. - * This method does not interpret tool arguments. - * - * @param parserRequest the request containing all necessary information for parsing - * @return the parsed executor request - * @throws ParserException if there's an error during parsing of the request - * @throws IOException if there's an I/O error during the parsing process - */ - @Nonnull - ExecutorRequest parseExecution(@Nonnull ParserRequest parserRequest) throws ParserException, IOException; - /** * Parses the given ParserRequest to create an {@link InvokerRequest}. * This method does interpret tool arguments. diff --git a/impl/maven-cli/pom.xml b/impl/maven-cli/pom.xml index a34a19eb8b..ab55f3da0d 100644 --- a/impl/maven-cli/pom.xml +++ b/impl/maven-cli/pom.xml @@ -95,6 +95,21 @@ under the License. <version>1.3.0</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.maven.resolver</groupId> + <artifactId>maven-resolver-connector-basic</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.maven.resolver</groupId> + <artifactId>maven-resolver-transport-file</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.maven.resolver</groupId> + <artifactId>maven-resolver-transport-jdk</artifactId> + <scope>test</scope> + </dependency> </dependencies> <build> @@ -145,6 +160,10 @@ under the License. <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> + <properties> + <configurationParameters>junit.jupiter.testclass.order.default = org.junit.jupiter.api.ClassOrderer$OrderAnnotation</configurationParameters> + </properties> + <promoteUserPropertiesToSystemProperties>false</promoteUserPropertiesToSystemProperties> <systemPropertyVariables> <maven.home>${maven.home}</maven.home> </systemPropertyVariables> diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseExecutorRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseExecutorRequest.java deleted file mode 100644 index 00dbef9177..0000000000 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseExecutorRequest.java +++ /dev/null @@ -1,77 +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; - -import java.nio.file.Path; -import java.util.List; -import java.util.Optional; - -import org.apache.maven.api.annotations.Nonnull; -import org.apache.maven.api.annotations.Nullable; -import org.apache.maven.api.cli.ExecutorRequest; -import org.apache.maven.api.cli.ParserRequest; - -import static java.util.Objects.requireNonNull; - -public class BaseExecutorRequest implements ExecutorRequest { - private final ParserRequest parserRequest; - private final Path cwd; - private final Path installationDirectory; - private final Path userHomeDirectory; - private final List<String> jvmArguments; - - @SuppressWarnings("ParameterNumber") - public BaseExecutorRequest( - @Nonnull ParserRequest parserRequest, - @Nonnull Path cwd, - @Nonnull Path installationDirectory, - @Nonnull Path userHomeDirectory, - @Nullable List<String> jvmArguments) { - this.parserRequest = requireNonNull(parserRequest); - this.cwd = requireNonNull(cwd); - this.installationDirectory = requireNonNull(installationDirectory); - this.userHomeDirectory = requireNonNull(userHomeDirectory); - this.jvmArguments = jvmArguments; - } - - @Override - public ParserRequest parserRequest() { - return parserRequest; - } - - @Override - public Path cwd() { - return cwd; - } - - @Override - public Path installationDirectory() { - return installationDirectory; - } - - @Override - public Path userHomeDirectory() { - return userHomeDirectory; - } - - @Override - public Optional<List<String>> jvmArguments() { - return Optional.ofNullable(jvmArguments); - } -} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java index 25dcf754a7..e7b099d74a 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java @@ -33,7 +33,12 @@ import org.apache.maven.api.cli.extensions.CoreExtension; import static java.util.Objects.requireNonNull; -public abstract class BaseInvokerRequest extends BaseExecutorRequest implements InvokerRequest { +public abstract class BaseInvokerRequest implements InvokerRequest { + private final ParserRequest parserRequest; + private final Path cwd; + private final Path installationDirectory; + private final Path userHomeDirectory; + private final List<String> jvmArguments; private final Map<String, String> userProperties; private final Map<String, String> systemProperties; private final Path topDirectory; @@ -58,7 +63,12 @@ public abstract class BaseInvokerRequest extends BaseExecutorRequest implements @Nullable OutputStream err, @Nullable List<CoreExtension> coreExtensions, @Nullable List<String> jvmArguments) { - super(parserRequest, cwd, installationDirectory, userHomeDirectory, jvmArguments); + this.parserRequest = requireNonNull(parserRequest); + this.cwd = requireNonNull(cwd); + this.installationDirectory = requireNonNull(installationDirectory); + this.userHomeDirectory = requireNonNull(userHomeDirectory); + this.jvmArguments = jvmArguments; + this.userProperties = requireNonNull(userProperties); this.systemProperties = requireNonNull(systemProperties); this.topDirectory = requireNonNull(topDirectory); @@ -70,6 +80,31 @@ public abstract class BaseInvokerRequest extends BaseExecutorRequest implements this.err = err; } + @Override + public ParserRequest parserRequest() { + return parserRequest; + } + + @Override + public Path cwd() { + return cwd; + } + + @Override + public Path installationDirectory() { + return installationDirectory; + } + + @Override + public Path userHomeDirectory() { + return userHomeDirectory; + } + + @Override + public Optional<List<String>> jvmArguments() { + return Optional.ofNullable(jvmArguments); + } + @Override public Map<String, String> userProperties() { return userProperties; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java index 853fea00d8..1b72a0a6cd 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseParser.java @@ -37,7 +37,6 @@ import java.util.stream.Collectors; import org.apache.maven.api.Constants; import org.apache.maven.api.annotations.Nullable; -import org.apache.maven.api.cli.ExecutorRequest; import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.api.cli.Options; import org.apache.maven.api.cli.Parser; @@ -94,25 +93,6 @@ public abstract class BaseParser implements Parser { } } - @Override - public ExecutorRequest parseExecution(ParserRequest parserRequest) throws ParserException, IOException { - requireNonNull(parserRequest); - - LocalContext context = new LocalContext(parserRequest); - - // the basics - context.cwd = requireNonNull(getCwd(context)); - context.installationDirectory = requireNonNull(getInstallationDirectory(context)); - context.userHomeDirectory = requireNonNull(getUserHomeDirectory(context)); - - return getExecutionRequest(context); - } - - protected ExecutorRequest getExecutionRequest(LocalContext context) { - return new BaseExecutorRequest( - context.parserRequest, context.cwd, context.installationDirectory, context.userHomeDirectory, null); - } - @Override public InvokerRequest parseInvocation(ParserRequest parserRequest) throws ParserException, IOException { requireNonNull(parserRequest); diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupContext.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupContext.java index 51a2ad96d9..d0cae34c5f 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupContext.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupContext.java @@ -57,7 +57,7 @@ public class LookupContext implements AutoCloseable { Map<String, String> user = new HashMap<>(invokerRequest.userProperties()); user.put("session.topDirectory", invokerRequest.topDirectory().toString()); - if (invokerRequest.rootDirectory().isEmpty()) { + if (invokerRequest.rootDirectory().isPresent()) { user.put( "session.rootDirectory", invokerRequest.rootDirectory().get().toString()); @@ -112,4 +112,15 @@ public class LookupContext implements AutoCloseable { throw exception; } } + + protected void closeContainer() { + if (containerCapsule != null) { + try { + containerCapsule.close(); + } finally { + lookup = null; + containerCapsule = null; + } + } + } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index 766414d165..b4c3b57484 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -64,10 +64,6 @@ import org.apache.maven.bridge.MavenRepositorySystem; import org.apache.maven.cling.invoker.spi.PropertyContributorsHolder; import org.apache.maven.cling.logging.Slf4jConfiguration; import org.apache.maven.cling.logging.Slf4jConfigurationFactory; -import org.apache.maven.cling.transfer.ConsoleMavenTransferListener; -import org.apache.maven.cling.transfer.QuietMavenTransferListener; -import org.apache.maven.cling.transfer.SimplexTransferListener; -import org.apache.maven.cling.transfer.Slf4jMavenTransferListener; import org.apache.maven.cling.utils.CLIReportingUtils; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.internal.impl.SettingsUtilsV4; @@ -76,7 +72,6 @@ import org.apache.maven.jline.MessageUtils; import org.apache.maven.logging.LoggingOutputStream; import org.apache.maven.logging.api.LogLevelRecorder; import org.apache.maven.slf4j.MavenSimpleLogger; -import org.eclipse.aether.transfer.TransferListener; import org.jline.terminal.Terminal; import org.jline.terminal.TerminalBuilder; import org.jline.terminal.impl.AbstractPosixTerminal; @@ -132,6 +127,7 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker validate(context); prepare(context); configureLogging(context); + createTerminal(context); activateLogging(context); helpOrVersionAndMayExit(context); preCommands(context); @@ -216,18 +212,9 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker context.slf4jConfiguration.setRootLoggerLevel(context.loggerLevel); // else fall back to default log level specified in conf // see https://issues.apache.org/jira/browse/MNG-2570 - - // JLine is quite slow to start due to the native library unpacking and loading - // so boot it asynchronously - context.terminal = createTerminal(context); - context.closeables.add(MessageUtils::systemUninstall); - MessageUtils.registerShutdownHook(); // safety belt - if (context.coloredOutput != null) { - MessageUtils.setColorEnabled(context.coloredOutput); - } } - protected Terminal createTerminal(C context) { + protected void createTerminal(C context) { MessageUtils.systemInstall( builder -> { builder.streams( @@ -243,7 +230,15 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker } }, terminal -> doConfigureWithTerminal(context, terminal)); - return MessageUtils.getTerminal(); + + context.terminal = MessageUtils.getTerminal(); + // JLine is quite slow to start due to the native library unpacking and loading + // so boot it asynchronously + context.closeables.add(MessageUtils::systemUninstall); + MessageUtils.registerShutdownHook(); // safety belt + if (context.coloredOutput != null) { + MessageUtils.setColorEnabled(context.coloredOutput); + } } protected void doConfigureWithTerminal(C context, Terminal terminal) { @@ -271,7 +266,7 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker if (options.logFile().isPresent()) { Path logFile = context.cwdResolver.apply(options.logFile().get()); try { - PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(logFile)); + PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(logFile), true); context.closeables.add(printWriter); return printWriter::println; } catch (IOException e) { @@ -376,7 +371,7 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker protected void container(C context) throws Exception { context.containerCapsule = createContainerCapsuleFactory().createContainerCapsule(this, context); - context.closeables.add(context.containerCapsule); + context.closeables.add(context::closeContainer); context.lookup = context.containerCapsule.getLookup(); // refresh logger in case container got customized by spy @@ -743,24 +738,5 @@ public abstract class LookupInvoker<C extends LookupContext> implements Invoker return ciEnv != null && !"false".equals(ciEnv); } - protected TransferListener determineTransferListener(C context, boolean noTransferProgress) { - boolean quiet = context.invokerRequest.options().quiet().orElse(false); - boolean logFile = context.invokerRequest.options().logFile().isPresent(); - boolean runningOnCI = isRunningOnCI(context); - boolean quietCI = runningOnCI - && !context.invokerRequest.options().forceInteractive().orElse(false); - - if (quiet || noTransferProgress || quietCI) { - return new QuietMavenTransferListener(); - } else if (context.interactive && !logFile) { - return new SimplexTransferListener(new ConsoleMavenTransferListener( - context.invokerRequest.messageBuilderFactory(), - context.terminal.writer(), - context.invokerRequest.options().verbose().orElse(false))); - } else { - return new Slf4jMavenTransferListener(); - } - } - protected abstract int execute(C context) throws Exception; } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/Utils.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/Utils.java index b278724a77..853cccc931 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/Utils.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/Utils.java @@ -18,7 +18,6 @@ */ package org.apache.maven.cling.invoker; -import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.util.Collection; @@ -47,14 +46,6 @@ import static java.util.Objects.requireNonNull; public final class Utils { private Utils() {} - @Nullable - public static File toFile(Path path) { - if (path != null) { - return path.toFile(); - } - return null; - } - @Nonnull public static String stripLeadingAndTrailingQuotes(String str) { requireNonNull(str, "str"); diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java index 7ffbcab9e9..c533b601ac 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java @@ -32,6 +32,12 @@ public class MavenContext extends LookupContext { public BuildEventListener buildEventListener; public EventSpyDispatcher eventSpyDispatcher; - public Maven maven; + + @Override + protected void closeContainer() { + eventSpyDispatcher = null; + maven = null; + super.closeContainer(); + } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java index ffcf67c3c2..579f1e5683 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvoker.java @@ -50,6 +50,10 @@ import org.apache.maven.cling.event.ExecutionEventLogger; import org.apache.maven.cling.invoker.LookupInvoker; import org.apache.maven.cling.invoker.ProtoLookup; import org.apache.maven.cling.invoker.Utils; +import org.apache.maven.cling.transfer.ConsoleMavenTransferListener; +import org.apache.maven.cling.transfer.QuietMavenTransferListener; +import org.apache.maven.cling.transfer.SimplexTransferListener; +import org.apache.maven.cling.transfer.Slf4jMavenTransferListener; import org.apache.maven.cling.utils.CLIReportingUtils; import org.apache.maven.eventspy.internal.EventSpyDispatcher; import org.apache.maven.exception.DefaultExceptionHandler; @@ -389,12 +393,27 @@ public abstract class MavenInvoker<C extends MavenContext> extends LookupInvoker if (context.eventSpyDispatcher != null) { listener = context.eventSpyDispatcher.chainListener(listener); } - listener = new LoggingExecutionListener(listener, determineBuildEventListener(context)); - return listener; + return new LoggingExecutionListener(listener, determineBuildEventListener(context)); } protected TransferListener determineTransferListener(C context, boolean noTransferProgress) { - TransferListener delegate = super.determineTransferListener(context, noTransferProgress); + boolean quiet = context.invokerRequest.options().quiet().orElse(false); + boolean logFile = context.invokerRequest.options().logFile().isPresent(); + boolean runningOnCI = isRunningOnCI(context); + boolean quietCI = runningOnCI + && !context.invokerRequest.options().forceInteractive().orElse(false); + + TransferListener delegate; + if (quiet || noTransferProgress || quietCI) { + delegate = new QuietMavenTransferListener(); + } else if (context.interactive && !logFile) { + delegate = new SimplexTransferListener(new ConsoleMavenTransferListener( + context.invokerRequest.messageBuilderFactory(), + context.terminal.writer(), + context.invokerRequest.options().verbose().orElse(false))); + } else { + delegate = new Slf4jMavenTransferListener(); + } return new MavenTransferListener(delegate, determineBuildEventListener(context)); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenExecutor.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenExecutor.java deleted file mode 100644 index b8e25b84df..0000000000 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenExecutor.java +++ /dev/null @@ -1,73 +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 org.apache.maven.api.cli.Executor; -import org.apache.maven.api.cli.ExecutorException; -import org.apache.maven.api.cli.ExecutorRequest; -import org.apache.maven.internal.impl.model.profile.Os; - -import static java.util.Objects.requireNonNull; - -/** - * Forked executor implementation, that spawns a subprocess with Maven from the installation directory. - */ -public class ForkedMavenExecutor implements Executor { - @Override - public int execute(ExecutorRequest executorRequest) throws ExecutorException { - requireNonNull(executorRequest); - validate(executorRequest); - - ArrayList<String> cmdAndArguments = new ArrayList<>(); - cmdAndArguments.add(executorRequest - .installationDirectory() - .resolve("bin") - .resolve( - Os.IS_WINDOWS - ? executorRequest.parserRequest().command() + ".cmd" - : executorRequest.parserRequest().command()) - .toString()); - - cmdAndArguments.addAll(executorRequest.parserRequest().args()); - - try { - ProcessBuilder pb = new ProcessBuilder() - .directory(executorRequest.cwd().toFile()) - .command(cmdAndArguments); - - if (executorRequest.jvmArguments().isPresent()) { - pb.environment() - .put( - "MAVEN_OPTS", - String.join(" ", executorRequest.jvmArguments().get())); - } - - return pb.start().waitFor(); - } catch (IOException e) { - throw new ExecutorException("IO problem while executing command: " + cmdAndArguments, e); - } catch (InterruptedException e) { - throw new ExecutorException("Interrupted while executing command: " + cmdAndArguments, e); - } - } - - protected void validate(ExecutorRequest executorRequest) throws ExecutorException {} -} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenContext.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenContext.java index 7d96785d4f..fb1e2ed7ea 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenContext.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenContext.java @@ -29,12 +29,12 @@ public class ResidentMavenContext extends MavenContext { } @Override - public void close() throws InvokerException { - // we are resident, we do not shut down here + protected void closeContainer() { + // we are resident; we do not shut down here } public void shutDown() throws InvokerException { - super.close(); + super.closeContainer(); } public ResidentMavenContext copy(InvokerRequest invokerRequest) { @@ -43,16 +43,9 @@ public class ResidentMavenContext extends MavenContext { } ResidentMavenContext shadow = new ResidentMavenContext(invokerRequest); - shadow.logger = logger; - shadow.loggerFactory = loggerFactory; - shadow.loggerLevel = loggerLevel; + // we carry over only "resident" things shadow.containerCapsule = containerCapsule; shadow.lookup = lookup; - - shadow.interactive = interactive; - shadow.localRepositoryPath = localRepositoryPath; - shadow.effectiveSettings = effectiveSettings; - shadow.eventSpyDispatcher = eventSpyDispatcher; shadow.maven = maven; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java index 6511072093..b07abab37e 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/AbstractMavenTransferListener.java @@ -18,7 +18,6 @@ */ package org.apache.maven.cling.transfer; -import java.io.PrintStream; import java.io.PrintWriter; import org.apache.maven.api.services.MessageBuilder; @@ -37,10 +36,6 @@ public abstract class AbstractMavenTransferListener extends AbstractTransferList protected final MessageBuilderFactory messageBuilderFactory; protected final PrintWriter out; - protected AbstractMavenTransferListener(MessageBuilderFactory messageBuilderFactory, PrintStream out) { - this(messageBuilderFactory, new PrintWriter(out)); - } - protected AbstractMavenTransferListener(MessageBuilderFactory messageBuilderFactory, PrintWriter out) { this.messageBuilderFactory = messageBuilderFactory; this.out = out; diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListener.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListener.java index 46a17908ec..3b4078c5da 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListener.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/transfer/ConsoleMavenTransferListener.java @@ -18,7 +18,6 @@ */ package org.apache.maven.cling.transfer; -import java.io.PrintStream; import java.io.PrintWriter; import java.util.Iterator; import java.util.LinkedHashMap; @@ -43,11 +42,6 @@ public class ConsoleMavenTransferListener extends AbstractMavenTransferListener private final boolean printResourceNames; private int lastLength; - public ConsoleMavenTransferListener( - MessageBuilderFactory messageBuilderFactory, PrintStream out, boolean printResourceNames) { - this(messageBuilderFactory, new PrintWriter(out), printResourceNames); - } - public ConsoleMavenTransferListener( MessageBuilderFactory messageBuilderFactory, PrintWriter out, boolean printResourceNames) { super(messageBuilderFactory, out); diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenExecutorTestSupport.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenExecutorTestSupport.java deleted file mode 100644 index c3c487dcc1..0000000000 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenExecutorTestSupport.java +++ /dev/null @@ -1,64 +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; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; -import java.util.List; - -import org.apache.maven.api.cli.Executor; -import org.apache.maven.api.cli.Parser; -import org.apache.maven.api.cli.ParserRequest; -import org.apache.maven.cling.invoker.ProtoLogger; -import org.apache.maven.jline.JLineMessageBuilderFactory; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public abstract class MavenExecutorTestSupport { - - protected void execute(Path cwd, Collection<String> goals) throws Exception { - Files.createDirectory(cwd.resolve(".mvn")); - Path pom = cwd.resolve("pom.xml").toAbsolutePath(); - Files.writeString(pom, MavenTestSupport.POM_STRING); - Path appJava = cwd.resolve("src/main/java/org/apache/maven/samples/sample/App.java"); - Files.createDirectories(appJava.getParent()); - Files.writeString(appJava, MavenTestSupport.APP_JAVA_STRING); - - Parser parser = createParser(); - try (Executor invoker = createExecutor()) { - for (String goal : goals) { - Path logFile = cwd.resolve(goal + "-build.log").toAbsolutePath(); - int exitCode = invoker.execute(parser.parseExecution(ParserRequest.mvn( - List.of("-l", logFile.toString(), goal), - new ProtoLogger(), - new JLineMessageBuilderFactory()) - .cwd(cwd) - .build())); - String log = Files.readString(logFile); - System.out.println(log); - assertEquals(0, exitCode, log); - } - } - } - - protected abstract Executor createExecutor(); - - protected abstract Parser createParser(); -} diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java index d3dcf3fc9a..4f3f46d4a0 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java @@ -34,6 +34,51 @@ import org.junit.jupiter.api.Assumptions; import static org.junit.jupiter.api.Assertions.assertEquals; public abstract class MavenInvokerTestSupport { + public static final String POM_STRING = + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.samples</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.junit</groupId> + <artifactId>junit-bom</artifactId> + <version>5.11.1</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + </project> + """; + + public static final String APP_JAVA_STRING = + """ + package org.apache.maven.samples.sample; + + public class App { + public static void main(String... args) { + System.out.println("Hello World!"); + } + } + """; protected void invoke(Path cwd, Collection<String> goals) throws Exception { // works only in recent Maven4 @@ -45,10 +90,10 @@ public abstract class MavenInvokerTestSupport { Files.createDirectory(cwd.resolve(".mvn")); Path pom = cwd.resolve("pom.xml").toAbsolutePath(); - Files.writeString(pom, MavenTestSupport.POM_STRING); + Files.writeString(pom, POM_STRING); Path appJava = cwd.resolve("src/main/java/org/apache/maven/samples/sample/App.java"); Files.createDirectories(appJava.getParent()); - Files.writeString(appJava, MavenTestSupport.APP_JAVA_STRING); + Files.writeString(appJava, APP_JAVA_STRING); Parser parser = createParser(); try (Invoker invoker = createInvoker()) { diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenTestSupport.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenTestSupport.java deleted file mode 100644 index 9c1ebeff36..0000000000 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenTestSupport.java +++ /dev/null @@ -1,69 +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; - -public final class MavenTestSupport { - private MavenTestSupport() {} - - public static final String POM_STRING = - """ - <?xml version="1.0" encoding="UTF-8"?> - <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> - - <modelVersion>4.0.0</modelVersion> - - <groupId>org.apache.maven.samples</groupId> - <artifactId>sample</artifactId> - <version>1.0.0</version> - - <dependencyManagement> - <dependencies> - <dependency> - <groupId>org.junit</groupId> - <artifactId>junit-bom</artifactId> - <version>5.11.1</version> - <type>pom</type> - <scope>import</scope> - </dependency> - </dependencies> - </dependencyManagement> - - <dependencies> - <dependency> - <groupId>org.junit.jupiter</groupId> - <artifactId>junit-jupiter-api</artifactId> - <scope>test</scope> - </dependency> - </dependencies> - - </project> - """; - - public static final String APP_JAVA_STRING = - """ - package org.apache.maven.samples.sample; - - public class App { - public static void main(String... args) { - System.out.println("Hello World!"); - } - } - """; -} diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/embedded/EmbeddedMavenExecutorTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/embedded/EmbeddedMavenExecutorTest.java deleted file mode 100644 index 7199d43fdd..0000000000 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/embedded/EmbeddedMavenExecutorTest.java +++ /dev/null @@ -1,61 +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.embedded; - -import java.nio.file.Path; -import java.util.List; - -import org.apache.maven.api.cli.Executor; -import org.apache.maven.api.cli.Parser; -import org.apache.maven.cling.invoker.mvn.MavenExecutorTestSupport; -import org.apache.maven.cling.invoker.mvn.MavenParser; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.CleanupMode; -import org.junit.jupiter.api.io.TempDir; - -/** - * Forked UT: it cannot use jimFS as it runs in child process. - */ -@Disabled( - "The tests reuse properties from the JVM being launched, thus may lead to failures depending on which options are used") -public class EmbeddedMavenExecutorTest extends MavenExecutorTestSupport { - - @Override - protected Executor createExecutor() { - return new EmbeddedMavenExecutor(); - } - - @Override - protected Parser createParser() { - return new MavenParser(); - } - - @Test - void defaultFs(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception { - System.setProperty("maven.home", "/home/cstamas/Tools/maven/apache-maven-4.0.0-beta-6-SNAPSHOT"); - execute(tempDir, List.of("verify")); - } - - @Test - void defaultFs3x(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception { - System.setProperty("maven.home", "/home/cstamas/.sdkman/candidates/maven/3.9.9"); - execute(tempDir, List.of("verify")); - } -} diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvokerTest.java index 01ddfe3267..56c1cd2388 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/forked/ForkedMavenInvokerTest.java @@ -25,7 +25,7 @@ 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.Disabled; +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; @@ -33,8 +33,7 @@ import org.junit.jupiter.api.io.TempDir; /** * Forked UT: it cannot use jimFS as it runs in child process. */ -@Disabled( - "The tests reuse properties from the JVM being launched, thus may lead to failures depending on which options are used") +@Order(300) public class ForkedMavenInvokerTest extends MavenInvokerTestSupport { @Override diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/DefaultLocalMavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/LocalMavenInvokerTest.java similarity index 91% rename from impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/DefaultLocalMavenInvokerTest.java rename to impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/LocalMavenInvokerTest.java index d4389788fa..11b8a4d878 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/DefaultLocalMavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/local/LocalMavenInvokerTest.java @@ -31,6 +31,7 @@ import org.apache.maven.cling.invoker.mvn.MavenInvokerTestSupport; import org.apache.maven.cling.invoker.mvn.MavenParser; import org.codehaus.plexus.classworlds.ClassWorld; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; @@ -38,9 +39,8 @@ import org.junit.jupiter.api.io.TempDir; /** * Local UT. */ -@Disabled( - "The tests reuse properties from the JVM being launched, thus may lead to failures depending on which options are used") -public class DefaultLocalMavenInvokerTest extends MavenInvokerTestSupport { +@Order(200) +public class LocalMavenInvokerTest extends MavenInvokerTestSupport { @Override protected Invoker createInvoker() { return new LocalMavenInvoker(ProtoLookup.builder() diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java similarity index 91% rename from impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerTest.java rename to impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java index de65db9503..004b234de6 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/DefaultResidentMavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java @@ -31,6 +31,7 @@ import org.apache.maven.cling.invoker.mvn.MavenInvokerTestSupport; import org.apache.maven.cling.invoker.mvn.MavenParser; import org.codehaus.plexus.classworlds.ClassWorld; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; @@ -38,9 +39,8 @@ import org.junit.jupiter.api.io.TempDir; /** * Resident UT. */ -@Disabled( - "The tests reuse properties from the JVM being launched, thus may lead to failures depending on which options are used") -public class DefaultResidentMavenInvokerTest extends MavenInvokerTestSupport { +@Order(100) +public class ResidentMavenInvokerTest extends MavenInvokerTestSupport { @Override protected Invoker createInvoker() { diff --git a/impl/maven-executor/pom.xml b/impl/maven-executor/pom.xml new file mode 100644 index 0000000000..da060ae30a --- /dev/null +++ b/impl/maven-executor/pom.xml @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.maven</groupId> + <artifactId>maven</artifactId> + <version>4.0.0-beta-6-SNAPSHOT</version> + + <relativePath>../../</relativePath> + </parent> + + <artifactId>maven-executor</artifactId> + + <name>Maven 4 Executor</name> + <description>Maven 4 Executor, for executing Maven 3/4.</description> + + <properties> + <maven3version>3.9.9</maven3version> + <maven4version>4.0.0-beta-5</maven4version> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-api-meta</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>prepare-maven-distros</id> + <goals> + <goal>unpack</goal> + </goals> + <phase>generate-test-resources</phase> + <configuration> + <artifactItems> + <artifactItem> + <groupId>org.apache.maven</groupId> + <artifactId>apache-maven</artifactId> + <version>${maven3version}</version> + <classifier>bin</classifier> + <type>zip</type> + </artifactItem> + <artifactItem> + <groupId>org.apache.maven</groupId> + <artifactId>apache-maven</artifactId> + <version>${maven4version}</version> + <classifier>bin</classifier> + <type>zip</type> + </artifactItem> + </artifactItems> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <promoteUserPropertiesToSystemProperties>false</promoteUserPropertiesToSystemProperties> + <systemPropertyVariables> + <maven3version>${maven3version}</maven3version> + <maven4version>${maven4version}</maven4version> + <maven3home>${project.build.directory}/dependency/apache-maven-${maven3version}</maven3home> + <maven4home>${project.build.directory}/dependency/apache-maven-${maven4version}</maven4home> + </systemPropertyVariables> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Executor.java b/impl/maven-executor/src/main/java/org/apache/maven/api/cli/Executor.java similarity index 70% rename from api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Executor.java rename to impl/maven-executor/src/main/java/org/apache/maven/api/cli/Executor.java index 60cd38921a..fec88707df 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Executor.java +++ b/impl/maven-executor/src/main/java/org/apache/maven/api/cli/Executor.java @@ -30,6 +30,14 @@ import org.apache.maven.api.annotations.Nonnull; */ @Experimental public interface Executor extends AutoCloseable { + // Logic borrowed from Commons-Lang3 + boolean IS_WINDOWS = System.getProperty("os.name", "unknown").startsWith("Windows"); + + /** + * Maven version string returned when the actual version of Maven cannot be determinet. + */ + String UNKNOWN_VERSION = "unknown"; + /** * Invokes the tool application using the provided {@link ExecutorRequest}. * This method is responsible for executing the command or build @@ -41,6 +49,18 @@ public interface Executor extends AutoCloseable { */ int execute(@Nonnull ExecutorRequest executorRequest) throws ExecutorException; + /** + * Returns the Maven version that provided {@link ExecutorRequest} point at (would use). Please not, that this + * operation, depending on underlying implementation may be costly. If caller use this method often, it is + * caller responsibility to properly cache returned values (key can be {@link ExecutorRequest#installationDirectory()}. + * + * @param executorRequest the request containing all necessary information for the execution + * @return a string representing the Maven version or {@link #UNKNOWN_VERSION} + * @throws ExecutorException if an error occurs during the execution process + */ + @Nonnull + String mavenVersion(@Nonnull ExecutorRequest executorRequest) throws ExecutorException; + /** * Closes and disposes of this {@link Executor} instance, releasing any resources it may hold. * This method is called automatically when using try-with-resources statements. diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ExecutorException.java b/impl/maven-executor/src/main/java/org/apache/maven/api/cli/ExecutorException.java similarity index 94% rename from api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ExecutorException.java rename to impl/maven-executor/src/main/java/org/apache/maven/api/cli/ExecutorException.java index 8ce5bf157d..d1c4a918c1 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ExecutorException.java +++ b/impl/maven-executor/src/main/java/org/apache/maven/api/cli/ExecutorException.java @@ -20,7 +20,6 @@ package org.apache.maven.api.cli; import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Nullable; -import org.apache.maven.api.services.MavenException; /** * Represents an exception that occurs during the execution of a Maven build or command. @@ -30,7 +29,7 @@ import org.apache.maven.api.services.MavenException; * @since 4.0.0 */ @Experimental -public class ExecutorException extends MavenException { +public class ExecutorException extends RuntimeException { /** * Constructs a new {@code InvokerException} with the specified detail message. * diff --git a/impl/maven-executor/src/main/java/org/apache/maven/api/cli/ExecutorRequest.java b/impl/maven-executor/src/main/java/org/apache/maven/api/cli/ExecutorRequest.java new file mode 100644 index 0000000000..1f40efa4b6 --- /dev/null +++ b/impl/maven-executor/src/main/java/org/apache/maven/api/cli/ExecutorRequest.java @@ -0,0 +1,299 @@ +/* + * 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; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Immutable; +import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.annotations.Nullable; + +import static java.util.Objects.requireNonNull; + +/** + * Represents a request to execute Maven with command-line arguments. + * This interface encapsulates all the necessary information needed to execute + * Maven command with arguments. The arguments were not parsed, they are just passed over + * to executed tool. + * + * @since 4.0.0 + */ +@Immutable +@Experimental +public interface ExecutorRequest { + /** + * The command to execute, ie "mvn". + */ + @Nonnull + String command(); + + /** + * The immutable list of arguments to pass to the command. + */ + @Nonnull + List<String> arguments(); + + /** + * Returns the current working directory for the Maven execution. + * This is typically the directory from which Maven was invoked. + * + * @return the current working directory path + */ + @Nonnull + Path cwd(); + + /** + * Returns the Maven installation directory. + * This is usually set by the Maven launcher script using the "maven.home" system property. + * + * @return the Maven installation directory path + */ + @Nonnull + Path installationDirectory(); + + /** + * Returns the user's home directory. + * This is typically obtained from the "user.home" system property. + * + * @return the user's home directory path + */ + @Nonnull + Path userHomeDirectory(); + + /** + * Returns the list of extra JVM arguments to be passed to the forked process. + * These arguments allow for customization of the JVM environment in which tool will run. + * This property is used ONLY by executors and invokers that spawn a new JVM. + * + * @return an Optional containing the list of extra JVM arguments, or empty if not specified + */ + @Nonnull + Optional<List<String>> jvmArguments(); + + /** + * Returns {@link Builder} for this instance. + */ + @Nonnull + default Builder toBuilder() { + return new Builder( + command(), + arguments(), + cwd(), + installationDirectory(), + userHomeDirectory(), + jvmArguments().orElse(null)); + } + + /** + * Returns new empty builder. + */ + @Nonnull + static Builder empyBuilder() { + return new Builder(); + } + + /** + * Returns new builder pre-set to run Maven. The discovery of maven home is attempted. + */ + @Nonnull + static Builder mavenBuilder(@Nullable Path installationDirectory) { + return new Builder( + "mvn", + null, + getCanonicalPath(Paths.get(System.getProperty("user.dir"))), + installationDirectory != null ? getCanonicalPath(installationDirectory) : discoverMavenHome(), + getCanonicalPath(Paths.get(System.getProperty("user.home"))), + null); + } + + class Builder { + private String command; + private List<String> arguments; + private Path cwd; + private Path installationDirectory; + private Path userHomeDirectory; + private List<String> jvmArguments; + + private Builder() {} + + private Builder( + String command, + List<String> arguments, + Path cwd, + Path installationDirectory, + Path userHomeDirectory, + List<String> jvmArguments) { + this.command = command; + this.arguments = arguments; + this.cwd = cwd; + this.installationDirectory = installationDirectory; + this.userHomeDirectory = userHomeDirectory; + this.jvmArguments = jvmArguments; + } + + @Nonnull + public Builder command(String command) { + this.command = requireNonNull(command, "command"); + return this; + } + + @Nonnull + public Builder arguments(List<String> arguments) { + this.arguments = requireNonNull(arguments, "arguments"); + return this; + } + + @Nonnull + public Builder argument(String argument) { + if (arguments == null) { + arguments = new ArrayList<>(); + } + this.arguments.add(requireNonNull(argument, "argument")); + return this; + } + + @Nonnull + public Builder cwd(Path cwd) { + this.cwd = requireNonNull(cwd, "cwd"); + return this; + } + + @Nonnull + public Builder installationDirectory(Path installationDirectory) { + this.installationDirectory = requireNonNull(installationDirectory, "installationDirectory"); + return this; + } + + @Nonnull + public Builder userHomeDirectory(Path userHomeDirectory) { + this.userHomeDirectory = requireNonNull(userHomeDirectory, "userHomeDirectory"); + return this; + } + + @Nonnull + public Builder jvmArguments(List<String> jvmArguments) { + this.jvmArguments = jvmArguments; + return this; + } + + @Nonnull + public Builder jvmArgument(String jvmArgument) { + if (jvmArguments == null) { + jvmArguments = new ArrayList<>(); + } + this.jvmArguments.add(requireNonNull(jvmArgument, "jvmArgument")); + return this; + } + + @Nonnull + public ExecutorRequest build() { + return new Impl(command, arguments, cwd, installationDirectory, userHomeDirectory, jvmArguments); + } + + private static class Impl implements ExecutorRequest { + private final String command; + private final List<String> arguments; + private final Path cwd; + private final Path installationDirectory; + private final Path userHomeDirectory; + private final List<String> jvmArguments; + + private Impl( + String command, + List<String> arguments, + Path cwd, + Path installationDirectory, + Path userHomeDirectory, + List<String> jvmArguments) { + this.command = requireNonNull(command); + this.arguments = arguments == null ? List.of() : List.copyOf(arguments); + this.cwd = requireNonNull(cwd); + this.installationDirectory = requireNonNull(installationDirectory); + this.userHomeDirectory = requireNonNull(userHomeDirectory); + this.jvmArguments = jvmArguments != null ? List.copyOf(jvmArguments) : null; + } + + @Override + public String command() { + return command; + } + + @Override + public List<String> arguments() { + return arguments; + } + + @Override + public Path cwd() { + return cwd; + } + + @Override + public Path installationDirectory() { + return installationDirectory; + } + + @Override + public Path userHomeDirectory() { + return userHomeDirectory; + } + + @Override + public Optional<List<String>> jvmArguments() { + return Optional.ofNullable(jvmArguments); + } + + @Override + public String toString() { + return "ExecutionRequest{" + "command='" + + command + '\'' + ", arguments=" + + arguments + ", cwd=" + + cwd + ", installationDirectory=" + + installationDirectory + ", userHomeDirectory=" + + userHomeDirectory + ", jvmArguments=" + + jvmArguments + '}'; + } + } + } + + @Nonnull + static Path discoverMavenHome() { + String mavenHome = System.getProperty("maven.home"); + if (mavenHome == null) { + throw new ExecutorException("requires maven.home Java System Property set"); + } + return getCanonicalPath(Paths.get(mavenHome)); + } + + @Nonnull + static Path getCanonicalPath(Path path) { + requireNonNull(path, "path"); + try { + return path.toRealPath(); + } catch (IOException e) { + return getCanonicalPath(path.getParent()).resolve(path.getFileName()); + } + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/embedded/EmbeddedMavenExecutor.java b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java similarity index 93% rename from impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/embedded/EmbeddedMavenExecutor.java rename to impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java index ff34b8c489..350b0cc648 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/embedded/EmbeddedMavenExecutor.java +++ b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutor.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cling.invoker.mvn.embedded; +package org.apache.maven.cling.executor.embedded; import java.io.Closeable; import java.io.IOException; @@ -102,6 +102,13 @@ public class EmbeddedMavenExecutor implements Executor { } } + @Override + public String mavenVersion(ExecutorRequest executorRequest) throws ExecutorException { + requireNonNull(executorRequest); + validate(executorRequest); + return mayCreate(executorRequest).version; + } + protected Context mayCreate(ExecutorRequest executorRequest) { Path installation = executorRequest.installationDirectory(); if (!Files.isDirectory(installation)) { @@ -148,7 +155,7 @@ public class EmbeddedMavenExecutor implements Executor { Object classWorld = launcherClass.getMethod("getWorld").invoke(launcher); Class<?> cliClass = (Class<?>) launcherClass.getMethod("getMainClass").invoke(launcher); - String version = getMavenVersion(cliClass.getClassLoader()); + String version = getMavenVersion(cliClass); Function<ExecutorRequest, Integer> exec; if (version.startsWith("3.")) { @@ -160,10 +167,7 @@ public class EmbeddedMavenExecutor implements Executor { exec = r -> { try { return (int) doMain.invoke(mavenCli, new Object[] { - r.parserRequest().args().toArray(new String[0]), - r.cwd().toString(), - null, - null + r.arguments().toArray(new String[0]), r.cwd().toString(), null, null }); } catch (Exception e) { throw new ExecutorException("Failed to execute", e); @@ -174,8 +178,7 @@ public class EmbeddedMavenExecutor implements Executor { Method mainMethod = cliClass.getMethod("main", String[].class, classWorld.getClass()); exec = r -> { try { - return (int) mainMethod.invoke( - null, r.parserRequest().args().toArray(new String[0]), classWorld); + return (int) mainMethod.invoke(null, r.arguments().toArray(new String[0]), classWorld); } catch (Exception e) { throw new ExecutorException("Failed to execute", e); } @@ -250,10 +253,9 @@ public class EmbeddedMavenExecutor implements Executor { urls.toArray(new URL[0]), ClassLoader.getSystemClassLoader().getParent()); } - public String getMavenVersion(ClassLoader classLoader) throws IOException { + protected String getMavenVersion(Class<?> clazz) throws IOException { Properties props = new Properties(); - try (InputStream is = - classLoader.getResourceAsStream("/META-INF/maven/org.apache.maven/maven-core/pom.properties")) { + try (InputStream is = clazz.getResourceAsStream("/META-INF/maven/org.apache.maven/maven-core/pom.properties")) { if (is != null) { props.load(is); } @@ -261,7 +263,7 @@ public class EmbeddedMavenExecutor implements Executor { if (version != null) { return version; } - return "unknown"; + return UNKNOWN_VERSION; } } } diff --git a/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java new file mode 100644 index 0000000000..47ae235cac --- /dev/null +++ b/impl/maven-executor/src/main/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutor.java @@ -0,0 +1,128 @@ +/* + * 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.executor.forked; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.apache.maven.api.cli.Executor; +import org.apache.maven.api.cli.ExecutorException; +import org.apache.maven.api.cli.ExecutorRequest; + +import static java.util.Objects.requireNonNull; + +/** + * Forked executor implementation, that spawns a subprocess with Maven from the installation directory. Very costly + * but provides the best isolation. + */ +public class ForkedMavenExecutor implements Executor { + @Override + public int execute(ExecutorRequest executorRequest) throws ExecutorException { + requireNonNull(executorRequest); + validate(executorRequest); + + return doExecute(executorRequest, null); + } + + @Override + public String mavenVersion(ExecutorRequest executorRequest) throws ExecutorException { + requireNonNull(executorRequest); + validate(executorRequest); + try { + Path cwd = Files.createTempDirectory("forked-executor-maven-version"); + try { + ArrayList<String> stdout = new ArrayList<>(); + int exitCode = doExecute( + executorRequest.toBuilder() + .cwd(cwd) + .arguments(List.of("--version", "--color", "never")) + .build(), + p -> { + String line; + try (BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()))) { + while ((line = br.readLine()) != null) { + stdout.add(line); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + if (exitCode == 0) { + for (String line : stdout) { + if (line.startsWith("Apache Maven ")) { + return line.substring(13, line.indexOf("(") - 1); + } + } + return UNKNOWN_VERSION; + } else { + throw new ExecutorException( + "Maven version query unexpected exitCode=" + exitCode + "\nLog: " + stdout); + } + } finally { + Files.deleteIfExists(cwd); + } + } catch (IOException e) { + throw new ExecutorException("Failed to determine maven version", e); + } + } + + protected void validate(ExecutorRequest executorRequest) throws ExecutorException {} + + protected int doExecute(ExecutorRequest executorRequest, Consumer<Process> processConsumer) + throws ExecutorException { + ArrayList<String> cmdAndArguments = new ArrayList<>(); + cmdAndArguments.add(executorRequest + .installationDirectory() + .resolve("bin") + .resolve(IS_WINDOWS ? executorRequest.command() + ".cmd" : executorRequest.command()) + .toString()); + + cmdAndArguments.addAll(executorRequest.arguments()); + + try { + ProcessBuilder pb = new ProcessBuilder() + .directory(executorRequest.cwd().toFile()) + .command(cmdAndArguments); + + if (executorRequest.jvmArguments().isPresent()) { + pb.environment() + .put( + "MAVEN_OPTS", + String.join(" ", executorRequest.jvmArguments().get())); + } + + Process process = pb.start(); + if (processConsumer != null) { + processConsumer.accept(process); + } + return process.waitFor(); + } catch (IOException e) { + throw new ExecutorException("IO problem while executing command: " + cmdAndArguments, e); + } catch (InterruptedException e) { + throw new ExecutorException("Interrupted while executing command: " + cmdAndArguments, e); + } + } +} diff --git a/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/MavenExecutorTestSupport.java b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/MavenExecutorTestSupport.java new file mode 100644 index 0000000000..bf1086ae88 --- /dev/null +++ b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/MavenExecutorTestSupport.java @@ -0,0 +1,187 @@ +/* + * 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.executor; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.List; + +import org.apache.maven.api.cli.Executor; +import org.apache.maven.api.cli.ExecutorRequest; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public abstract class MavenExecutorTestSupport { + @Test + void defaultFs(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception { + layDownFiles(tempDir); + String logfile = "m4.log"; + execute( + tempDir.resolve(logfile), + List.of(mvn4ExecutorRequestBuilder() + .cwd(tempDir) + .argument("verify") + .argument("-l") + .argument(logfile) + .build())); + } + + @Test + void version() throws Exception { + assertEquals( + System.getProperty("maven4version"), + mavenVersion(mvn4ExecutorRequestBuilder().build())); + } + + @Disabled("JUnit on Windows fails to clean up as mvn3 seems does not close log file properly") + @Test + void defaultFs3x(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception { + layDownFiles(tempDir); + String logfile = "m3.log"; + execute( + tempDir.resolve(logfile), + List.of(mvn3ExecutorRequestBuilder() + .cwd(tempDir) + .argument("verify") + .argument("-l") + .argument(logfile) + .build())); + } + + @Test + void version3x() throws Exception { + assertEquals( + System.getProperty("maven3version"), + mavenVersion(mvn3ExecutorRequestBuilder().build())); + } + + public static final String POM_STRING = + """ + <?xml version="1.0" encoding="UTF-8"?> + <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.samples</groupId> + <artifactId>sample</artifactId> + <version>1.0.0</version> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.junit</groupId> + <artifactId>junit-bom</artifactId> + <version>5.11.1</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + </project> + """; + + public static final String APP_JAVA_STRING = + """ + package org.apache.maven.samples.sample; + + public class App { + public static void main(String... args) { + System.out.println("Hello World!"); + } + } + """; + + protected void execute(Path logFile, Collection<ExecutorRequest> requests) throws Exception { + try (Executor invoker = createExecutor()) { + for (ExecutorRequest request : requests) { + int exitCode = invoker.execute(request); + if (exitCode != 0) { + throw new FailedExecution(request, exitCode, Files.readString(logFile)); + } + } + } + } + + protected String mavenVersion(ExecutorRequest request) throws Exception { + try (Executor invoker = createExecutor()) { + return invoker.mavenVersion(request); + } + } + + protected ExecutorRequest.Builder mvn3ExecutorRequestBuilder() { + return ExecutorRequest.mavenBuilder(Paths.get(System.getProperty("maven3home"))); + } + + protected ExecutorRequest.Builder mvn4ExecutorRequestBuilder() { + return ExecutorRequest.mavenBuilder(Paths.get(System.getProperty("maven4home"))); + } + + protected void layDownFiles(Path cwd) throws IOException { + Files.createDirectory(cwd.resolve(".mvn")); + Path pom = cwd.resolve("pom.xml").toAbsolutePath(); + Files.writeString(pom, POM_STRING); + Path appJava = cwd.resolve("src/main/java/org/apache/maven/samples/sample/App.java"); + Files.createDirectories(appJava.getParent()); + Files.writeString(appJava, APP_JAVA_STRING); + } + + protected static class FailedExecution extends Exception { + private final ExecutorRequest request; + private final int exitCode; + private final String log; + + public FailedExecution(ExecutorRequest request, int exitCode, String log) { + super(request.toString() + " => " + exitCode + "\n" + log); + this.request = request; + this.exitCode = exitCode; + this.log = log; + } + + public ExecutorRequest getRequest() { + return request; + } + + public int getExitCode() { + return exitCode; + } + + public String getLog() { + return log; + } + } + + protected abstract Executor createExecutor(); +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutorTest.java similarity index 58% copy from impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java copy to impl/maven-executor/src/test/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutorTest.java index 7ffbcab9e9..1fe0caef83 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java +++ b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/embedded/EmbeddedMavenExecutorTest.java @@ -16,22 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cling.invoker.mvn; +package org.apache.maven.cling.executor.embedded; -import org.apache.maven.Maven; -import org.apache.maven.api.cli.InvokerRequest; -import org.apache.maven.cling.invoker.LookupContext; -import org.apache.maven.eventspy.internal.EventSpyDispatcher; -import org.apache.maven.logging.BuildEventListener; +import org.apache.maven.api.cli.Executor; +import org.apache.maven.cling.executor.MavenExecutorTestSupport; -@SuppressWarnings("VisibilityModifier") -public class MavenContext extends LookupContext { - public MavenContext(InvokerRequest invokerRequest) { - super(invokerRequest); - } - - public BuildEventListener buildEventListener; - public EventSpyDispatcher eventSpyDispatcher; +/** + * Embedded executor UT + */ +public class EmbeddedMavenExecutorTest extends MavenExecutorTestSupport { - public Maven maven; + @Override + protected Executor createExecutor() { + return new EmbeddedMavenExecutor(); + } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutorTest.java similarity index 58% copy from impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java copy to impl/maven-executor/src/test/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutorTest.java index 7ffbcab9e9..ee1c007fb9 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenContext.java +++ b/impl/maven-executor/src/test/java/org/apache/maven/cling/executor/forked/ForkedMavenExecutorTest.java @@ -16,22 +16,18 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cling.invoker.mvn; +package org.apache.maven.cling.executor.forked; -import org.apache.maven.Maven; -import org.apache.maven.api.cli.InvokerRequest; -import org.apache.maven.cling.invoker.LookupContext; -import org.apache.maven.eventspy.internal.EventSpyDispatcher; -import org.apache.maven.logging.BuildEventListener; +import org.apache.maven.api.cli.Executor; +import org.apache.maven.cling.executor.MavenExecutorTestSupport; -@SuppressWarnings("VisibilityModifier") -public class MavenContext extends LookupContext { - public MavenContext(InvokerRequest invokerRequest) { - super(invokerRequest); - } - - public BuildEventListener buildEventListener; - public EventSpyDispatcher eventSpyDispatcher; +/** + * Forked executor UT + */ +public class ForkedMavenExecutorTest extends MavenExecutorTestSupport { - public Maven maven; + @Override + protected Executor createExecutor() { + return new ForkedMavenExecutor(); + } } diff --git a/impl/pom.xml b/impl/pom.xml index b2740230de..f7b38fe395 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -38,5 +38,6 @@ under the License. <module>maven-logging</module> <module>maven-core</module> <module>maven-cli</module> + <module>maven-executor</module> </modules> </project> diff --git a/pom.xml b/pom.xml index f933906b63..7aff69544f 100644 --- a/pom.xml +++ b/pom.xml @@ -675,6 +675,7 @@ under the License. <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> + <version>3.5.2</version> <configuration> <argLine>-Xmx256m</argLine> </configuration> diff --git a/src/graph/ReactorGraph.java b/src/graph/ReactorGraph.java index c5010b404f..4034dbd1d5 100755 --- a/src/graph/ReactorGraph.java +++ b/src/graph/ReactorGraph.java @@ -42,7 +42,7 @@ public class ReactorGraph { CLUSTER_PATTERNS.put("JLine", Pattern.compile("^org\\.jline:.*")); CLUSTER_PATTERNS.put("Maven API", Pattern.compile("^org\\.apache\\.maven:maven-api-(?!impl).*")); CLUSTER_PATTERNS.put("Maven Resolver", Pattern.compile("^org\\.apache\\.maven\\.resolver:.*")); - CLUSTER_PATTERNS.put("Maven Implementation", Pattern.compile("^org\\.apache\\.maven:maven-(impl|di|core|cli|xml|jline|logging):.*")); + CLUSTER_PATTERNS.put("Maven Implementation", Pattern.compile("^org\\.apache\\.maven:maven-(impl|di|core|cli|xml|jline|logging|executor):.*")); CLUSTER_PATTERNS.put("Maven Compatibility", Pattern.compile("^org\\.apache\\.maven:maven-(artifact|builder-support|compat|embedder|model|model-builder|plugin-api|repository-metadata|resolver-provider|settings|settings-builder|toolchain-builder|toolchain-model):.*")); CLUSTER_PATTERNS.put("Sisu", Pattern.compile("(^org\\.eclipse\\.sisu:.*)|(.*:guice:.*)|(.*:javax.inject:.*)|(.*:javax.annotation-api:.*)")); CLUSTER_PATTERNS.put("Plexus", Pattern.compile("^org\\.codehaus\\.plexus:.*"));