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 4fc2ea08d4 [MNG-8615] [MNG-8616] Maven core extensions handling improvements (#2147) 4fc2ea08d4 is described below commit 4fc2ea08d4f7cd94126bc1b70d9ba59ff91c0420 Author: Tamas Cservenak <ta...@cservenak.net> AuthorDate: Thu Mar 13 13:50:59 2025 +0100 [MNG-8615] [MNG-8616] Maven core extensions handling improvements (#2147) It all started with MNG-8615 to not swallow DI problems while loading core extensions. Then tried to add origin as well, but it turns out there is lack of context. Then turned out models are not location tracking. Then came MNG-8616 as Maven was too rigid and did not apply precedence for extensions making project hopping problematic, if not impossible... Changes: * do not swallow DI issues happened during core extension loading; belly up instead. * report which extension caused DI issue * make core extension models and IO location tracking * alter CLI api to carry all extensions "by origin" (project, user, installation) * parser should be plain dumb, all it does is load extensions in precedence order and validates them (validity: they must be GA unique) * DI capsule performs now "selection" of extensions (based on precedence) and loads them as before * finally: we have now DEBUG logs which all extensions were considered and which were effectively loaded, something I missed a lot * new UTs revealed MavenInvokerUT problem: ClassWorlds were not cleaned up properly Behavioural change since 4.0.0-rc-3: * conflict within same source (same file) makes Maven fail - as before * conflict spanning across sources is warning only; precedence is applied to select extension to be loaded --- https://issues.apache.org/jira/browse/MNG-8615 https://issues.apache.org/jira/browse/MNG-8616 --- api/maven-api-cli/pom.xml | 2 + .../org/apache/maven/api/cli/CoreExtensions.java | 48 +++++++++++ .../org/apache/maven/api/cli/InvokerRequest.java | 11 ++- .../org/apache/maven/api/cli/ParserRequest.java | 2 +- .../maven/api/cli/extensions/package-info.java | 2 - .../main/java/org/apache/maven/cli/MavenCli.java | 5 +- impl/maven-cli/pom.xml | 1 + .../extensions/BootstrapCoreExtensionManager.java | 8 +- .../LoadedCoreExtension.java} | 19 ++--- .../maven/cling/invoker/BaseInvokerRequest.java | 8 +- .../org/apache/maven/cling/invoker/BaseParser.java | 98 +++++++++++++--------- .../cling/invoker/ContainerCapsuleFactory.java | 3 +- ...suleFactory.java => CoreExtensionSelector.java} | 12 ++- .../apache/maven/cling/invoker/LookupInvoker.java | 7 +- .../invoker/PlexusContainerCapsuleFactory.java | 73 ++++++++++------ .../invoker/PrecedenceCoreExtensionSelector.java | 96 +++++++++++++++++++++ .../cling/invoker/mvn/MavenInvokerRequest.java | 4 +- .../invoker/mvnenc/EncryptInvokerRequest.java | 4 +- .../cling/invoker/mvnsh/ShellInvokerRequest.java | 4 +- .../maven/cling/invoker/mvn/MavenInvokerTest.java | 81 ++++++++++++++++-- .../cling/invoker/mvn/MavenInvokerTestSupport.java | 10 ++- .../mvn/resident/ResidentMavenInvokerTest.java | 7 +- 22 files changed, 387 insertions(+), 118 deletions(-) diff --git a/api/maven-api-cli/pom.xml b/api/maven-api-cli/pom.xml index fd096191b1..92517b0c99 100644 --- a/api/maven-api-cli/pom.xml +++ b/api/maven-api-cli/pom.xml @@ -59,6 +59,8 @@ <template>model.vm</template> </templates> <params> + <param>locationTracking=true</param> + <param>generateLocationClasses=true</param> <param>packageModelV4=org.apache.maven.api.cli.extensions</param> <param>packageToolV4=org.apache.maven.cli.internal.extension.io</param> </params> diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/CoreExtensions.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/CoreExtensions.java new file mode 100644 index 0000000000..8206b26b0a --- /dev/null +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/CoreExtensions.java @@ -0,0 +1,48 @@ +/* + * 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 org.apache.maven.api.Constants; +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.cli.extensions.CoreExtension; + +import static java.util.Objects.requireNonNull; + +/** + * Represents the list of core extensions configured at one source. The list is validated (are GA unique), but no + * other logic than that is applied. + * + * @since 4.0.0 + * @param source The source file of core extensions, is never {@code null}. + * @param coreExtensions The configured core extensions, is never {@code null}. Contents of list is guaranteed to be unique by GA. + * + * @see Constants#MAVEN_PROJECT_EXTENSIONS + * @see Constants#MAVEN_USER_EXTENSIONS + * @see Constants#MAVEN_INSTALLATION_EXTENSIONS + */ +@Experimental +public record CoreExtensions(Path source, List<CoreExtension> coreExtensions) { + public CoreExtensions(Path source, List<CoreExtension> coreExtensions) { + this.source = requireNonNull(source, "source"); + this.coreExtensions = requireNonNull(coreExtensions, "coreExtensions"); + } +} 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 3df660c130..e9b2a480ed 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 @@ -28,7 +28,6 @@ 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.cli.extensions.CoreExtension; import org.apache.maven.api.services.Lookup; import org.apache.maven.api.services.MessageBuilderFactory; @@ -172,12 +171,16 @@ default Optional<OutputStream> stdErr() { } /** - * Returns a list of core extensions, if configured in the .mvn/extensions.xml file. + * Returns a list of core extensions from all sources, that were discovered and loaded. Each instance of + * {@link CoreExtensions} is validated, but the list elements may have overlapping elements, that requires + * some logic to sort out (like precedence). + * <p> + * The list of {@link CoreExtensions} if present, is in precedence order. * - * @return an {@link Optional} containing the list of core extensions, or empty if not configured + * @return an {@link Optional} containing the {@link CoreExtensions}, or empty if not configured */ @Nonnull - Optional<List<CoreExtension>> coreExtensions(); + Optional<List<CoreExtensions>> coreExtensions(); /** * Returns the options associated with this invocation request. diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java index 5a5913b766..ea30233fc1 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/ParserRequest.java @@ -317,7 +317,7 @@ public ParserRequest build() { command, commandName, List.copyOf(args), - logger, + lookup.lookupOptional(Logger.class).orElse(logger), messageBuilderFactory, lookup, cwd, diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/extensions/package-info.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/extensions/package-info.java index 7b4aafe562..65b9a662b5 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/extensions/package-info.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/extensions/package-info.java @@ -19,8 +19,6 @@ /** * Provides support for Maven core extensions configuration and management. - * Core extensions can be configured through {@code .mvn/extensions.xml} in the project - * base directory to enhance Maven's capabilities during build execution. * * <p>This package contains classes for:</p> * <ul> diff --git a/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java b/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java index 979b4a523a..69c83a1714 100644 --- a/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java +++ b/compat/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java @@ -60,6 +60,7 @@ import org.apache.maven.Maven; import org.apache.maven.api.Constants; import org.apache.maven.api.cli.extensions.CoreExtension; +import org.apache.maven.api.cli.extensions.InputSource; import org.apache.maven.api.services.MessageBuilder; import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.building.FileSource; @@ -858,7 +859,9 @@ private List<CoreExtension> readCoreExtensionsDescriptor(String extensionsFile) Path extensionsPath = Path.of(extensionsFile); if (Files.exists(extensionsPath)) { try (InputStream is = Files.newInputStream(extensionsPath)) { - return new CoreExtensionsStaxReader().read(is, true).getExtensions(); + return new CoreExtensionsStaxReader() + .read(is, true, new InputSource(extensionsFile)) + .getExtensions(); } } } diff --git a/impl/maven-cli/pom.xml b/impl/maven-cli/pom.xml index cb68e12289..97076b1cd6 100644 --- a/impl/maven-cli/pom.xml +++ b/impl/maven-cli/pom.xml @@ -261,6 +261,7 @@ under the License. <params> <param>packageModelV4=org.apache.maven.api.cli.extensions</param> <param>packageToolV4=org.apache.maven.cling.internal.extension.io</param> + <param>locationTracking=true</param> </params> <velocityBasedir>${project.basedir}/../../src/mdo</velocityBasedir> </configuration> diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java index d0630453df..89dd0e0c0e 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/BootstrapCoreExtensionManager.java @@ -132,7 +132,7 @@ public BootstrapCoreExtensionManager( this.interpolator = interpolator; } - public List<CoreExtensionEntry> loadCoreExtensions( + public List<LoadedCoreExtension> loadCoreExtensions( MavenExecutionRequest request, Set<String> providedArtifacts, List<CoreExtension> extensions) throws Exception { try (CloseableSession repoSession = repositorySystemSessionFactory @@ -150,14 +150,14 @@ public List<CoreExtensionEntry> loadCoreExtensions( } } - private List<CoreExtensionEntry> resolveCoreExtensions( + private List<LoadedCoreExtension> resolveCoreExtensions( RepositorySystemSession repoSession, List<RemoteRepository> repositories, Set<String> providedArtifacts, List<CoreExtension> configuration, UnaryOperator<String> interpolator) throws Exception { - List<CoreExtensionEntry> extensions = new ArrayList<>(); + List<LoadedCoreExtension> extensions = new ArrayList<>(); DependencyFilter dependencyFilter = new ExclusionsDependencyFilter(providedArtifacts); @@ -165,7 +165,7 @@ private List<CoreExtensionEntry> resolveCoreExtensions( List<Artifact> artifacts = resolveExtension(extension, repoSession, repositories, dependencyFilter, interpolator); if (!artifacts.isEmpty()) { - extensions.add(createExtension(extension, artifacts)); + extensions.add(new LoadedCoreExtension(extension, createExtension(extension, artifacts))); } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/ContainerCapsuleFactory.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/LoadedCoreExtension.java similarity index 64% copy from impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/ContainerCapsuleFactory.java copy to impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/LoadedCoreExtension.java index 1953ad233c..6679b60e41 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/ContainerCapsuleFactory.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/extensions/LoadedCoreExtension.java @@ -16,19 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cling.invoker; +package org.apache.maven.cling.extensions; -import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.cli.extensions.CoreExtension; +import org.apache.maven.extension.internal.CoreExtensionEntry; /** - * Container capsule factory. - * - * @param <C> The context type. + * Represents a core extension that has been selected to be loaded. + * @param coreExtension The extension configuration entry with location tracking. + * @param entry The loaded entry with descriptor. */ -public interface ContainerCapsuleFactory<C extends LookupContext> { - /** - * Creates container capsule. - */ - @Nonnull - ContainerCapsule createContainerCapsule(LookupInvoker<C> invoker, C context) throws Exception; -} +public record LoadedCoreExtension(CoreExtension coreExtension, CoreExtensionEntry entry) {} 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 9cb49cb27d..caef621191 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 @@ -25,9 +25,9 @@ import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nullable; +import org.apache.maven.api.cli.CoreExtensions; import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.api.cli.ParserRequest; -import org.apache.maven.api.cli.extensions.CoreExtension; import static java.util.Objects.requireNonNull; @@ -41,7 +41,7 @@ public abstract class BaseInvokerRequest implements InvokerRequest { private final Map<String, String> systemProperties; private final Path topDirectory; private final Path rootDirectory; - private final List<CoreExtension> coreExtensions; + private final List<CoreExtensions> coreExtensions; @SuppressWarnings("ParameterNumber") public BaseInvokerRequest( @@ -54,7 +54,7 @@ public BaseInvokerRequest( @Nonnull Map<String, String> systemProperties, @Nonnull Path topDirectory, @Nullable Path rootDirectory, - @Nullable List<CoreExtension> coreExtensions) { + @Nullable List<CoreExtensions> coreExtensions) { this.parserRequest = requireNonNull(parserRequest); this.parsingFailed = parsingFailed; this.cwd = requireNonNull(cwd); @@ -114,7 +114,7 @@ public Optional<Path> rootDirectory() { } @Override - public Optional<List<CoreExtension>> coreExtensions() { + public Optional<List<CoreExtensions>> coreExtensions() { return Optional.ofNullable(coreExtensions); } } 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 7c2737be98..d7e7e869c1 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 @@ -34,14 +34,18 @@ import java.util.Objects; import java.util.Properties; import java.util.function.UnaryOperator; +import java.util.stream.Collectors; import org.apache.maven.api.Constants; import org.apache.maven.api.annotations.Nullable; +import org.apache.maven.api.cli.CoreExtensions; import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.api.cli.Options; import org.apache.maven.api.cli.Parser; import org.apache.maven.api.cli.ParserRequest; import org.apache.maven.api.cli.extensions.CoreExtension; +import org.apache.maven.api.cli.extensions.InputLocation; +import org.apache.maven.api.cli.extensions.InputSource; import org.apache.maven.api.services.Interpolator; import org.apache.maven.cling.internal.extension.io.CoreExtensionsStaxReader; import org.apache.maven.cling.props.MavenPropertiesLoader; @@ -79,7 +83,9 @@ public LocalContext(ParserRequest parserRequest) { @Nullable public Path rootDirectory; - public List<CoreExtension> extensions; + @Nullable + public List<CoreExtensions> extensions; + public Options options; public Map<String, String> extraInterpolationSource() { @@ -425,54 +431,48 @@ protected Map<String, String> populateUserProperties(LocalContext context) { protected abstract Options assembleOptions(List<Options> parsedOptions); - protected List<CoreExtension> readCoreExtensionsDescriptor(LocalContext context) { - String installationExtensionsFile = context.userProperties.get(Constants.MAVEN_INSTALLATION_EXTENSIONS); - ArrayList<CoreExtension> installationExtensions = new ArrayList<>(readCoreExtensionsDescriptorFromFile( - context.installationDirectory.resolve(installationExtensionsFile))); - - String userExtensionsFile = context.userProperties.get(Constants.MAVEN_USER_EXTENSIONS); - ArrayList<CoreExtension> userExtensions = new ArrayList<>( - readCoreExtensionsDescriptorFromFile(context.userHomeDirectory.resolve(userExtensionsFile))); - - String projectExtensionsFile = context.userProperties.get(Constants.MAVEN_PROJECT_EXTENSIONS); - ArrayList<CoreExtension> projectExtensions = - new ArrayList<>(readCoreExtensionsDescriptorFromFile(context.cwd.resolve(projectExtensionsFile))); - - // merge these 3 but check for GA uniqueness; we don't want to load up same extension w/ different Vs - HashMap<String, String> gas = new HashMap<>(); - ArrayList<String> conflicts = new ArrayList<>(); - - ArrayList<CoreExtension> coreExtensions = - new ArrayList<>(installationExtensions.size() + userExtensions.size() + projectExtensions.size()); - coreExtensions.addAll(mergeExtensions(installationExtensions, installationExtensionsFile, gas, conflicts)); - coreExtensions.addAll(mergeExtensions(userExtensions, userExtensionsFile, gas, conflicts)); - coreExtensions.addAll(mergeExtensions(projectExtensions, projectExtensionsFile, gas, conflicts)); - - if (!conflicts.isEmpty()) { - throw new IllegalStateException("Extension conflicts: " + String.join("; ", conflicts)); + /** + * Important: This method must return list of {@link CoreExtensions} in precedence order. + */ + protected List<CoreExtensions> readCoreExtensionsDescriptor(LocalContext context) { + ArrayList<CoreExtensions> result = new ArrayList<>(); + Path file; + List<CoreExtension> loaded; + + // project + file = context.cwd.resolve(context.userProperties.get(Constants.MAVEN_PROJECT_EXTENSIONS)); + loaded = readCoreExtensionsDescriptorFromFile(file); + if (!loaded.isEmpty()) { + result.add(new CoreExtensions(file, loaded)); } - return coreExtensions; - } + // user + file = context.userHomeDirectory.resolve(context.userProperties.get(Constants.MAVEN_USER_EXTENSIONS)); + loaded = readCoreExtensionsDescriptorFromFile(file); + if (!loaded.isEmpty()) { + result.add(new CoreExtensions(file, loaded)); + } - private List<CoreExtension> mergeExtensions( - List<CoreExtension> extensions, String extensionsSource, Map<String, String> gas, List<String> conflicts) { - for (CoreExtension extension : extensions) { - String ga = extension.getGroupId() + ":" + extension.getArtifactId(); - if (gas.containsKey(ga)) { - conflicts.add(ga + " from " + extensionsSource + " already specified in " + gas.get(ga)); - } else { - gas.put(ga, extensionsSource); - } + // installation + file = context.installationDirectory.resolve( + context.userProperties.get(Constants.MAVEN_INSTALLATION_EXTENSIONS)); + loaded = readCoreExtensionsDescriptorFromFile(file); + if (!loaded.isEmpty()) { + result.add(new CoreExtensions(file, loaded)); } - return extensions; + + return result.isEmpty() ? null : List.copyOf(result); } protected List<CoreExtension> readCoreExtensionsDescriptorFromFile(Path extensionsFile) { try { if (extensionsFile != null && Files.exists(extensionsFile)) { try (InputStream is = Files.newInputStream(extensionsFile)) { - return new CoreExtensionsStaxReader().read(is, true).getExtensions(); + return validateCoreExtensionsDescriptorFromFile( + extensionsFile, + List.copyOf(new CoreExtensionsStaxReader() + .read(is, true, new InputSource(extensionsFile.toString())) + .getExtensions())); } } return List.of(); @@ -480,4 +480,24 @@ protected List<CoreExtension> readCoreExtensionsDescriptorFromFile(Path extensio throw new IllegalArgumentException("Failed to parse extensions file: " + extensionsFile, e); } } + + protected List<CoreExtension> validateCoreExtensionsDescriptorFromFile( + Path extensionFile, List<CoreExtension> coreExtensions) { + Map<String, List<InputLocation>> gasLocations = new HashMap<>(); + for (CoreExtension coreExtension : coreExtensions) { + String ga = coreExtension.getGroupId() + ":" + coreExtension.getArtifactId(); + InputLocation location = coreExtension.getLocation(""); + gasLocations.computeIfAbsent(ga, k -> new ArrayList<>()).add(location); + } + if (gasLocations.values().stream().noneMatch(l -> l.size() > 1)) { + return coreExtensions; + } + throw new IllegalStateException("Extension conflicts in file " + extensionFile + ": " + + gasLocations.entrySet().stream() + .map(e -> e.getKey() + " defined on lines " + + e.getValue().stream() + .map(l -> String.valueOf(l.getLineNumber())) + .collect(Collectors.joining(", "))) + .collect(Collectors.joining("; "))); + } } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/ContainerCapsuleFactory.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/ContainerCapsuleFactory.java index 1953ad233c..e044f441ed 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/ContainerCapsuleFactory.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/ContainerCapsuleFactory.java @@ -30,5 +30,6 @@ public interface ContainerCapsuleFactory<C extends LookupContext> { * Creates container capsule. */ @Nonnull - ContainerCapsule createContainerCapsule(LookupInvoker<C> invoker, C context) throws Exception; + ContainerCapsule createContainerCapsule( + LookupInvoker<C> invoker, C context, CoreExtensionSelector<C> coreExtensionSelector) throws Exception; } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/ContainerCapsuleFactory.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CoreExtensionSelector.java similarity index 67% copy from impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/ContainerCapsuleFactory.java copy to impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CoreExtensionSelector.java index 1953ad233c..e45f12aeaf 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/ContainerCapsuleFactory.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/CoreExtensionSelector.java @@ -18,17 +18,21 @@ */ package org.apache.maven.cling.invoker; +import java.util.List; + import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.cli.InvokerRequest; +import org.apache.maven.api.cli.extensions.CoreExtension; /** - * Container capsule factory. + * Core extension selector: selects which entries to load from {@link InvokerRequest#coreExtensions()}. * * @param <C> The context type. */ -public interface ContainerCapsuleFactory<C extends LookupContext> { +public interface CoreExtensionSelector<C extends LookupContext> { /** - * Creates container capsule. + * Selects core extensions to be loaded from list of all sources detected. */ @Nonnull - ContainerCapsule createContainerCapsule(LookupInvoker<C> invoker, C context) throws Exception; + List<CoreExtension> selectCoreExtensions(LookupInvoker<C> invoker, C context); } 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 abd90ebe9f..ccdfa43700 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 @@ -503,7 +503,8 @@ protected void preCommands(C context) throws Exception { protected void container(C context) throws Exception { if (context.lookup == null) { - context.containerCapsule = createContainerCapsuleFactory().createContainerCapsule(this, context); + context.containerCapsule = createContainerCapsuleFactory() + .createContainerCapsule(this, context, createCoreExtensionSelector()); context.closeables.add(context::closeContainer); context.lookup = context.containerCapsule.getLookup(); } else { @@ -511,6 +512,10 @@ protected void container(C context) throws Exception { } } + protected CoreExtensionSelector<C> createCoreExtensionSelector() { + return new PrecedenceCoreExtensionSelector<>(); + } + protected ContainerCapsuleFactory<C> createContainerCapsuleFactory() { return new PlexusContainerCapsuleFactory<>(); } diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java index 98c8618872..15e4630c9a 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PlexusContainerCapsuleFactory.java @@ -31,13 +31,13 @@ import com.google.inject.Module; import org.apache.maven.api.Constants; import org.apache.maven.api.ProtoSession; -import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.api.cli.Logger; import org.apache.maven.api.cli.extensions.CoreExtension; import org.apache.maven.api.services.MessageBuilderFactory; import org.apache.maven.api.services.SettingsBuilder; import org.apache.maven.cling.extensions.BootstrapCoreExtensionManager; import org.apache.maven.cling.extensions.ExtensionConfigurationModule; +import org.apache.maven.cling.extensions.LoadedCoreExtension; import org.apache.maven.cling.logging.Slf4jLoggerManager; import org.apache.maven.di.Injector; import org.apache.maven.execution.DefaultMavenExecutionRequest; @@ -60,6 +60,7 @@ import org.codehaus.plexus.logging.LoggerManager; import org.slf4j.ILoggerFactory; +import static java.util.Objects.requireNonNull; import static org.apache.maven.cling.invoker.Utils.toPlexusLoggingLevel; /** @@ -69,20 +70,33 @@ */ public class PlexusContainerCapsuleFactory<C extends LookupContext> implements ContainerCapsuleFactory<C> { @Override - public ContainerCapsule createContainerCapsule(LookupInvoker<C> invoker, C context) throws Exception { + public ContainerCapsule createContainerCapsule( + LookupInvoker<C> invoker, C context, CoreExtensionSelector<C> coreExtensionSelector) throws Exception { + requireNonNull(invoker, "invoker"); + requireNonNull(context, "context"); + requireNonNull(coreExtensionSelector, "coreExtensionSelector"); return new PlexusContainerCapsule( - context, Thread.currentThread().getContextClassLoader(), container(invoker, context)); + context, + Thread.currentThread().getContextClassLoader(), + container(invoker, context, coreExtensionSelector)); } - protected DefaultPlexusContainer container(LookupInvoker<C> invoker, C context) throws Exception { - ClassWorld classWorld = invoker.protoLookup.lookup(ClassWorld.class); + protected DefaultPlexusContainer container( + LookupInvoker<C> invoker, C context, CoreExtensionSelector<C> coreExtensionSelector) throws Exception { + ClassWorld classWorld = requireNonNull(invoker.protoLookup.lookup(ClassWorld.class), "classWorld"); ClassRealm coreRealm = classWorld.getClassRealm("plexus.core"); List<Path> extClassPath = parseExtClasspath(context); CoreExtensionEntry coreEntry = CoreExtensionEntry.discoverFrom(coreRealm); - List<CoreExtensionEntry> extensions = - loadCoreExtensions(invoker, context, coreRealm, coreEntry.getExportedArtifacts()); + List<LoadedCoreExtension> loadedExtensions = loadCoreExtensions( + invoker, + context, + coreRealm, + coreEntry.getExportedArtifacts(), + coreExtensionSelector.selectCoreExtensions(invoker, context)); + List<CoreExtensionEntry> loadedExtensionsEntries = + loadedExtensions.stream().map(LoadedCoreExtension::entry).toList(); ClassRealm containerRealm = - setupContainerRealm(context.logger, classWorld, coreRealm, extClassPath, extensions); + setupContainerRealm(context.logger, classWorld, coreRealm, extClassPath, loadedExtensionsEntries); ContainerConfiguration cc = new DefaultContainerConfiguration() .setClassWorld(classWorld) .setRealm(containerRealm) @@ -95,8 +109,8 @@ protected DefaultPlexusContainer container(LookupInvoker<C> invoker, C context) CoreExports exports = new CoreExports( containerRealm, - collectExportedArtifacts(coreEntry, extensions), - collectExportedPackages(coreEntry, extensions)); + collectExportedArtifacts(coreEntry, loadedExtensionsEntries), + collectExportedPackages(coreEntry, loadedExtensionsEntries)); Thread.currentThread().setContextClassLoader(containerRealm); DefaultPlexusContainer container = new DefaultPlexusContainer(cc, getCustomModule(context, exports)); @@ -113,24 +127,35 @@ protected DefaultPlexusContainer container(LookupInvoker<C> invoker, C context) } return value; }; - for (CoreExtensionEntry extension : extensions) { + List<Throwable> failures = new ArrayList<>(); + for (LoadedCoreExtension extension : loadedExtensions) { container.discoverComponents( - extension.getClassRealm(), + extension.entry().getClassRealm(), new AbstractModule() { @Override protected void configure() { try { - container.lookup(Injector.class).discover(extension.getClassRealm()); + container + .lookup(Injector.class) + .discover(extension.entry().getClassRealm()); } catch (Throwable e) { - context.logger.warn("Maven DI failure", e); + failures.add(new IllegalStateException( + "Injection failure in " + + extension.coreExtension().getId(), + e)); } } }, new SessionScopeModule(container.lookup(SessionScope.class)), new MojoExecutionScopeModule(container.lookup(MojoExecutionScope.class)), - new ExtensionConfigurationModule(extension, extensionSource)); + new ExtensionConfigurationModule(extension.entry(), extensionSource)); + } + if (!failures.isEmpty()) { + IllegalStateException mavenDiFailed = new IllegalStateException( + "Maven dependency injection failed for at least one of the registered core extension"); + failures.forEach(mavenDiFailed::addSuppressed); + throw mavenDiFailed; } - container.getLoggerManager().setThresholds(toPlexusLoggingLevel(context.loggerLevel)); customizeContainer(context, container); @@ -241,16 +266,16 @@ protected ClassRealm setupContainerRealm( return coreRealm; } - protected List<CoreExtensionEntry> loadCoreExtensions( - LookupInvoker<C> invoker, C context, ClassRealm containerRealm, Set<String> providedArtifacts) + protected List<LoadedCoreExtension> loadCoreExtensions( + LookupInvoker<C> invoker, + C context, + ClassRealm containerRealm, + Set<String> providedArtifacts, + List<CoreExtension> extensions) throws Exception { - InvokerRequest invokerRequest = context.invokerRequest; - if (invokerRequest.coreExtensions().isEmpty() - || invokerRequest.coreExtensions().get().isEmpty()) { - return Collections.emptyList(); + if (extensions.isEmpty()) { + return List.of(); } - - List<CoreExtension> extensions = invokerRequest.coreExtensions().get(); ContainerConfiguration cc = new DefaultContainerConfiguration() .setClassWorld(containerRealm.getWorld()) .setRealm(containerRealm) diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java new file mode 100644 index 0000000000..6edfde98ee --- /dev/null +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/PrecedenceCoreExtensionSelector.java @@ -0,0 +1,96 @@ +/* + * 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.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Optional; + +import org.apache.maven.api.cli.CoreExtensions; +import org.apache.maven.api.cli.InvokerRequest; +import org.apache.maven.api.cli.extensions.CoreExtension; +import org.apache.maven.api.cli.extensions.InputLocation; + +public class PrecedenceCoreExtensionSelector<C extends LookupContext> implements CoreExtensionSelector<C> { + @Override + public List<CoreExtension> selectCoreExtensions(LookupInvoker<C> invoker, C context) { + Optional<List<CoreExtensions>> coreExtensions = context.invokerRequest.coreExtensions(); + if (coreExtensions.isEmpty() || coreExtensions.get().isEmpty()) { + return List.of(); + } + + return selectCoreExtensions( + context, context.invokerRequest.coreExtensions().orElseThrow()); + } + + /** + * Selects extensions to load discovered from various sources by precedence ("first wins"), as + * {@link InvokerRequest#coreExtensions()} is in precedence order. Also reports conflicts, if any. + * Finally, at DEBUG level reports configured vs selected extensions. + */ + protected List<CoreExtension> selectCoreExtensions(C context, List<CoreExtensions> configuredCoreExtensions) { + context.logger.debug("Configured core extensions (in precedence order):"); + for (CoreExtensions source : configuredCoreExtensions) { + context.logger.debug("* Source file: " + source.source()); + for (CoreExtension extension : source.coreExtensions()) { + context.logger.debug(" - " + extension.getId() + " -> " + formatLocation(extension.getLocation(""))); + } + } + + LinkedHashMap<String, CoreExtension> selectedExtensions = new LinkedHashMap<>(); + List<String> conflicts = new ArrayList<>(); + for (CoreExtensions coreExtensions : configuredCoreExtensions) { + for (CoreExtension coreExtension : coreExtensions.coreExtensions()) { + String key = coreExtension.getGroupId() + ":" + coreExtension.getArtifactId(); + CoreExtension conflict = selectedExtensions.putIfAbsent(key, coreExtension); + if (conflict != null) { + conflicts.add(String.format( + "Conflicting extension %s: %s vs %s", + key, + formatLocation(conflict.getLocation("")), + formatLocation(coreExtension.getLocation("")))); + } + } + } + if (!conflicts.isEmpty()) { + context.logger.warn("Found " + conflicts.size() + " extension conflict(s):"); + for (String conflict : conflicts) { + context.logger.warn("* " + conflict); + } + context.logger.warn(""); + context.logger.warn( + "Order of core extensions precedence is project > user > installation. Selected extensions are:"); + for (CoreExtension extension : selectedExtensions.values()) { + context.logger.warn( + "* " + extension.getId() + " configured in " + formatLocation(extension.getLocation(""))); + } + } + + context.logger.debug("Selected core extensions (in loading order):"); + for (CoreExtension source : selectedExtensions.values()) { + context.logger.debug("* " + source.getId() + ": " + formatLocation(source.getLocation(""))); + } + return List.copyOf(selectedExtensions.values()); + } + + protected String formatLocation(InputLocation location) { + return location.getSource().getLocation() + ":" + location.getLineNumber(); + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java index efd5e9ecec..2df07aec03 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/MavenInvokerRequest.java @@ -23,8 +23,8 @@ import java.util.Map; import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.cli.CoreExtensions; import org.apache.maven.api.cli.ParserRequest; -import org.apache.maven.api.cli.extensions.CoreExtension; import org.apache.maven.api.cli.mvn.MavenOptions; import org.apache.maven.cling.invoker.BaseInvokerRequest; @@ -47,7 +47,7 @@ public MavenInvokerRequest( Map<String, String> systemProperties, Path topDirectory, Path rootDirectory, - List<CoreExtension> coreExtensions, + List<CoreExtensions> coreExtensions, MavenOptions options) { super( parserRequest, diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java index 2b2900b598..f2847b624f 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/EncryptInvokerRequest.java @@ -23,8 +23,8 @@ import java.util.Map; import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.cli.CoreExtensions; import org.apache.maven.api.cli.ParserRequest; -import org.apache.maven.api.cli.extensions.CoreExtension; import org.apache.maven.api.cli.mvnenc.EncryptOptions; import org.apache.maven.cling.invoker.BaseInvokerRequest; @@ -44,7 +44,7 @@ public EncryptInvokerRequest( Map<String, String> systemProperties, Path topDirectory, Path rootDirectory, - List<CoreExtension> coreExtensions, + List<CoreExtensions> coreExtensions, EncryptOptions options) { super( parserRequest, diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java index 9268b9ed30..5a841a9658 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnsh/ShellInvokerRequest.java @@ -23,8 +23,8 @@ import java.util.Map; import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.cli.CoreExtensions; import org.apache.maven.api.cli.ParserRequest; -import org.apache.maven.api.cli.extensions.CoreExtension; import org.apache.maven.api.cli.mvnsh.ShellOptions; import org.apache.maven.cling.invoker.BaseInvokerRequest; @@ -44,7 +44,7 @@ public ShellInvokerRequest( Map<String, String> systemProperties, Path topDirectory, Path rootDirectory, - List<CoreExtension> coreExtensions, + List<CoreExtensions> coreExtensions, ShellOptions options) { super( parserRequest, diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java index 9bc896c030..de8b4fdf7b 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTest.java @@ -37,6 +37,7 @@ import org.junit.jupiter.api.io.CleanupMode; import org.junit.jupiter.api.io.TempDir; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -47,10 +48,9 @@ @Order(200) public class MavenInvokerTest extends MavenInvokerTestSupport { @Override - protected Invoker createInvoker() { - return new MavenInvoker(ProtoLookup.builder() - .addMapping(ClassWorld.class, new ClassWorld("plexus.core", ClassLoader.getSystemClassLoader())) - .build()); + protected Invoker createInvoker(ClassWorld classWorld) { + return new MavenInvoker( + ProtoLookup.builder().addMapping(ClassWorld.class, classWorld).build()); } @Override @@ -66,8 +66,59 @@ void defaultFs( invoke(cwd, userHome, List.of("verify"), List.of()); } + /** + * Same source (user or project extensions.xml) must not contain same GA with different V. + */ @Test - void conflictingExtensions( + void conflictingExtensionsFromSameSource( + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd, + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome) + throws Exception { + String projectExtensionsXml = + """ + <?xml version="1.0" encoding="UTF-8"?> + <extensions> + <extension> + <groupId>io.takari.maven</groupId> + <artifactId>takari-smart-builder</artifactId> + <version>1.0.2</version> + </extension> + <extension> + <groupId>io.takari.maven</groupId> + <artifactId>takari-smart-builder</artifactId> + <version>1.0.1</version> + </extension> + </extensions> + """; + Path dotMvn = cwd.resolve(".mvn"); + Files.createDirectories(dotMvn); + Path projectExtensions = dotMvn.resolve("extensions.xml"); + Files.writeString(projectExtensions, projectExtensionsXml); + + String userExtensionsXml = + """ + <?xml version="1.0" encoding="UTF-8"?> + <extensions> + <extension> + <groupId>io.takari.maven</groupId> + <artifactId>takari-smart-builder</artifactId> + <version>1.0.2</version> + </extension> + </extensions> + """; + Path userConf = userHome.resolve(".m2"); + Files.createDirectories(userConf); + Path userExtensions = userConf.resolve("extensions.xml"); + Files.writeString(userExtensions, userExtensionsXml); + + assertThrows(InvokerException.class, () -> invoke(cwd, userHome, List.of("validate"), List.of())); + } + + /** + * In case of conflict spanning different sources, precedence is applied: project > user > installation. + */ + @Test + void conflictingExtensionsFromDifferentSource( @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path cwd, @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path userHome) throws Exception { @@ -76,9 +127,9 @@ void conflictingExtensions( <?xml version="1.0" encoding="UTF-8"?> <extensions> <extension> - <groupId>eu.maveniverse.maven.mimir</groupId> - <artifactId>extension3</artifactId> - <version>0.3.4</version> + <groupId>io.takari.maven</groupId> + <artifactId>takari-smart-builder</artifactId> + <version>1.0.2</version> </extension> </extensions> """; @@ -92,7 +143,19 @@ void conflictingExtensions( Path userExtensions = userConf.resolve("extensions.xml"); Files.writeString(userExtensions, extensionsXml); - assertThrows(InvokerException.class, () -> invoke(cwd, userHome, List.of("validate"), List.of())); + // this should not throw + assertDoesNotThrow(() -> invoke(cwd, userHome, List.of("validate"), List.of())); + // but warn + + // [main] WARNING org.apache.maven.cling.invoker.PlexusContainerCapsuleFactory - Found 1 extension conflict(s): + // [main] WARNING org.apache.maven.cling.invoker.PlexusContainerCapsuleFactory - * Conflicting extension + // eu.maveniverse.maven.mimir:extension3: /tmp/junit-191051426131307150/.mvn/extensions.xml:3 vs + // /tmp/junit-16591192886395443631/.m2/extensions.xml:3 + // [main] WARNING org.apache.maven.cling.invoker.PlexusContainerCapsuleFactory - + // [main] WARNING org.apache.maven.cling.invoker.PlexusContainerCapsuleFactory - Order of core extensions + // precedence is project > user > installation. Selected extensions are: + // [main] WARNING org.apache.maven.cling.invoker.PlexusContainerCapsuleFactory - * + // eu.maveniverse.maven.mimir:extension3:0.3.4 configured in /tmp/junit-191051426131307150/.mvn/extensions.xml:3 } @Test 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 7f2db7c3ee..ba9246c639 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 @@ -32,6 +32,7 @@ import org.apache.maven.api.cli.Parser; import org.apache.maven.api.cli.ParserRequest; import org.apache.maven.jline.JLineMessageBuilderFactory; +import org.codehaus.plexus.classworlds.ClassWorld; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -99,7 +100,8 @@ protected Map<String, String> invoke(Path cwd, Path userHome, Collection<String> HashMap<String, String> logs = new HashMap<>(); Parser parser = createParser(); - try (Invoker invoker = createInvoker()) { + try (ClassWorld classWorld = createClassWorld(); + Invoker invoker = createInvoker(classWorld)) { for (String goal : goals) { ByteArrayOutputStream stdout = new ByteArrayOutputStream(); ByteArrayOutputStream stderr = new ByteArrayOutputStream(); @@ -134,7 +136,11 @@ protected Map<String, String> invoke(Path cwd, Path userHome, Collection<String> return logs; } - protected abstract Invoker createInvoker(); + protected ClassWorld createClassWorld() { + return new ClassWorld("plexus.core", ClassLoader.getSystemClassLoader()); + } + + protected abstract Invoker createInvoker(ClassWorld classWorld); protected abstract Parser createParser(); } diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java index 7ecbcee663..8636316280 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/resident/ResidentMavenInvokerTest.java @@ -43,10 +43,9 @@ public class ResidentMavenInvokerTest extends MavenInvokerTestSupport { @Override - protected Invoker createInvoker() { - return new ResidentMavenInvoker(ProtoLookup.builder() - .addMapping(ClassWorld.class, new ClassWorld("plexus.core", ClassLoader.getSystemClassLoader())) - .build()); + protected Invoker createInvoker(ClassWorld classWorld) { + return new ResidentMavenInvoker( + ProtoLookup.builder().addMapping(ClassWorld.class, classWorld).build()); } @Override