mirror of https://github.com/apache/maven.git
[MNG-8403] Maven ITs use maven-executor (#1940)
The goal of this PR is manifold, but major one is to be able to use in ITs new options introduced in Maven4. Currently the "embedded" mode supports only Maven3 options, as Maven4 got new CLI entry point (CLIng), while verifier uses old MavenCli entry point, that is also deprecated. Finally, a full cleanup of (black) magic happened as well, keep ITs simple and clean. Changes: * dropped from ITs classpath maven-shared-util * dropped from ITs classpath maven-verifier, copied last master Verifier to maven-it-helper and modified * enhancements to new maven-executor to make it fully replace maven-verifier * ITs are now using new infra and are using new CLIng "entry point" as well (so far ITs used deprecated maven-embedder/MavenCLI class). --- https://issues.apache.org/jira/browse/MNG-8403
This commit is contained in:
parent
73e30c5d6f
commit
e74bde05c9
|
@ -52,7 +52,7 @@ jobs:
|
|||
|
||||
- name: Set up Maven
|
||||
shell: bash
|
||||
run: mvn --errors --batch-mode --show-version org.apache.maven.plugins:maven-wrapper-plugin:3.3.2:wrapper "-Dmaven=4.0.0-beta-4"
|
||||
run: mvn --errors --batch-mode --show-version org.apache.maven.plugins:maven-wrapper-plugin:3.3.2:wrapper "-Dmaven=4.0.0-rc-1"
|
||||
|
||||
- name: Build Maven distributions
|
||||
shell: bash
|
||||
|
|
|
@ -35,7 +35,7 @@ under the License.
|
|||
|
||||
<properties>
|
||||
<maven3version>3.9.9</maven3version>
|
||||
<maven4version>4.0.0-beta-5</maven4version>
|
||||
<maven4version>4.0.0-rc-1</maven4version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -50,6 +50,11 @@ under the License.
|
|||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -34,7 +34,7 @@ public interface Executor extends AutoCloseable {
|
|||
boolean IS_WINDOWS = System.getProperty("os.name", "unknown").startsWith("Windows");
|
||||
|
||||
/**
|
||||
* Maven version string returned when the actual version of Maven cannot be determinet.
|
||||
* Maven version string returned when the actual version of Maven cannot be determined.
|
||||
*/
|
||||
String UNKNOWN_VERSION = "unknown";
|
||||
|
||||
|
@ -50,7 +50,7 @@ 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
|
||||
* Returns the Maven version that provided {@link ExecutorRequest} point at (would use). Please note, 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()}.
|
||||
*
|
||||
|
|
|
@ -19,10 +19,13 @@
|
|||
package org.apache.maven.api.cli;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.maven.api.annotations.Experimental;
|
||||
|
@ -35,7 +38,7 @@ 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
|
||||
* Maven command with arguments. The arguments are not parsed, they are just passed over
|
||||
* to executed tool.
|
||||
*
|
||||
* @since 4.0.0
|
||||
|
@ -43,6 +46,11 @@ import static java.util.Objects.requireNonNull;
|
|||
@Immutable
|
||||
@Experimental
|
||||
public interface ExecutorRequest {
|
||||
/**
|
||||
* The Maven command.
|
||||
*/
|
||||
String MVN = "mvn";
|
||||
|
||||
/**
|
||||
* The command to execute, ie "mvn".
|
||||
*/
|
||||
|
@ -82,10 +90,27 @@ public interface ExecutorRequest {
|
|||
@Nonnull
|
||||
Path userHomeDirectory();
|
||||
|
||||
/**
|
||||
* Returns the map of Java System Properties to set before executing process.
|
||||
*
|
||||
* @return an Optional containing the map of Java System Properties, or empty if not specified
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<Map<String, String>> jvmSystemProperties();
|
||||
|
||||
/**
|
||||
* Returns the map of environment variables to set before executing process.
|
||||
* This property is used ONLY by executors that spawn a new JVM.
|
||||
*
|
||||
* @return an Optional containing the map of environment variables, or empty if not specified
|
||||
*/
|
||||
@Nonnull
|
||||
Optional<Map<String, String>> environmentVariables();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* This property is used ONLY by executors that spawn a new JVM.
|
||||
*
|
||||
* @return an Optional containing the list of extra JVM arguments, or empty if not specified
|
||||
*/
|
||||
|
@ -93,7 +118,25 @@ public interface ExecutorRequest {
|
|||
Optional<List<String>> jvmArguments();
|
||||
|
||||
/**
|
||||
* Returns {@link Builder} for this instance.
|
||||
* Optional consumer for STD out of the Maven. If given, this consumer will get all output from the std out of
|
||||
* Maven. Note: whether consumer gets to consume anything depends on invocation arguments passed in
|
||||
* {@link #arguments()}, as if log file is set, not much will go to stdout.
|
||||
*
|
||||
* @return an Optional containing the stdout consumer, or empty if not specified.
|
||||
*/
|
||||
Optional<OutputStream> stdoutConsumer();
|
||||
|
||||
/**
|
||||
* Optional consumer for STD err of the Maven. If given, this consumer will get all output from the std err of
|
||||
* Maven. Note: whether consumer gets to consume anything depends on invocation arguments passed in
|
||||
* {@link #arguments()}, as if log file is set, not much will go to stderr.
|
||||
*
|
||||
* @return an Optional containing the stderr consumer, or empty if not specified.
|
||||
*/
|
||||
Optional<OutputStream> stderrConsumer();
|
||||
|
||||
/**
|
||||
* Returns {@link Builder} created from this instance.
|
||||
*/
|
||||
@Nonnull
|
||||
default Builder toBuilder() {
|
||||
|
@ -103,28 +146,29 @@ public interface ExecutorRequest {
|
|||
cwd(),
|
||||
installationDirectory(),
|
||||
userHomeDirectory(),
|
||||
jvmArguments().orElse(null));
|
||||
jvmSystemProperties().orElse(null),
|
||||
environmentVariables().orElse(null),
|
||||
jvmArguments().orElse(null),
|
||||
stdoutConsumer().orElse(null),
|
||||
stderrConsumer().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.
|
||||
* Returns new builder pre-set to run Maven. The discovery of maven home is attempted, user cwd and home are
|
||||
* also discovered by standard means.
|
||||
*/
|
||||
@Nonnull
|
||||
static Builder mavenBuilder(@Nullable Path installationDirectory) {
|
||||
return new Builder(
|
||||
"mvn",
|
||||
MVN,
|
||||
null,
|
||||
getCanonicalPath(Paths.get(System.getProperty("user.dir"))),
|
||||
installationDirectory != null ? getCanonicalPath(installationDirectory) : discoverMavenHome(),
|
||||
getCanonicalPath(Paths.get(System.getProperty("user.home"))),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
|
@ -134,23 +178,36 @@ public interface ExecutorRequest {
|
|||
private Path cwd;
|
||||
private Path installationDirectory;
|
||||
private Path userHomeDirectory;
|
||||
private Map<String, String> jvmSystemProperties;
|
||||
private Map<String, String> environmentVariables;
|
||||
private List<String> jvmArguments;
|
||||
private OutputStream stdoutConsumer;
|
||||
private OutputStream stderrConsumer;
|
||||
|
||||
private Builder() {}
|
||||
|
||||
@SuppressWarnings("ParameterNumber")
|
||||
private Builder(
|
||||
String command,
|
||||
List<String> arguments,
|
||||
Path cwd,
|
||||
Path installationDirectory,
|
||||
Path userHomeDirectory,
|
||||
List<String> jvmArguments) {
|
||||
Map<String, String> jvmSystemProperties,
|
||||
Map<String, String> environmentVariables,
|
||||
List<String> jvmArguments,
|
||||
OutputStream stdoutConsumer,
|
||||
OutputStream stderrConsumer) {
|
||||
this.command = command;
|
||||
this.arguments = arguments;
|
||||
this.cwd = cwd;
|
||||
this.installationDirectory = installationDirectory;
|
||||
this.userHomeDirectory = userHomeDirectory;
|
||||
this.jvmSystemProperties = jvmSystemProperties;
|
||||
this.environmentVariables = environmentVariables;
|
||||
this.jvmArguments = jvmArguments;
|
||||
this.stdoutConsumer = stdoutConsumer;
|
||||
this.stderrConsumer = stderrConsumer;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -176,19 +233,54 @@ public interface ExecutorRequest {
|
|||
|
||||
@Nonnull
|
||||
public Builder cwd(Path cwd) {
|
||||
this.cwd = requireNonNull(cwd, "cwd");
|
||||
this.cwd = getCanonicalPath(requireNonNull(cwd, "cwd"));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Builder installationDirectory(Path installationDirectory) {
|
||||
this.installationDirectory = requireNonNull(installationDirectory, "installationDirectory");
|
||||
this.installationDirectory =
|
||||
getCanonicalPath(requireNonNull(installationDirectory, "installationDirectory"));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Builder userHomeDirectory(Path userHomeDirectory) {
|
||||
this.userHomeDirectory = requireNonNull(userHomeDirectory, "userHomeDirectory");
|
||||
this.userHomeDirectory = getCanonicalPath(requireNonNull(userHomeDirectory, "userHomeDirectory"));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Builder jvmSystemProperties(Map<String, String> jvmSystemProperties) {
|
||||
this.jvmSystemProperties = jvmSystemProperties;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Builder jvmSystemProperty(String key, String value) {
|
||||
requireNonNull(key, "env key");
|
||||
requireNonNull(value, "env value");
|
||||
if (jvmSystemProperties == null) {
|
||||
this.jvmSystemProperties = new HashMap<>();
|
||||
}
|
||||
this.jvmSystemProperties.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Builder environmentVariables(Map<String, String> environmentVariables) {
|
||||
this.environmentVariables = environmentVariables;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Builder environmentVariable(String key, String value) {
|
||||
requireNonNull(key, "env key");
|
||||
requireNonNull(value, "env value");
|
||||
if (environmentVariables == null) {
|
||||
this.environmentVariables = new HashMap<>();
|
||||
}
|
||||
this.environmentVariables.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -207,9 +299,31 @@ public interface ExecutorRequest {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Builder stdoutConsumer(OutputStream stdoutConsumer) {
|
||||
this.stdoutConsumer = stdoutConsumer;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Builder stderrConsumer(OutputStream stderrConsumer) {
|
||||
this.stderrConsumer = stderrConsumer;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public ExecutorRequest build() {
|
||||
return new Impl(command, arguments, cwd, installationDirectory, userHomeDirectory, jvmArguments);
|
||||
return new Impl(
|
||||
command,
|
||||
arguments,
|
||||
cwd,
|
||||
installationDirectory,
|
||||
userHomeDirectory,
|
||||
jvmSystemProperties,
|
||||
environmentVariables,
|
||||
jvmArguments,
|
||||
stdoutConsumer,
|
||||
stderrConsumer);
|
||||
}
|
||||
|
||||
private static class Impl implements ExecutorRequest {
|
||||
|
@ -218,21 +332,34 @@ public interface ExecutorRequest {
|
|||
private final Path cwd;
|
||||
private final Path installationDirectory;
|
||||
private final Path userHomeDirectory;
|
||||
private final Map<String, String> jvmSystemProperties;
|
||||
private final Map<String, String> environmentVariables;
|
||||
private final List<String> jvmArguments;
|
||||
private final OutputStream stdoutConsumer;
|
||||
private final OutputStream stderrConsumer;
|
||||
|
||||
@SuppressWarnings("ParameterNumber")
|
||||
private Impl(
|
||||
String command,
|
||||
List<String> arguments,
|
||||
Path cwd,
|
||||
Path installationDirectory,
|
||||
Path userHomeDirectory,
|
||||
List<String> jvmArguments) {
|
||||
Map<String, String> jvmSystemProperties,
|
||||
Map<String, String> environmentVariables,
|
||||
List<String> jvmArguments,
|
||||
OutputStream stdoutConsumer,
|
||||
OutputStream stderrConsumer) {
|
||||
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.cwd = getCanonicalPath(requireNonNull(cwd));
|
||||
this.installationDirectory = getCanonicalPath(requireNonNull(installationDirectory));
|
||||
this.userHomeDirectory = getCanonicalPath(requireNonNull(userHomeDirectory));
|
||||
this.jvmSystemProperties = jvmSystemProperties != null ? Map.copyOf(jvmSystemProperties) : null;
|
||||
this.environmentVariables = environmentVariables != null ? Map.copyOf(environmentVariables) : null;
|
||||
this.jvmArguments = jvmArguments != null ? List.copyOf(jvmArguments) : null;
|
||||
this.stdoutConsumer = stdoutConsumer;
|
||||
this.stderrConsumer = stderrConsumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -260,20 +387,44 @@ public interface ExecutorRequest {
|
|||
return userHomeDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Map<String, String>> jvmSystemProperties() {
|
||||
return Optional.ofNullable(jvmSystemProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Map<String, String>> environmentVariables() {
|
||||
return Optional.ofNullable(environmentVariables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<List<String>> jvmArguments() {
|
||||
return Optional.ofNullable(jvmArguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<OutputStream> stdoutConsumer() {
|
||||
return Optional.ofNullable(stdoutConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<OutputStream> stderrConsumer() {
|
||||
return Optional.ofNullable(stderrConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExecutionRequest{" + "command='"
|
||||
return "Impl{" + "command='"
|
||||
+ command + '\'' + ", arguments="
|
||||
+ arguments + ", cwd="
|
||||
+ cwd + ", installationDirectory="
|
||||
+ installationDirectory + ", userHomeDirectory="
|
||||
+ userHomeDirectory + ", jvmArguments="
|
||||
+ jvmArguments + '}';
|
||||
+ userHomeDirectory + ", jvmSystemProperties="
|
||||
+ jvmSystemProperties + ", environmentVariables="
|
||||
+ environmentVariables + ", jvmArguments="
|
||||
+ jvmArguments + ", stdoutConsumer="
|
||||
+ stdoutConsumer + ", stderrConsumer="
|
||||
+ stderrConsumer + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 org.apache.maven.api.annotations.Nonnull;
|
||||
import org.apache.maven.api.cli.Executor;
|
||||
import org.apache.maven.api.cli.ExecutorException;
|
||||
import org.apache.maven.api.cli.ExecutorRequest;
|
||||
|
||||
/**
|
||||
* Helper class for routing Maven execution based on preferences and/or issued execution requests.
|
||||
*/
|
||||
public interface ExecutorHelper extends ExecutorTool {
|
||||
/**
|
||||
* The modes of execution.
|
||||
*/
|
||||
enum Mode {
|
||||
/**
|
||||
* Automatically decide. For example, presence of {@link ExecutorRequest#environmentVariables()} or
|
||||
* {@link ExecutorRequest#jvmArguments()} will result in choosing {@link #FORKED} executor. Otherwise,
|
||||
* {@link #EMBEDDED} executor is preferred.
|
||||
*/
|
||||
AUTO,
|
||||
/**
|
||||
* Forces embedded execution. May fail if {@link ExecutorRequest} contains input unsupported by executor.
|
||||
*/
|
||||
EMBEDDED,
|
||||
/**
|
||||
* Forces forked execution. Always carried out, most isolated and "most correct", but is slow as it uses child process.
|
||||
*/
|
||||
FORKED
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preferred mode of this helper.
|
||||
*/
|
||||
@Nonnull
|
||||
Mode getDefaultMode();
|
||||
|
||||
/**
|
||||
* Creates pre-populated builder for {@link ExecutorRequest}. Users of helper must use this method to create
|
||||
* properly initialized request builder.
|
||||
*/
|
||||
@Nonnull
|
||||
ExecutorRequest.Builder executorRequest();
|
||||
|
||||
/**
|
||||
* Executes the request with preferred mode executor.
|
||||
*/
|
||||
default int execute(ExecutorRequest executorRequest) throws ExecutorException {
|
||||
return execute(getDefaultMode(), executorRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the request with passed in mode executor.
|
||||
*/
|
||||
int execute(Mode mode, ExecutorRequest executorRequest) throws ExecutorException;
|
||||
|
||||
/**
|
||||
* High level operation, returns the version of the Maven covered by this helper. This method call caches
|
||||
* underlying operation, and is safe to invoke as many times needed.
|
||||
*
|
||||
* @see Executor#mavenVersion(ExecutorRequest)
|
||||
*/
|
||||
@Nonnull
|
||||
String mavenVersion();
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.util.Map;
|
||||
|
||||
import org.apache.maven.api.annotations.Nullable;
|
||||
import org.apache.maven.api.cli.ExecutorException;
|
||||
import org.apache.maven.api.cli.ExecutorRequest;
|
||||
|
||||
/**
|
||||
* A tool implementing some common Maven operations.
|
||||
*/
|
||||
public interface ExecutorTool {
|
||||
/**
|
||||
* Performs a diagnostic dump of the environment.
|
||||
*
|
||||
* @param request never {@code null}
|
||||
*/
|
||||
Map<String, String> dump(ExecutorRequest.Builder request) throws ExecutorException;
|
||||
|
||||
/**
|
||||
* Returns the location of local repository, as detected by Maven. The {@code userSettings} param may contain
|
||||
* some override (equivalent of {@code -s settings.xml} on CLI).
|
||||
*
|
||||
* @param request never {@code null}
|
||||
*/
|
||||
String localRepository(ExecutorRequest.Builder request) throws ExecutorException;
|
||||
|
||||
/**
|
||||
* Returns relative (to {@link #localRepository(ExecutorRequest.Builder)}) path of given artifact in local repository.
|
||||
*
|
||||
* @param request never {@code null}
|
||||
* @param gav the usual resolver artifact GAV string, never {@code null}
|
||||
* @param repositoryId the remote repository ID in case "remote artifact" is asked for
|
||||
*/
|
||||
String artifactPath(ExecutorRequest.Builder request, String gav, @Nullable String repositoryId)
|
||||
throws ExecutorException;
|
||||
|
||||
/**
|
||||
* Returns relative (to {@link #localRepository(ExecutorRequest.Builder)}) path of given metadata in local repository.
|
||||
* The metadata coordinates in form of {@code [G]:[A]:[V]:[type]}. Absence of {@code A} implies absence of {@code V}
|
||||
* as well (in other words, it can be {@code G}, {@code G:A} or {@code G:A:V}). The absence of {@code type} implies
|
||||
* it is "maven-metadata.xml". The simplest spec string is {@code :::}.
|
||||
* <p>
|
||||
* Examples:
|
||||
* <ul>
|
||||
* <li>{@code :::} is root metadata named "maven-metadata.xml"</li>
|
||||
* <li>{@code :::my-metadata.xml} is root metadata named "my-metadata.xml"</li>
|
||||
* <li>{@code G:::} equals to {@code G:::maven-metadata.xml}</li>
|
||||
* <li>{@code G:A::} equals to {@code G:A::maven-metadata.xml}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param request never {@code null}
|
||||
* @param gav the resolver metadata GAV string
|
||||
* @param repositoryId the remote repository ID in case "remote metadata" is asked for
|
||||
*/
|
||||
String metadataPath(ExecutorRequest.Builder request, String gav, @Nullable String repositoryId)
|
||||
throws ExecutorException;
|
||||
}
|
|
@ -23,6 +23,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
@ -31,9 +32,13 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -45,40 +50,51 @@ import static java.util.Objects.requireNonNull;
|
|||
|
||||
/**
|
||||
* Embedded executor implementation, that invokes Maven from installation directory within this same JVM but in isolated
|
||||
* classloader. This class supports Maven 4.x and Maven 3.x as well.
|
||||
* The class world with Maven is kept in memory as long as instance of this class is not closed. Subsequent execution
|
||||
* requests over same installation home are cached.
|
||||
* classloader. This class supports Maven 4.x and Maven 3.x as well. The ClassWorld of Maven is kept in memory as
|
||||
* long as instance of this class is not closed. Subsequent execution requests over same installation home are cached.
|
||||
*/
|
||||
public class EmbeddedMavenExecutor implements Executor {
|
||||
protected static final class Context {
|
||||
private final Properties properties;
|
||||
private final URLClassLoader bootClassLoader;
|
||||
private final String version;
|
||||
private final Object classWorld;
|
||||
private final Set<String> originalClassRealmIds;
|
||||
private final ClassLoader tccl;
|
||||
private final Function<ExecutorRequest, Integer> exec;
|
||||
|
||||
public Context(
|
||||
Properties properties,
|
||||
URLClassLoader bootClassLoader,
|
||||
String version,
|
||||
Object classWorld,
|
||||
Set<String> originalClassRealmIds,
|
||||
ClassLoader tccl,
|
||||
Function<ExecutorRequest, Integer> exec) {
|
||||
this.properties = properties;
|
||||
this.bootClassLoader = bootClassLoader;
|
||||
this.version = version;
|
||||
this.classWorld = classWorld;
|
||||
this.originalClassRealmIds = originalClassRealmIds;
|
||||
this.tccl = tccl;
|
||||
this.exec = exec;
|
||||
}
|
||||
}
|
||||
|
||||
private final Properties originalProperties;
|
||||
private final ClassLoader originalClassLoader;
|
||||
private final ConcurrentHashMap<Path, Context> contexts;
|
||||
protected final boolean cacheContexts;
|
||||
protected final AtomicBoolean closed;
|
||||
protected final PrintStream originalStdout;
|
||||
protected final PrintStream originalStderr;
|
||||
protected final Properties originalProperties;
|
||||
protected final ClassLoader originalClassLoader;
|
||||
protected final ConcurrentHashMap<Path, Context> contexts;
|
||||
|
||||
public EmbeddedMavenExecutor() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public EmbeddedMavenExecutor(boolean cacheContexts) {
|
||||
this.cacheContexts = cacheContexts;
|
||||
this.closed = new AtomicBoolean(false);
|
||||
this.originalStdout = System.out;
|
||||
this.originalStderr = System.err;
|
||||
this.originalClassLoader = Thread.currentThread().getContextClassLoader();
|
||||
this.contexts = new ConcurrentHashMap<>();
|
||||
this.originalProperties = System.getProperties();
|
||||
|
@ -87,18 +103,51 @@ public class EmbeddedMavenExecutor implements Executor {
|
|||
@Override
|
||||
public int execute(ExecutorRequest executorRequest) throws ExecutorException {
|
||||
requireNonNull(executorRequest);
|
||||
if (closed.get()) {
|
||||
throw new ExecutorException("Executor is closed");
|
||||
}
|
||||
validate(executorRequest);
|
||||
Context context = mayCreate(executorRequest);
|
||||
|
||||
System.setProperties(context.properties);
|
||||
Thread.currentThread().setContextClassLoader(context.tccl);
|
||||
try {
|
||||
if (executorRequest.stdoutConsumer().isPresent()) {
|
||||
System.setOut(new PrintStream(executorRequest.stdoutConsumer().get(), true));
|
||||
}
|
||||
if (executorRequest.stderrConsumer().isPresent()) {
|
||||
System.setErr(new PrintStream(executorRequest.stderrConsumer().get(), true));
|
||||
}
|
||||
return context.exec.apply(executorRequest);
|
||||
} catch (Exception e) {
|
||||
throw new ExecutorException("Failed to execute", e);
|
||||
} finally {
|
||||
try {
|
||||
disposeRuntimeCreatedRealms(context);
|
||||
} finally {
|
||||
System.setOut(originalStdout);
|
||||
System.setErr(originalStderr);
|
||||
Thread.currentThread().setContextClassLoader(originalClassLoader);
|
||||
System.setProperties(originalProperties);
|
||||
if (!cacheContexts) {
|
||||
doClose(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void disposeRuntimeCreatedRealms(Context context) {
|
||||
try {
|
||||
Method getRealms = context.classWorld.getClass().getMethod("getRealms");
|
||||
Method disposeRealm = context.classWorld.getClass().getMethod("disposeRealm", String.class);
|
||||
List<Object> realms = (List<Object>) getRealms.invoke(context.classWorld);
|
||||
for (Object realm : realms) {
|
||||
String realmId = (String) realm.getClass().getMethod("getId").invoke(realm);
|
||||
if (!context.originalClassRealmIds.contains(realmId)) {
|
||||
disposeRealm.invoke(context.classWorld, realmId);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ExecutorException("Failed to dispose runtime created realms", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,41 +155,42 @@ public class EmbeddedMavenExecutor implements Executor {
|
|||
public String mavenVersion(ExecutorRequest executorRequest) throws ExecutorException {
|
||||
requireNonNull(executorRequest);
|
||||
validate(executorRequest);
|
||||
if (closed.get()) {
|
||||
throw new ExecutorException("Executor is closed");
|
||||
}
|
||||
return mayCreate(executorRequest).version;
|
||||
}
|
||||
|
||||
protected Context mayCreate(ExecutorRequest executorRequest) {
|
||||
Path installation = executorRequest.installationDirectory();
|
||||
if (!Files.isDirectory(installation)) {
|
||||
Path mavenHome = ExecutorRequest.getCanonicalPath(executorRequest.installationDirectory());
|
||||
if (cacheContexts) {
|
||||
return contexts.computeIfAbsent(mavenHome, k -> doCreate(mavenHome, executorRequest));
|
||||
} else {
|
||||
return doCreate(mavenHome, executorRequest);
|
||||
}
|
||||
}
|
||||
|
||||
protected Context doCreate(Path mavenHome, ExecutorRequest executorRequest) {
|
||||
if (!Files.isDirectory(mavenHome)) {
|
||||
throw new IllegalArgumentException("Installation directory must point to existing directory");
|
||||
}
|
||||
return contexts.computeIfAbsent(installation, k -> {
|
||||
Path mavenHome = installation.toAbsolutePath().normalize();
|
||||
if (!Objects.equals(executorRequest.command(), ExecutorRequest.MVN)) {
|
||||
throw new IllegalArgumentException(
|
||||
getClass().getSimpleName() + " does not support command " + executorRequest.command());
|
||||
}
|
||||
if (executorRequest.environmentVariables().isPresent()) {
|
||||
throw new IllegalArgumentException(getClass().getSimpleName() + " does not support environment variables");
|
||||
}
|
||||
if (executorRequest.jvmArguments().isPresent()) {
|
||||
throw new IllegalArgumentException(getClass().getSimpleName() + " does not support jvmArguments");
|
||||
}
|
||||
Path boot = mavenHome.resolve("boot");
|
||||
Path m2conf = mavenHome.resolve("bin/m2.conf");
|
||||
if (!Files.isDirectory(boot) || !Files.isRegularFile(m2conf)) {
|
||||
throw new IllegalArgumentException("Installation directory does not point to Maven installation");
|
||||
}
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.putAll(System.getProperties());
|
||||
properties.put(
|
||||
"user.dir",
|
||||
executorRequest.cwd().toAbsolutePath().normalize().toString());
|
||||
properties.put(
|
||||
"maven.multiModuleProjectDirectory",
|
||||
executorRequest.cwd().toAbsolutePath().normalize().toString());
|
||||
properties.put(
|
||||
"user.home",
|
||||
executorRequest
|
||||
.userHomeDirectory()
|
||||
.toAbsolutePath()
|
||||
.normalize()
|
||||
.toString());
|
||||
properties.put("maven.home", mavenHome.toString());
|
||||
properties.put("maven.mainClass", "org.apache.maven.cling.MavenCling");
|
||||
properties.put(
|
||||
"library.jline.path", mavenHome.resolve("lib/jline-native").toString());
|
||||
Properties properties = prepareProperties(executorRequest);
|
||||
|
||||
System.setProperties(properties);
|
||||
URLClassLoader bootClassLoader = createMavenBootClassLoader(boot, Collections.emptyList());
|
||||
|
@ -153,6 +203,16 @@ public class EmbeddedMavenExecutor implements Executor {
|
|||
configure.invoke(launcher, inputStream);
|
||||
}
|
||||
Object classWorld = launcherClass.getMethod("getWorld").invoke(launcher);
|
||||
Set<String> originalClassRealmIds = new HashSet<>();
|
||||
|
||||
// collect pre-created (in m2.conf) class realms as "original ones"; the rest are created at runtime
|
||||
Method getRealms = classWorld.getClass().getMethod("getRealms");
|
||||
List<Object> realms = (List<Object>) getRealms.invoke(classWorld);
|
||||
for (Object realm : realms) {
|
||||
Method realmGetId = realm.getClass().getMethod("getId");
|
||||
originalClassRealmIds.add((String) realmGetId.invoke(realm));
|
||||
}
|
||||
|
||||
Class<?> cliClass =
|
||||
(Class<?>) launcherClass.getMethod("getMainClass").invoke(launcher);
|
||||
String version = getMavenVersion(cliClass);
|
||||
|
@ -165,6 +225,8 @@ public class EmbeddedMavenExecutor implements Executor {
|
|||
Class<?>[] parameterTypes = {String[].class, String.class, PrintStream.class, PrintStream.class};
|
||||
Method doMain = cliClass.getMethod("doMain", parameterTypes);
|
||||
exec = r -> {
|
||||
System.setProperties(null);
|
||||
System.setProperties(prepareProperties(r));
|
||||
try {
|
||||
return (int) doMain.invoke(mavenCli, new Object[] {
|
||||
r.arguments().toArray(new String[0]), r.cwd().toString(), null, null
|
||||
|
@ -176,27 +238,68 @@ public class EmbeddedMavenExecutor implements Executor {
|
|||
} else {
|
||||
// assume 4.x
|
||||
Method mainMethod = cliClass.getMethod("main", String[].class, classWorld.getClass());
|
||||
Class<?> ansiConsole = cliClass.getClassLoader().loadClass("org.jline.jansi.AnsiConsole");
|
||||
Field ansiConsoleInstalled = ansiConsole.getDeclaredField("installed");
|
||||
ansiConsoleInstalled.setAccessible(true);
|
||||
exec = r -> {
|
||||
System.setProperties(null);
|
||||
System.setProperties(prepareProperties(r));
|
||||
try {
|
||||
try {
|
||||
if (r.stdoutConsumer().isPresent()
|
||||
|| r.stderrConsumer().isPresent()) {
|
||||
ansiConsoleInstalled.set(null, 1);
|
||||
}
|
||||
return (int) mainMethod.invoke(null, r.arguments().toArray(new String[0]), classWorld);
|
||||
} finally {
|
||||
if (r.stdoutConsumer().isPresent()
|
||||
|| r.stderrConsumer().isPresent()) {
|
||||
ansiConsoleInstalled.set(null, 0);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ExecutorException("Failed to execute", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new Context(properties, bootClassLoader, version, classWorld, cliClass.getClassLoader(), exec);
|
||||
return new Context(
|
||||
bootClassLoader, version, classWorld, originalClassRealmIds, cliClass.getClassLoader(), exec);
|
||||
} catch (Exception e) {
|
||||
throw new ExecutorException("Failed to create executor", e);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(originalClassLoader);
|
||||
System.setProperties(originalProperties);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected Properties prepareProperties(ExecutorRequest request) {
|
||||
Properties properties = new Properties();
|
||||
properties.putAll(System.getProperties());
|
||||
|
||||
properties.setProperty("user.dir", request.cwd().toString());
|
||||
properties.setProperty("user.home", request.userHomeDirectory().toString());
|
||||
|
||||
Path mavenHome = request.installationDirectory();
|
||||
properties.setProperty("maven.home", mavenHome.toString());
|
||||
properties.setProperty(
|
||||
"maven.multiModuleProjectDirectory", request.cwd().toString());
|
||||
properties.setProperty("maven.mainClass", "org.apache.maven.cling.MavenCling");
|
||||
properties.setProperty(
|
||||
"library.jline.path", mavenHome.resolve("lib/jline-native").toString());
|
||||
// TODO: is this needed?
|
||||
properties.setProperty("org.jline.terminal.provider", "dumb");
|
||||
|
||||
if (request.jvmSystemProperties().isPresent()) {
|
||||
properties.putAll(request.jvmSystemProperties().get());
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws ExecutorException {
|
||||
if (closed.compareAndExchange(false, true)) {
|
||||
try {
|
||||
ArrayList<Exception> exceptions = new ArrayList<>();
|
||||
for (Context context : contexts.values()) {
|
||||
|
@ -215,8 +318,9 @@ public class EmbeddedMavenExecutor implements Executor {
|
|||
System.setProperties(originalProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void doClose(Context context) throws Exception {
|
||||
protected void doClose(Context context) throws ExecutorException {
|
||||
Thread.currentThread().setContextClassLoader(context.bootClassLoader);
|
||||
try {
|
||||
try {
|
||||
|
@ -224,6 +328,8 @@ public class EmbeddedMavenExecutor implements Executor {
|
|||
} finally {
|
||||
context.bootClassLoader.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new ExecutorException("Failed to close cleanly", e);
|
||||
} finally {
|
||||
Thread.currentThread().setContextClassLoader(originalClassLoader);
|
||||
}
|
||||
|
|
|
@ -18,21 +18,24 @@
|
|||
*/
|
||||
package org.apache.maven.cling.executor.forked;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.maven.api.annotations.Nullable;
|
||||
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;
|
||||
import static org.apache.maven.api.cli.ExecutorRequest.getCanonicalPath;
|
||||
|
||||
/**
|
||||
* Forked executor implementation, that spawns a subprocess with Maven from the installation directory. Very costly
|
||||
|
@ -44,7 +47,7 @@ public class ForkedMavenExecutor implements Executor {
|
|||
requireNonNull(executorRequest);
|
||||
validate(executorRequest);
|
||||
|
||||
return doExecute(executorRequest, null);
|
||||
return doExecute(executorRequest, wrapStdouterrConsumer(executorRequest));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,27 +57,18 @@ public class ForkedMavenExecutor implements Executor {
|
|||
try {
|
||||
Path cwd = Files.createTempDirectory("forked-executor-maven-version");
|
||||
try {
|
||||
ArrayList<String> stdout = new ArrayList<>();
|
||||
int exitCode = doExecute(
|
||||
executorRequest.toBuilder()
|
||||
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
|
||||
int exitCode = execute(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);
|
||||
}
|
||||
});
|
||||
.arguments(List.of("--version", "--quiet"))
|
||||
.stdoutConsumer(stdout)
|
||||
.build());
|
||||
if (exitCode == 0) {
|
||||
for (String line : stdout) {
|
||||
if (line.startsWith("Apache Maven ")) {
|
||||
return line.substring(13, line.indexOf("(") - 1);
|
||||
}
|
||||
if (stdout.size() > 0) {
|
||||
return stdout.toString()
|
||||
.replace("\n", "")
|
||||
.replace("\r", "")
|
||||
.trim();
|
||||
}
|
||||
return UNKNOWN_VERSION;
|
||||
} else {
|
||||
|
@ -91,6 +85,29 @@ public class ForkedMavenExecutor implements Executor {
|
|||
|
||||
protected void validate(ExecutorRequest executorRequest) throws ExecutorException {}
|
||||
|
||||
@Nullable
|
||||
protected Consumer<Process> wrapStdouterrConsumer(ExecutorRequest executorRequest) {
|
||||
if (executorRequest.stdoutConsumer().isEmpty()
|
||||
&& executorRequest.stderrConsumer().isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return p -> {
|
||||
try {
|
||||
if (executorRequest.stdoutConsumer().isPresent()) {
|
||||
p.getInputStream()
|
||||
.transferTo(executorRequest.stdoutConsumer().get());
|
||||
}
|
||||
if (executorRequest.stderrConsumer().isPresent()) {
|
||||
p.getErrorStream()
|
||||
.transferTo(executorRequest.stderrConsumer().get());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected int doExecute(ExecutorRequest executorRequest, Consumer<Process> processConsumer)
|
||||
throws ExecutorException {
|
||||
ArrayList<String> cmdAndArguments = new ArrayList<>();
|
||||
|
@ -102,16 +119,38 @@ public class ForkedMavenExecutor implements Executor {
|
|||
|
||||
cmdAndArguments.addAll(executorRequest.arguments());
|
||||
|
||||
ArrayList<String> jvmArgs = new ArrayList<>();
|
||||
if (!executorRequest.userHomeDirectory().equals(getCanonicalPath(Paths.get(System.getProperty("user.home"))))) {
|
||||
jvmArgs.add("-Duser.home=" + executorRequest.userHomeDirectory().toString());
|
||||
}
|
||||
if (executorRequest.jvmArguments().isPresent()) {
|
||||
jvmArgs.addAll(executorRequest.jvmArguments().get());
|
||||
}
|
||||
if (executorRequest.jvmSystemProperties().isPresent()) {
|
||||
jvmArgs.addAll(executorRequest.jvmSystemProperties().get().entrySet().stream()
|
||||
.map(e -> "-D" + e.getKey() + "=" + e.getValue())
|
||||
.toList());
|
||||
}
|
||||
|
||||
HashMap<String, String> env = new HashMap<>();
|
||||
if (executorRequest.environmentVariables().isPresent()) {
|
||||
env.putAll(executorRequest.environmentVariables().get());
|
||||
}
|
||||
if (!jvmArgs.isEmpty()) {
|
||||
String mavenOpts = env.getOrDefault("MAVEN_OPTS", "");
|
||||
if (!mavenOpts.isEmpty()) {
|
||||
mavenOpts += " ";
|
||||
}
|
||||
mavenOpts += String.join(" ", jvmArgs);
|
||||
env.put("MAVEN_OPTS", mavenOpts);
|
||||
}
|
||||
|
||||
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()));
|
||||
if (!env.isEmpty()) {
|
||||
pb.environment().putAll(env);
|
||||
}
|
||||
|
||||
Process process = pb.start();
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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.internal;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.maven.api.annotations.Nullable;
|
||||
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.cling.executor.ExecutorHelper;
|
||||
import org.apache.maven.cling.executor.ExecutorTool;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Simple router to executors, and delegate to executor tool.
|
||||
*/
|
||||
public class HelperImpl implements ExecutorHelper {
|
||||
private final Mode defaultMode;
|
||||
private final Path installationDirectory;
|
||||
private final ExecutorTool executorTool;
|
||||
private final HashMap<Mode, Executor> executors;
|
||||
|
||||
private final ConcurrentHashMap<String, String> cache;
|
||||
|
||||
public HelperImpl(Mode defaultMode, @Nullable Path installationDirectory, Executor embedded, Executor forked) {
|
||||
this.defaultMode = requireNonNull(defaultMode);
|
||||
this.installationDirectory = installationDirectory != null
|
||||
? ExecutorRequest.getCanonicalPath(installationDirectory)
|
||||
: ExecutorRequest.discoverMavenHome();
|
||||
this.executorTool = new ToolboxTool(this);
|
||||
this.executors = new HashMap<>();
|
||||
|
||||
this.executors.put(Mode.EMBEDDED, requireNonNull(embedded, "embedded"));
|
||||
this.executors.put(Mode.FORKED, requireNonNull(forked, "forked"));
|
||||
this.cache = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getDefaultMode() {
|
||||
return defaultMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutorRequest.Builder executorRequest() {
|
||||
return ExecutorRequest.mavenBuilder(installationDirectory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int execute(Mode mode, ExecutorRequest executorRequest) throws ExecutorException {
|
||||
return getExecutor(mode, executorRequest).execute(executorRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mavenVersion() {
|
||||
return cache.computeIfAbsent("maven.version", k -> {
|
||||
ExecutorRequest request = executorRequest().build();
|
||||
return getExecutor(Mode.AUTO, request).mavenVersion(request);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> dump(ExecutorRequest.Builder request) throws ExecutorException {
|
||||
return executorTool.dump(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String localRepository(ExecutorRequest.Builder request) throws ExecutorException {
|
||||
return executorTool.localRepository(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String artifactPath(ExecutorRequest.Builder request, String gav, String repositoryId)
|
||||
throws ExecutorException {
|
||||
return executorTool.artifactPath(request, gav, repositoryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String metadataPath(ExecutorRequest.Builder request, String gav, String repositoryId)
|
||||
throws ExecutorException {
|
||||
return executorTool.metadataPath(request, gav, repositoryId);
|
||||
}
|
||||
|
||||
protected Executor getExecutor(Mode mode, ExecutorRequest request) throws ExecutorException {
|
||||
return switch (mode) {
|
||||
case AUTO -> getExecutorByRequest(request);
|
||||
case EMBEDDED -> executors.get(Mode.EMBEDDED);
|
||||
case FORKED -> executors.get(Mode.FORKED);
|
||||
};
|
||||
}
|
||||
|
||||
private Executor getExecutorByRequest(ExecutorRequest request) {
|
||||
if (Objects.equals(request.command(), ExecutorRequest.MVN)
|
||||
&& request.environmentVariables().orElse(Collections.emptyMap()).isEmpty()
|
||||
&& request.jvmArguments().orElse(Collections.emptyList()).isEmpty()) {
|
||||
return getExecutor(Mode.EMBEDDED, request);
|
||||
} else {
|
||||
return getExecutor(Mode.FORKED, request);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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.internal;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.maven.api.cli.ExecutorException;
|
||||
import org.apache.maven.api.cli.ExecutorRequest;
|
||||
import org.apache.maven.cling.executor.ExecutorHelper;
|
||||
import org.apache.maven.cling.executor.ExecutorTool;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* {@link ExecutorTool} implementation based on Maveniverse Toolbox. It uses Toolbox mojos to implement all the
|
||||
* required operations.
|
||||
*
|
||||
* @see <a href="https://github.com/maveniverse/toolbox">Maveniverse Toolbox</a>
|
||||
*/
|
||||
public class ToolboxTool implements ExecutorTool {
|
||||
private static final String TOOLBOX = "eu.maveniverse.maven.plugins:toolbox:0.5.2:";
|
||||
|
||||
private final ExecutorHelper helper;
|
||||
|
||||
public ToolboxTool(ExecutorHelper helper) {
|
||||
this.helper = requireNonNull(helper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> dump(ExecutorRequest.Builder executorRequest) throws ExecutorException {
|
||||
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream stderr = new ByteArrayOutputStream();
|
||||
ExecutorRequest.Builder builder = mojo(executorRequest, "gav-dump")
|
||||
.argument("-DasProperties")
|
||||
.stdoutConsumer(stdout)
|
||||
.stderrConsumer(stderr);
|
||||
doExecute(builder);
|
||||
try {
|
||||
Properties properties = new Properties();
|
||||
properties.load(new ByteArrayInputStream(stdout.toByteArray()));
|
||||
return properties.entrySet().stream()
|
||||
.collect(Collectors.toMap(
|
||||
e -> String.valueOf(e.getKey()),
|
||||
e -> String.valueOf(e.getValue()),
|
||||
(prev, next) -> next,
|
||||
HashMap::new));
|
||||
} catch (IOException e) {
|
||||
throw new ExecutorException("Unable to parse properties", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String localRepository(ExecutorRequest.Builder executorRequest) throws ExecutorException {
|
||||
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream stderr = new ByteArrayOutputStream();
|
||||
ExecutorRequest.Builder builder = mojo(executorRequest, "gav-local-repository-path")
|
||||
.stdoutConsumer(stdout)
|
||||
.stderrConsumer(stderr);
|
||||
doExecute(builder);
|
||||
return shaveStdout(stdout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String artifactPath(ExecutorRequest.Builder executorRequest, String gav, String repositoryId)
|
||||
throws ExecutorException {
|
||||
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream stderr = new ByteArrayOutputStream();
|
||||
ExecutorRequest.Builder builder = mojo(executorRequest, "gav-artifact-path")
|
||||
.argument("-Dgav=" + gav)
|
||||
.stdoutConsumer(stdout)
|
||||
.stderrConsumer(stderr);
|
||||
if (repositoryId != null) {
|
||||
builder.argument("-Drepository=" + repositoryId + "::unimportant");
|
||||
}
|
||||
doExecute(builder);
|
||||
return shaveStdout(stdout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String metadataPath(ExecutorRequest.Builder executorRequest, String gav, String repositoryId)
|
||||
throws ExecutorException {
|
||||
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
|
||||
ByteArrayOutputStream stderr = new ByteArrayOutputStream();
|
||||
ExecutorRequest.Builder builder = mojo(executorRequest, "gav-metadata-path")
|
||||
.argument("-Dgav=" + gav)
|
||||
.stdoutConsumer(stdout)
|
||||
.stderrConsumer(stderr);
|
||||
if (repositoryId != null) {
|
||||
builder.argument("-Drepository=" + repositoryId + "::unimportant");
|
||||
}
|
||||
doExecute(builder);
|
||||
return shaveStdout(stdout);
|
||||
}
|
||||
|
||||
private ExecutorRequest.Builder mojo(ExecutorRequest.Builder builder, String mojo) {
|
||||
if (helper.mavenVersion().startsWith("4.")) {
|
||||
builder.argument("--raw-streams");
|
||||
}
|
||||
return builder.argument(TOOLBOX + mojo).argument("--quiet").argument("-DforceStdout");
|
||||
}
|
||||
|
||||
private void doExecute(ExecutorRequest.Builder builder) {
|
||||
ExecutorRequest request = builder.build();
|
||||
int ec = helper.execute(request);
|
||||
if (ec != 0) {
|
||||
throw new ExecutorException("Unexpected exit code=" + ec + "; stdout="
|
||||
+ request.stdoutConsumer().orElse(null) + "; stderr="
|
||||
+ request.stderrConsumer().orElse(null));
|
||||
}
|
||||
}
|
||||
|
||||
private String shaveStdout(ByteArrayOutputStream stdout) {
|
||||
return stdout.toString().replace("\n", "").replace("\r", "");
|
||||
}
|
||||
}
|
|
@ -35,6 +35,43 @@ import org.junit.jupiter.api.io.TempDir;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public abstract class MavenExecutorTestSupport {
|
||||
@Disabled("JUnit on Windows fails to clean up as mvn3 seems does not close log file properly")
|
||||
@Test
|
||||
void dump3(
|
||||
@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd,
|
||||
@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome)
|
||||
throws Exception {
|
||||
String logfile = "m3.log";
|
||||
execute(
|
||||
cwd.resolve(logfile),
|
||||
List.of(mvn3ExecutorRequestBuilder()
|
||||
.cwd(cwd)
|
||||
.userHomeDirectory(userHome)
|
||||
.argument("eu.maveniverse.maven.plugins:toolbox:0.5.2:gav-dump")
|
||||
.argument("-l")
|
||||
.argument(logfile)
|
||||
.build()));
|
||||
System.out.println(Files.readString(cwd.resolve(logfile)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dump4(
|
||||
@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd,
|
||||
@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome)
|
||||
throws Exception {
|
||||
String logfile = "m4.log";
|
||||
execute(
|
||||
cwd.resolve(logfile),
|
||||
List.of(mvn4ExecutorRequestBuilder()
|
||||
.cwd(cwd)
|
||||
.userHomeDirectory(userHome)
|
||||
.argument("eu.maveniverse.maven.plugins:toolbox:0.5.2:gav-dump")
|
||||
.argument("-l")
|
||||
.argument(logfile)
|
||||
.build()));
|
||||
System.out.println(Files.readString(cwd.resolve(logfile)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultFs(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path tempDir) throws Exception {
|
||||
layDownFiles(tempDir);
|
||||
|
@ -141,11 +178,11 @@ public abstract class MavenExecutorTestSupport {
|
|||
}
|
||||
}
|
||||
|
||||
protected ExecutorRequest.Builder mvn3ExecutorRequestBuilder() {
|
||||
public static ExecutorRequest.Builder mvn3ExecutorRequestBuilder() {
|
||||
return ExecutorRequest.mavenBuilder(Paths.get(System.getProperty("maven3home")));
|
||||
}
|
||||
|
||||
protected ExecutorRequest.Builder mvn4ExecutorRequestBuilder() {
|
||||
public static ExecutorRequest.Builder mvn4ExecutorRequestBuilder() {
|
||||
return ExecutorRequest.mavenBuilder(Paths.get(System.getProperty("maven4home")));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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.impl;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.maven.cling.executor.ExecutorHelper;
|
||||
import org.apache.maven.cling.executor.embedded.EmbeddedMavenExecutor;
|
||||
import org.apache.maven.cling.executor.forked.ForkedMavenExecutor;
|
||||
import org.apache.maven.cling.executor.internal.HelperImpl;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import static org.apache.maven.cling.executor.MavenExecutorTestSupport.mvn3ExecutorRequestBuilder;
|
||||
import static org.apache.maven.cling.executor.MavenExecutorTestSupport.mvn4ExecutorRequestBuilder;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class HelperImplTest {
|
||||
private static final EmbeddedMavenExecutor EMBEDDED_MAVEN_EXECUTOR = new EmbeddedMavenExecutor();
|
||||
private static final ForkedMavenExecutor FORKED_MAVEN_EXECUTOR = new ForkedMavenExecutor();
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(ExecutorHelper.Mode.class)
|
||||
void dump3(ExecutorHelper.Mode mode) throws Exception {
|
||||
ExecutorHelper helper = new HelperImpl(
|
||||
mode,
|
||||
mvn3ExecutorRequestBuilder().build().installationDirectory(),
|
||||
EMBEDDED_MAVEN_EXECUTOR,
|
||||
FORKED_MAVEN_EXECUTOR);
|
||||
Map<String, String> dump = helper.dump(helper.executorRequest());
|
||||
assertEquals(System.getProperty("maven3version"), dump.get("maven.version"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(ExecutorHelper.Mode.class)
|
||||
void dump4(ExecutorHelper.Mode mode) throws Exception {
|
||||
ExecutorHelper helper = new HelperImpl(
|
||||
mode,
|
||||
mvn4ExecutorRequestBuilder().build().installationDirectory(),
|
||||
EMBEDDED_MAVEN_EXECUTOR,
|
||||
FORKED_MAVEN_EXECUTOR);
|
||||
Map<String, String> dump = helper.dump(helper.executorRequest());
|
||||
assertEquals(System.getProperty("maven4version"), dump.get("maven.version"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(ExecutorHelper.Mode.class)
|
||||
void version3(ExecutorHelper.Mode mode) {
|
||||
ExecutorHelper helper = new HelperImpl(
|
||||
mode,
|
||||
mvn3ExecutorRequestBuilder().build().installationDirectory(),
|
||||
EMBEDDED_MAVEN_EXECUTOR,
|
||||
FORKED_MAVEN_EXECUTOR);
|
||||
assertEquals(System.getProperty("maven3version"), helper.mavenVersion());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(ExecutorHelper.Mode.class)
|
||||
void version4(ExecutorHelper.Mode mode) {
|
||||
ExecutorHelper helper = new HelperImpl(
|
||||
mode,
|
||||
mvn4ExecutorRequestBuilder().build().installationDirectory(),
|
||||
EMBEDDED_MAVEN_EXECUTOR,
|
||||
FORKED_MAVEN_EXECUTOR);
|
||||
assertEquals(System.getProperty("maven4version"), helper.mavenVersion());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(ExecutorHelper.Mode.class)
|
||||
void localRepository3(ExecutorHelper.Mode mode) {
|
||||
ExecutorHelper helper = new HelperImpl(
|
||||
mode,
|
||||
mvn3ExecutorRequestBuilder().build().installationDirectory(),
|
||||
EMBEDDED_MAVEN_EXECUTOR,
|
||||
FORKED_MAVEN_EXECUTOR);
|
||||
String localRepository = helper.localRepository(helper.executorRequest());
|
||||
Path local = Paths.get(localRepository);
|
||||
assertTrue(Files.isDirectory(local));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(ExecutorHelper.Mode.class)
|
||||
void localRepository4(ExecutorHelper.Mode mode) {
|
||||
ExecutorHelper helper = new HelperImpl(
|
||||
mode,
|
||||
mvn4ExecutorRequestBuilder().build().installationDirectory(),
|
||||
EMBEDDED_MAVEN_EXECUTOR,
|
||||
FORKED_MAVEN_EXECUTOR);
|
||||
String localRepository = helper.localRepository(helper.executorRequest());
|
||||
Path local = Paths.get(localRepository);
|
||||
assertTrue(Files.isDirectory(local));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(ExecutorHelper.Mode.class)
|
||||
void artifactPath3(ExecutorHelper.Mode mode) {
|
||||
ExecutorHelper helper = new HelperImpl(
|
||||
mode,
|
||||
mvn3ExecutorRequestBuilder().build().installationDirectory(),
|
||||
EMBEDDED_MAVEN_EXECUTOR,
|
||||
FORKED_MAVEN_EXECUTOR);
|
||||
String path = helper.artifactPath(helper.executorRequest(), "aopalliance:aopalliance:1.0", "central");
|
||||
assertEquals(
|
||||
"aopalliance" + File.separator + "aopalliance" + File.separator + "1.0" + File.separator
|
||||
+ "aopalliance-1.0.jar",
|
||||
path);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(ExecutorHelper.Mode.class)
|
||||
void artifactPath4(ExecutorHelper.Mode mode) {
|
||||
ExecutorHelper helper = new HelperImpl(
|
||||
mode,
|
||||
mvn4ExecutorRequestBuilder().build().installationDirectory(),
|
||||
EMBEDDED_MAVEN_EXECUTOR,
|
||||
FORKED_MAVEN_EXECUTOR);
|
||||
String path = helper.artifactPath(helper.executorRequest(), "aopalliance:aopalliance:1.0", "central");
|
||||
assertEquals(
|
||||
"aopalliance" + File.separator + "aopalliance" + File.separator + "1.0" + File.separator
|
||||
+ "aopalliance-1.0.jar",
|
||||
path);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(ExecutorHelper.Mode.class)
|
||||
void metadataPath3(ExecutorHelper.Mode mode) {
|
||||
ExecutorHelper helper = new HelperImpl(
|
||||
mode,
|
||||
mvn3ExecutorRequestBuilder().build().installationDirectory(),
|
||||
EMBEDDED_MAVEN_EXECUTOR,
|
||||
FORKED_MAVEN_EXECUTOR);
|
||||
String path = helper.metadataPath(helper.executorRequest(), "aopalliance", "someremote");
|
||||
assertEquals("aopalliance" + File.separator + "maven-metadata-someremote.xml", path);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(ExecutorHelper.Mode.class)
|
||||
void metadataPath4(ExecutorHelper.Mode mode) {
|
||||
ExecutorHelper helper = new HelperImpl(
|
||||
mode,
|
||||
mvn4ExecutorRequestBuilder().build().installationDirectory(),
|
||||
EMBEDDED_MAVEN_EXECUTOR,
|
||||
FORKED_MAVEN_EXECUTOR);
|
||||
String path = helper.metadataPath(helper.executorRequest(), "aopalliance", "someremote");
|
||||
assertEquals("aopalliance" + File.separator + "maven-metadata-someremote.xml", path);
|
||||
}
|
||||
}
|
|
@ -98,17 +98,6 @@ under the License.
|
|||
<artifactId>plexus-utils</artifactId>
|
||||
<!-- NOTE: Use compile scope for transitivity. -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.shared</groupId>
|
||||
<artifactId>maven-verifier</artifactId>
|
||||
<!-- TODO: not transitive in tests artifact, so must be left in main
|
||||
scope <scope>test</scope> -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.shared</groupId>
|
||||
<artifactId>maven-shared-utils</artifactId>
|
||||
<version>3.4.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jetty (as test HTTP server) -->
|
||||
<dependency>
|
||||
|
@ -526,6 +515,7 @@ under the License.
|
|||
<forkCount>0</forkCount>
|
||||
<reuseForks>true</reuseForks>
|
||||
<skip>true</skip>
|
||||
<promoteUserPropertiesToSystemProperties>false</promoteUserPropertiesToSystemProperties>
|
||||
<systemPropertyVariables>
|
||||
<maven.version>${maven.version}</maven.version>
|
||||
<maven.home>${preparedMavenHome}</maven.home>
|
||||
|
@ -571,7 +561,7 @@ under the License.
|
|||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>apache-maven</artifactId>
|
||||
<version>4.0.0-beta-6-SNAPSHOT</version>
|
||||
<version>${maven-version}</version>
|
||||
<classifier>bin</classifier>
|
||||
<type>zip</type>
|
||||
</dependency>
|
||||
|
@ -649,28 +639,6 @@ under the License.
|
|||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>maven-repo-local-layout</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>maven.repo.local.layout</name>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<!-- Pass this through to the tests (if set!) to have them
|
||||
pick the right repository layout -->
|
||||
<maven.repo.local.layout>${maven.repo.local.layout}</maven.repo.local.layout>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jdk-properties</id>
|
||||
<activation>
|
||||
|
@ -697,7 +665,6 @@ under the License.
|
|||
<useSystemClassLoader>false</useSystemClassLoader>
|
||||
<systemPropertyVariables>
|
||||
<verifier.forkMode>auto</verifier.forkMode>
|
||||
<emma.rt.control>false</emma.rt.control>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
|
||||
import org.apache.maven.shared.verifier.util.ResourceExtractor;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -34,6 +35,8 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||
*
|
||||
* @author Benjamin Bentmann
|
||||
*/
|
||||
@Disabled(
|
||||
"This IT is testing -l, while new Verifier uses same switch to make Maven4 log to file; in short, if that is broken, all ITs would be broken as well")
|
||||
public class MavenITmng3183LoggingToFileTest extends AbstractMavenIntegrationTestCase {
|
||||
|
||||
public MavenITmng3183LoggingToFileTest() {
|
||||
|
|
|
@ -116,15 +116,13 @@ public class MavenITmng3379ParallelArtifactDownloadsTest extends AbstractMavenIn
|
|||
}
|
||||
|
||||
private void assertMetadata(Verifier verifier, String gid, String aid, String ver, String sha1) throws Exception {
|
||||
String name = "maven-metadata-maven-core-it.xml";
|
||||
File file = new File(verifier.getArtifactMetadataPath(gid, aid, ver, name));
|
||||
File file = new File(verifier.getArtifactMetadataPath(gid, aid, ver, "maven-metadata.xml", "maven-core-it"));
|
||||
assertTrue(file.isFile(), file.getAbsolutePath());
|
||||
assertEquals(sha1, ItUtils.calcHash(file, "SHA-1"));
|
||||
}
|
||||
|
||||
private void assertMetadata(Verifier verifier, String gid, String aid, String sha1) throws Exception {
|
||||
String name = "maven-metadata-maven-core-it.xml";
|
||||
File file = new File(verifier.getArtifactMetadataPath(gid, aid, null, name));
|
||||
File file = new File(verifier.getArtifactMetadataPath(gid, aid, null, "maven-metadata.xml", "maven-core-it"));
|
||||
assertTrue(file.isFile(), file.getAbsolutePath());
|
||||
assertEquals(sha1, ItUtils.calcHash(file, "SHA-1"));
|
||||
}
|
||||
|
|
|
@ -54,8 +54,7 @@ public class MavenITmng3951AbsolutePathsTest extends AbstractMavenIntegrationTes
|
|||
*/
|
||||
String repoDir = new File(verifier.getLocalRepository()).getAbsolutePath();
|
||||
if (getRoot(new File(repoDir)).equals(getRoot(testDir))) {
|
||||
// NOTE: We can only test the local repo if it resides on the same drive as the test
|
||||
verifier.setLocalRepo(repoDir.substring(repoDir.indexOf(File.separator)));
|
||||
verifier.addCliArgument("-Dmaven.repo.local=" + repoDir.substring(repoDir.indexOf(File.separator)));
|
||||
}
|
||||
|
||||
verifier.setAutoclean(false);
|
||||
|
|
|
@ -62,7 +62,7 @@ public class MavenITmng3955EffectiveSettingsTest extends AbstractMavenIntegratio
|
|||
assertEquals("true", props.getProperty("settings.offline"));
|
||||
assertEquals("false", props.getProperty("settings.interactiveMode"));
|
||||
assertEquals(
|
||||
new File(verifier.getLocalRepository()).getAbsoluteFile(),
|
||||
new File(verifier.getLocalRepositoryWithSettings("settings.xml")).getAbsoluteFile(),
|
||||
new File(props.getProperty("settings.localRepository")).getAbsoluteFile());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,7 +114,6 @@ public class MavenITmng5868NoDuplicateAttachedArtifacts extends AbstractMavenInt
|
|||
verifier.deleteArtifacts("org.apache.maven.its.mng5868");
|
||||
verifier.addCliArgument("-Dartifact.attachedFile=" + tmp.toFile().getCanonicalPath());
|
||||
verifier.addCliArgument("-DdeploymentPort=" + port);
|
||||
verifier.displayStreamBuffers();
|
||||
verifier.addCliArguments("org.apache.maven.its.plugins:maven-it-plugin-artifact:2.1-SNAPSHOT:attach", "deploy");
|
||||
verifier.execute();
|
||||
verifier.verifyErrorFreeLog();
|
||||
|
|
|
@ -41,22 +41,22 @@ public class MavenITmng8106OverlappingDirectoryRolesTest extends AbstractMavenIn
|
|||
String tailRepo = System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository";
|
||||
|
||||
Verifier verifier = newVerifier(new File(testDir, "plugin").getAbsolutePath());
|
||||
verifier.setLocalRepo(repo);
|
||||
verifier.addCliArgument("-X");
|
||||
verifier.addCliArgument("-Dmaven.repo.local=" + repo);
|
||||
verifier.addCliArgument("-Dmaven.repo.local.tail=" + tailRepo);
|
||||
verifier.addCliArgument("install");
|
||||
verifier.execute();
|
||||
verifier.verifyErrorFreeLog();
|
||||
|
||||
verifier = newVerifier(new File(testDir, "jar").getAbsolutePath());
|
||||
verifier.setLocalRepo(repo);
|
||||
verifier.addCliArgument("-X");
|
||||
verifier.addCliArgument("-Dmaven.repo.local=" + repo);
|
||||
verifier.addCliArgument("-Dmaven.repo.local.tail=" + tailRepo);
|
||||
verifier.addCliArgument("install");
|
||||
verifier.execute();
|
||||
verifier.verifyErrorFreeLog();
|
||||
|
||||
File metadataFile = new File(new File(verifier.getLocalRepository()), "mng-8106/it/maven-metadata-local.xml");
|
||||
File metadataFile = new File(new File(repo), "mng-8106/it/maven-metadata-local.xml");
|
||||
Xpp3Dom dom;
|
||||
try (FileReader reader = new FileReader(metadataFile)) {
|
||||
dom = Xpp3DomBuilder.build(reader);
|
||||
|
|
|
@ -51,6 +51,7 @@ public class MavenITmng8181CentralRepoTest extends AbstractMavenIntegrationTestC
|
|||
verifier.addCliArgument("-Dmaven.repo.local.tail=target/null");
|
||||
verifier.addCliArgument("-Dmaven.repo.central=http://repo1.maven.org/");
|
||||
verifier.addCliArgument("validate");
|
||||
verifier.setHandleLocalRepoTail(false); // we want isolation to have Maven fail due non-HTTPS repo
|
||||
assertThrows(VerificationException.class, verifier::execute);
|
||||
verifier.verifyTextInLog("central (http://repo1.maven.org/, default, releases)");
|
||||
}
|
||||
|
|
|
@ -50,7 +50,8 @@ class MavenITmng8331VersionedAndUnversionedDependenciesTest extends AbstractMave
|
|||
|
||||
Verifier verifier = new Verifier(testDir.getAbsolutePath());
|
||||
verifier.setLogFileName("allDependenciesArePresentInTheProject.txt");
|
||||
verifier.executeGoal("test-compile");
|
||||
verifier.addCliArgument("test-compile");
|
||||
verifier.execute();
|
||||
|
||||
verifier.verifyErrorFreeLog();
|
||||
}
|
||||
|
|
|
@ -57,16 +57,13 @@ class MavenITmng8400CanonicalMavenHomeTest extends AbstractMavenIntegrationTestC
|
|||
System.setProperty("maven.home", linkedMavenHome.toString());
|
||||
|
||||
Verifier verifier = newVerifier(basedir.toString(), null);
|
||||
verifier.addCliArgument("--raw-streams");
|
||||
verifier.addCliArgument("--quiet");
|
||||
verifier.addCliArgument("-DforceStdout");
|
||||
verifier.addCliArgument("-DasProperties");
|
||||
verifier.addCliArgument("-DtoFile=dump.properties");
|
||||
verifier.addCliArgument("eu.maveniverse.maven.plugins:toolbox:0.5.2:gav-dump");
|
||||
// TODO: fork until new entry point CLIng is used
|
||||
verifier.setForkJvm(true);
|
||||
verifier.execute();
|
||||
verifier.verifyErrorFreeLog();
|
||||
|
||||
String dump = verifier.loadLogContent();
|
||||
String dump = Files.readString(basedir.resolve("dump.properties"), StandardCharsets.UTF_8);
|
||||
Properties props = new Properties();
|
||||
props.load(new ByteArrayInputStream(dump.getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
|
|
|
@ -31,30 +31,40 @@ under the License.
|
|||
<name>Maven IT Helper Library</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-executor</artifactId>
|
||||
<!-- TODO: hack -->
|
||||
<version>4.0.0-rc-2-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- needed for VersionRange and Version -->
|
||||
<dependency>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven-artifact</artifactId>
|
||||
<version>3.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<version>3.6.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.plexus</groupId>
|
||||
<artifactId>plexus-utils</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.shared</groupId>
|
||||
<artifactId>maven-verifier</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.shared</groupId>
|
||||
<artifactId>maven-shared-utils</artifactId>
|
||||
<version>3.4.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<maven.home>${maven.home}</maven.home>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
|
@ -252,7 +252,7 @@ public abstract class AbstractMavenIntegrationTestCase {
|
|||
}
|
||||
|
||||
protected Verifier newVerifier(String basedir, String settings, boolean debug) throws VerificationException {
|
||||
Verifier verifier = new Verifier(basedir, debug);
|
||||
Verifier verifier = new Verifier(basedir);
|
||||
|
||||
verifier.setAutoclean(false);
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -16,29 +16,21 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.maven.it;
|
||||
package org.apache.maven.shared.verifier;
|
||||
|
||||
/**
|
||||
* Basic Ansi support: can't use Ansi because IT is executed in separate classloader.
|
||||
* @author Jason van Zyl
|
||||
*/
|
||||
class AnsiSupport {
|
||||
private static final String ESC = String.valueOf((char) 27) + '[';
|
||||
|
||||
private static final String NORMAL = ESC + "0;39m";
|
||||
|
||||
static String success(String msg) {
|
||||
return ESC + "1;32m" + msg + NORMAL;
|
||||
public class VerificationException extends Exception {
|
||||
public VerificationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
static String warning(String msg) {
|
||||
return ESC + "1;33m" + msg + NORMAL;
|
||||
public VerificationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
static String error(String msg) {
|
||||
return ESC + "1;31m" + msg + NORMAL;
|
||||
}
|
||||
|
||||
static String bold(String msg) {
|
||||
return ESC + "1m" + msg + NORMAL;
|
||||
public VerificationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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.shared.verifier.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.codehaus.plexus.util.FileUtils;
|
||||
|
||||
/**
|
||||
* TODO this can be replaced with plexus-archiver
|
||||
*/
|
||||
public class ResourceExtractor {
|
||||
|
||||
public static File simpleExtractResources(Class<?> cl, String resourcePath) throws IOException {
|
||||
String tempDirPath = System.getProperty("maven.test.tmpdir", System.getProperty("java.io.tmpdir"));
|
||||
File tempDir = new File(tempDirPath);
|
||||
|
||||
File testDir = new File(tempDir, resourcePath);
|
||||
|
||||
FileUtils.deleteDirectory(testDir);
|
||||
|
||||
testDir = extractResourcePath(cl, resourcePath, tempDir, false);
|
||||
return testDir;
|
||||
}
|
||||
|
||||
public static File extractResourcePath(String resourcePath, File dest) throws IOException {
|
||||
return extractResourcePath(ResourceExtractor.class, resourcePath, dest);
|
||||
}
|
||||
|
||||
public static File extractResourcePath(Class<?> cl, String resourcePath, File dest) throws IOException {
|
||||
return extractResourcePath(cl, resourcePath, dest, false);
|
||||
}
|
||||
|
||||
public static File extractResourcePath(Class<?> cl, String resourcePath, File tempDir, boolean alwaysExtract)
|
||||
throws IOException {
|
||||
File dest = new File(tempDir, resourcePath);
|
||||
return extractResourceToDestination(cl, resourcePath, dest, alwaysExtract);
|
||||
}
|
||||
|
||||
public static File extractResourceToDestination(
|
||||
Class<?> cl, String resourcePath, File destination, boolean alwaysExtract) throws IOException {
|
||||
URL url = cl.getResource(resourcePath);
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("Resource not found: " + resourcePath);
|
||||
}
|
||||
if ("jar".equalsIgnoreCase(url.getProtocol())) {
|
||||
File jarFile = getJarFileFromUrl(url);
|
||||
extractResourcePathFromJar(cl, jarFile, resourcePath, destination);
|
||||
} else {
|
||||
try {
|
||||
File resourceFile = new File(new URI(url.toExternalForm()));
|
||||
if (!alwaysExtract) {
|
||||
return resourceFile;
|
||||
}
|
||||
if (resourceFile.isDirectory()) {
|
||||
FileUtils.copyDirectoryStructure(resourceFile, destination);
|
||||
} else {
|
||||
FileUtils.copyFile(resourceFile, destination);
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("Couldn't convert URL to File:" + url, e);
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static void extractResourcePathFromJar(Class<?> cl, File jarFile, String resourcePath, File dest)
|
||||
throws IOException {
|
||||
ZipFile z = new ZipFile(jarFile, ZipFile.OPEN_READ);
|
||||
String zipStyleResourcePath = resourcePath.substring(1) + "/";
|
||||
ZipEntry ze = z.getEntry(zipStyleResourcePath);
|
||||
if (ze != null) {
|
||||
// DGF If it's a directory, then we need to look at all the entries
|
||||
for (Enumeration<? extends ZipEntry> entries = z.entries(); entries.hasMoreElements(); ) {
|
||||
ze = entries.nextElement();
|
||||
if (ze.getName().startsWith(zipStyleResourcePath)) {
|
||||
String relativePath = ze.getName().substring(zipStyleResourcePath.length());
|
||||
File destFile = new File(dest, relativePath);
|
||||
if (ze.isDirectory()) {
|
||||
destFile.mkdirs();
|
||||
} else {
|
||||
try (FileOutputStream fos = new FileOutputStream(destFile)) {
|
||||
z.getInputStream(ze).transferTo(fos);
|
||||
} finally {
|
||||
z.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try (FileOutputStream fos = new FileOutputStream(dest)) {
|
||||
cl.getResourceAsStream(resourcePath).transferTo(fos);
|
||||
} finally {
|
||||
z.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static File getJarFileFromUrl(URL url) {
|
||||
if (!"jar".equalsIgnoreCase(url.getProtocol())) {
|
||||
throw new IllegalArgumentException("This is not a Jar URL:" + url.toString());
|
||||
}
|
||||
String resourceFilePath = url.getFile();
|
||||
int index = resourceFilePath.indexOf("!");
|
||||
if (index == -1) {
|
||||
throw new RuntimeException("Bug! " + url.toExternalForm() + " does not have a '!'");
|
||||
}
|
||||
String jarFileURI = resourceFilePath.substring(0, index);
|
||||
try {
|
||||
File jarFile = new File(new URI(jarFileURI));
|
||||
return jarFile;
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("Bug! URI failed to parse: " + jarFileURI, e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ under the License.
|
|||
<parent>
|
||||
<groupId>org.apache.maven</groupId>
|
||||
<artifactId>maven</artifactId>
|
||||
<version>4.0.0-beta-6-SNAPSHOT</version>
|
||||
<version>4.0.0-rc-2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.apache.maven.its</groupId>
|
||||
|
@ -73,7 +73,7 @@ under the License.
|
|||
<!-- <maven.compiler.source>8</maven.compiler.source>-->
|
||||
<!-- <maven.compiler.target>8</maven.compiler.target>-->
|
||||
|
||||
<maven-version>4.0.0-beta-6-SNAPSHOT</maven-version>
|
||||
<maven-version>4.0.0-rc-2-SNAPSHOT</maven-version>
|
||||
<maven-plugin-tools-version>3.15.1</maven-plugin-tools-version>
|
||||
</properties>
|
||||
|
||||
|
|
Loading…
Reference in New Issue