From cf30a3c091fb21b67ff2fbe14afad04b831be5bf Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 28 Nov 2023 18:28:40 +0100 Subject: [PATCH] Upgrade to Maven 4.0.0-alpha-9 --- maven-plugin-testing-harness/pom.xml | 16 +- .../maven/api/plugin/testing/Basedir.java | 32 ++ .../maven/api/plugin/testing/InjectMojo.java | 6 +- .../api/plugin/testing/MojoExtension.java | 461 +++++++++++++----- .../api/plugin/testing/MojoParameter.java | 2 + .../api/plugin/testing/MojoParameters.java | 2 + .../testing/stubs/MojoExecutionStub.java | 67 ++- .../api/plugin/testing/stubs/PluginStub.java | 102 ++++ .../api/plugin/testing/stubs/SessionStub.java | 55 ++- .../testing/ExpressionEvaluatorTest.java | 28 +- pom.xml | 4 +- 11 files changed, 616 insertions(+), 159 deletions(-) create mode 100644 maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/Basedir.java create mode 100644 maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/PluginStub.java diff --git a/maven-plugin-testing-harness/pom.xml b/maven-plugin-testing-harness/pom.xml index beaa39d..90026f3 100644 --- a/maven-plugin-testing-harness/pom.xml +++ b/maven-plugin-testing-harness/pom.xml @@ -74,6 +74,11 @@ under the License. ${mavenVersion} provided + + org.apache.maven + maven-xml-impl + ${mavenVersion} + @@ -85,7 +90,7 @@ under the License. org.codehaus.plexus plexus-xml - 4.0.0 + 4.0.3-SNAPSHOT true @@ -104,8 +109,17 @@ under the License. org.junit.jupiter junit-jupiter-api + + com.google.inject + guice + + + com.google.inject + guice + 6.0.0 + com.google.guava diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/Basedir.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/Basedir.java new file mode 100644 index 0000000..69eaba2 --- /dev/null +++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/Basedir.java @@ -0,0 +1,32 @@ +/* + * 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.plugin.testing; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Mojo parameters container + */ +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface Basedir { + String value() default ""; +} diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/InjectMojo.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/InjectMojo.java index e094d06..272eb9b 100644 --- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/InjectMojo.java +++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/InjectMojo.java @@ -18,6 +18,7 @@ */ package org.apache.maven.api.plugin.testing; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -25,11 +26,10 @@ * */ @Retention(RetentionPolicy.RUNTIME) +@Inherited public @interface InjectMojo { String goal(); - String pom(); - - boolean empty() default false; + String pom() default ""; } diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java index b217fab..224d587 100644 --- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java +++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoExtension.java @@ -20,44 +20,62 @@ import java.io.BufferedReader; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; +import java.lang.annotation.Annotation; import java.lang.reflect.AccessibleObject; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.net.URL; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import com.google.common.reflect.ClassPath; +import com.google.inject.Binding; +import com.google.inject.Injector; +import com.google.inject.Provider; +import com.google.inject.Scope; +import com.google.inject.Scopes; import com.google.inject.internal.ProviderMethodsModule; +import com.google.inject.spi.DefaultBindingScopingVisitor; +import com.google.inject.spi.LinkedKeyBinding; import org.apache.maven.api.MojoExecution; import org.apache.maven.api.Project; +import org.apache.maven.api.Service; import org.apache.maven.api.Session; +import org.apache.maven.api.model.Build; +import org.apache.maven.api.model.ConfigurationContainer; +import org.apache.maven.api.model.Model; import org.apache.maven.api.plugin.Log; import org.apache.maven.api.plugin.Mojo; +import org.apache.maven.api.plugin.testing.stubs.ArtifactStub; +import org.apache.maven.api.plugin.testing.stubs.MojoExecutionStub; +import org.apache.maven.api.plugin.testing.stubs.PluginStub; +import org.apache.maven.api.plugin.testing.stubs.ProjectStub; +import org.apache.maven.api.plugin.testing.stubs.SessionStub; import org.apache.maven.api.xml.XmlNode; import org.apache.maven.configuration.internal.EnhancedComponentConfigurator; +import org.apache.maven.execution.scope.internal.MojoExecutionScope; import org.apache.maven.internal.impl.DefaultLog; +import org.apache.maven.internal.impl.InternalSession; import org.apache.maven.internal.xml.XmlNodeImpl; +import org.apache.maven.internal.xml.XmlPlexusConfiguration; import org.apache.maven.lifecycle.internal.MojoDescriptorCreator; +import org.apache.maven.model.v4.MavenMerger; +import org.apache.maven.model.v4.MavenStaxReader; import org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.descriptor.Parameter; import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder; +import org.apache.maven.session.scope.internal.SessionScope; +import org.codehaus.plexus.ContainerConfiguration; import org.codehaus.plexus.DefaultPlexusContainer; import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.component.configurator.ComponentConfigurator; @@ -66,18 +84,24 @@ import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator; import org.codehaus.plexus.component.repository.ComponentDescriptor; import org.codehaus.plexus.component.repository.exception.ComponentLookupException; -import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration; import org.codehaus.plexus.testing.PlexusExtension; +import org.codehaus.plexus.util.IOUtil; import org.codehaus.plexus.util.InterpolationFilterReader; -import org.codehaus.plexus.util.ReaderFactory; import org.codehaus.plexus.util.ReflectionUtils; import org.codehaus.plexus.util.xml.XmlStreamReader; import org.codehaus.plexus.util.xml.Xpp3Dom; import org.codehaus.plexus.util.xml.Xpp3DomBuilder; +import org.eclipse.sisu.plexus.Hints; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.ParameterContext; import org.junit.jupiter.api.extension.ParameterResolutionException; import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.support.AnnotationSupport; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.TypePath; import org.slf4j.LoggerFactory; /** @@ -90,6 +114,23 @@ */ public class MojoExtension extends PlexusExtension implements ParameterResolver { + static String pluginBasedir; + static String basedir; + + public static String getBasedir() { + return basedir; + } + + public static String getPluginBasedir() { + return pluginBasedir; + } + + @Override + protected void customizeContainerConfiguration(ContainerConfiguration containerConfiguration) { + super.customizeContainerConfiguration(containerConfiguration); + containerConfiguration.setJSR250Lifecycle(true); + } + @Override public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { @@ -101,38 +142,129 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { try { - InjectMojo injectMojo = parameterContext - .findAnnotation(InjectMojo.class) - .orElseGet(() -> parameterContext.getDeclaringExecutable().getAnnotation(InjectMojo.class)); - - Set mojoParameters = - new HashSet<>(parameterContext.findRepeatableAnnotations(MojoParameter.class)); - - Optional.ofNullable(parameterContext.getDeclaringExecutable().getAnnotation(MojoParameter.class)) - .ifPresent(mojoParameters::add); - - Optional.ofNullable(parameterContext.getDeclaringExecutable().getAnnotation(MojoParameters.class)) - .map(MojoParameters::value) - .map(Arrays::asList) - .ifPresent(mojoParameters::addAll); - Class holder = parameterContext.getTarget().get().getClass(); PluginDescriptor descriptor = extensionContext .getStore(ExtensionContext.Namespace.GLOBAL) .get(PluginDescriptor.class, PluginDescriptor.class); - return lookupMojo(holder, injectMojo, mojoParameters, descriptor); + Model model = + extensionContext.getStore(ExtensionContext.Namespace.GLOBAL).get(Model.class, Model.class); + InjectMojo parameterInjectMojo = + parameterContext.getAnnotatedElement().getAnnotation(InjectMojo.class); + String goal; + if (parameterInjectMojo != null) { + String pom = parameterInjectMojo.pom(); + if (pom != null && !pom.isEmpty()) { + try (Reader r = openPomUrl(holder, pom)) { + Model localModel = new MavenStaxReader().read(r); + model = new MavenMerger().merge(localModel, model, false, null); + } + } + goal = parameterInjectMojo.goal(); + } else { + InjectMojo methodInjectMojo = AnnotationSupport.findAnnotation( + parameterContext.getDeclaringExecutable(), InjectMojo.class) + .orElse(null); + if (methodInjectMojo != null) { + goal = methodInjectMojo.goal(); + } else { + Class cl = parameterContext.getParameter().getType(); + byte[] data; + try (InputStream is = + cl.getClassLoader().getResourceAsStream(cl.getName().replace('.', '/') + ".class")) { + data = IOUtil.toByteArray(is); + } + List goals = new ArrayList<>(); + new ClassReader(data) + .accept( + new ClassVisitor(Opcodes.ASM9) { + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return new AnnotationVisitor(Opcodes.ASM9) { + @Override + public void visit(String name, Object value) { + if ("Lorg/apache/maven/api/plugin/annotations/Mojo;" + .equals(descriptor) + && "name".equals(name)) { + goals.add((String) value); + } + } + }; + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + int typeRef, TypePath typePath, String descriptor, boolean visible) { + return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible); + } + }, + ClassReader.SKIP_CODE); + goal = goals.iterator().next(); + } + } + Set mojoParameters = new LinkedHashSet<>(); + for (AnnotatedElement ae : + Arrays.asList(parameterContext.getDeclaringExecutable(), parameterContext.getAnnotatedElement())) { + mojoParameters.addAll(AnnotationSupport.findRepeatableAnnotations(ae, MojoParameter.class)); + } + String[] coord = mojoCoordinates(goal); + + XmlNode pluginConfiguration = model.getBuild().getPlugins().stream() + .filter(p -> + Objects.equals(p.getGroupId(), coord[0]) && Objects.equals(p.getArtifactId(), coord[1])) + .map(ConfigurationContainer::getConfiguration) + .findFirst() + .orElseGet(() -> new XmlNodeImpl("config")); + List children = mojoParameters.stream() + .map(mp -> new XmlNodeImpl(mp.name(), mp.value())) + .collect(Collectors.toList()); + XmlNode config = new XmlNodeImpl("configuration", null, null, children, null); + pluginConfiguration = XmlNode.merge(config, pluginConfiguration); + + // load default config + // pluginkey = groupId : artifactId : version : goal + Mojo mojo = lookup(Mojo.class, coord[0] + ":" + coord[1] + ":" + coord[2] + ":" + coord[3]); + for (MojoDescriptor mojoDescriptor : descriptor.getMojos()) { + if (Objects.equals( + mojoDescriptor.getImplementation(), mojo.getClass().getName())) { + if (pluginConfiguration != null) { + pluginConfiguration = finalizeConfig(pluginConfiguration, mojoDescriptor); + } + } + } + + Session session = getContainer().lookup(Session.class); + Project project = getContainer().lookup(Project.class); + MojoExecution mojoExecution = getContainer().lookup(MojoExecution.class); + ExpressionEvaluator evaluator = new WrapEvaluator( + getContainer(), new PluginParameterExpressionEvaluatorV4(session, project, mojoExecution)); + ComponentConfigurator configurator = new EnhancedComponentConfigurator(); + configurator.configureComponent( + mojo, + new XmlPlexusConfiguration(pluginConfiguration), + evaluator, + getContainer().getContainerRealm()); + return mojo; } catch (Exception e) { - throw new ParameterResolutionException("Unable to resolve parameter", e); + throw new ParameterResolutionException("Unable to resolve mojo", e); } } @Override public void beforeEach(ExtensionContext context) throws Exception { + if (pluginBasedir == null) { + pluginBasedir = PlexusExtension.getBasedir(); + } + basedir = AnnotationSupport.findAnnotation(context.getElement().get(), Basedir.class) + .map(Basedir::value) + .orElse(pluginBasedir); + if (basedir != null) { + basedir = basedir.replace("${basedir}", pluginBasedir); + } + // TODO provide protected setters in PlexusExtension - Field field = PlexusExtension.class.getDeclaredField("basedir"); - field.setAccessible(true); - field.set(null, getBasedir()); - field = PlexusExtension.class.getDeclaredField("context"); + // super.beforeEach(context); + + Field field = PlexusExtension.class.getDeclaredField("context"); field.setAccessible(true); field.set(this, context); @@ -142,74 +274,173 @@ public void beforeEach(ExtensionContext context) throws Exception { binder.install(ProviderMethodsModule.forObject(context.getRequiredTestInstance())); binder.requestInjection(context.getRequiredTestInstance()); binder.bind(Log.class).toInstance(new DefaultLog(LoggerFactory.getLogger("anonymous"))); + binder.bind(ExtensionContext.class).toInstance(context); + try { + for (ClassPath.ClassInfo clazz : + ClassPath.from(getClassLoader()).getAllClasses()) { + if ("org.apache.maven.api.services".equals(clazz.getPackageName())) { + Class load = clazz.load(); + if (Service.class.isAssignableFrom(load)) { + Class svc = (Class) load; + binder.bind(svc).toProvider((Provider) () -> { + try { + return getContainer() + .lookup(InternalSession.class) + .getService(svc); + } catch (ComponentLookupException e) { + throw new RuntimeException("Unable to lookup service " + svc.getName()); + } + }); + } + } + } + } catch (Exception e) { + throw new RuntimeException("Unable to bind session services", e); + } }); - Map map = getContainer().getContext().getContextData(); + // session + InternalSession s = null; + try { + Injector injector = getContainer().lookup(Injector.class); + if (injector.getBinding(InternalSession.class) != null) { + s = injector.getInstance(InternalSession.class); + } + } catch (ComponentLookupException | com.google.inject.ConfigurationException e) { + s = null; + } + if (s == null) { + s = SessionStub.getMockSession(MojoExtension.getBasedir()); + getContainer().addComponent(s, InternalSession.class, Hints.DEFAULT_HINT); + } - ClassLoader classLoader = context.getRequiredTestClass().getClassLoader(); - try (InputStream is = Objects.requireNonNull( - classLoader.getResourceAsStream(getPluginDescriptorLocation()), - "Unable to find plugin descriptor: " + getPluginDescriptorLocation()); - Reader reader = new BufferedReader(new XmlStreamReader(is)); - InterpolationFilterReader interpolationReader = new InterpolationFilterReader(reader, map, "${", "}")) { + Path basedirPath = Paths.get(getBasedir()); + + InjectMojo mojo = AnnotationSupport.findAnnotation(context.getElement().get(), InjectMojo.class) + .orElse(null); + Model defaultModel = Model.newBuilder() + .groupId("myGroupId") + .artifactId("myArtifactId") + .version("1.0-SNAPSHOT") + .packaging("jar") + .build(Build.newBuilder() + .directory(basedirPath.resolve("target").toString()) + .outputDirectory(basedirPath.resolve("target/classes").toString()) + .sourceDirectory(basedirPath.resolve("src/main/java").toString()) + .testOutputDirectory( + basedirPath.resolve("target/test-classes").toString()) + .build()) + .build(); + Model model = null; + if (mojo != null) { + String pom = mojo.pom(); + if (pom != null && !pom.isEmpty()) { + try (Reader r = openPomUrl(context.getRequiredTestClass(), pom)) { + model = new MavenStaxReader().read(r); + } + } else if (Files.exists(basedirPath.resolve("pom.xml"))) { + try (Reader r = Files.newBufferedReader(basedirPath.resolve("pom.xml"))) { + model = new MavenStaxReader().read(r); + } + } + } + if (model == null) { + model = defaultModel; + } else { + model = new MavenMerger().merge(model, defaultModel, false, null); + } + context.getStore(ExtensionContext.Namespace.GLOBAL).put(Model.class, model); - PluginDescriptor pluginDescriptor = new PluginDescriptorBuilder().build(interpolationReader); + // project + Project p; + try { + p = getContainer().lookup(Project.class); + } catch (ComponentLookupException e) { + ProjectStub stub = new ProjectStub(); + ArtifactStub artifact = new ArtifactStub( + model.getGroupId(), model.getArtifactId(), null, model.getVersion(), model.getPackaging()); + stub.setArtifact(artifact); + stub.setModel(model); + stub.setBasedir(Paths.get(MojoExtension.getBasedir())); + p = stub; + getContainer().addComponent(p, Project.class, Hints.DEFAULT_HINT); + } - context.getStore(ExtensionContext.Namespace.GLOBAL).put(PluginDescriptor.class, pluginDescriptor); + // mojo execution + Map map = getContainer().getContext().getContextData(); + ClassLoader classLoader = context.getRequiredTestClass().getClassLoader(); + PluginDescriptor pluginDescriptor = new PluginDescriptorBuilder().build(() -> { + InputStream is = Objects.requireNonNull( + classLoader.getResourceAsStream(getPluginDescriptorLocation()), + "Unable to find plugin descriptor: " + getPluginDescriptorLocation()); + Reader reader = new BufferedReader(new XmlStreamReader(is)); + return new InterpolationFilterReader(reader, map, "${", "}"); + }); + context.getStore(ExtensionContext.Namespace.GLOBAL).put(PluginDescriptor.class, pluginDescriptor); + for (ComponentDescriptor desc : pluginDescriptor.getComponents()) { + getContainer().addComponentDescriptor(desc); + } - for (ComponentDescriptor desc : pluginDescriptor.getComponents()) { - getContainer().addComponentDescriptor(desc); + MojoExecution me; + try { + me = getContainer().lookup(MojoExecution.class); + } catch (ComponentLookupException e) { + MojoExecutionStub mes = new MojoExecutionStub("executionId", null); + if (mojo != null) { + String goal = mojo.goal(); + int idx = goal.lastIndexOf(':'); + if (idx >= 0) { + goal = goal.substring(idx + 1); + } + mes.setGoal(goal); + MojoDescriptor mojoDescriptor = pluginDescriptor.getMojo(goal); + mes.setDescriptor(mojoDescriptor != null ? mojoDescriptor.getMojoDescriptorV4() : null); } + PluginStub plugin = new PluginStub(); + plugin.setDescriptor(pluginDescriptor.getPluginDescriptorV4()); + mes.setPlugin(plugin); + me = mes; } - } - protected String getPluginDescriptorLocation() { - return "META-INF/maven/plugin.xml"; + SessionScope sessionScope = getContainer().lookup(SessionScope.class); + sessionScope.enter(); + sessionScope.seed(Session.class, s); + sessionScope.seed(InternalSession.class, s); + + MojoExecutionScope mojoExecutionScope = getContainer().lookup(MojoExecutionScope.class); + mojoExecutionScope.enter(); + mojoExecutionScope.seed(Project.class, p); + mojoExecutionScope.seed(MojoExecution.class, me); } - private Mojo lookupMojo( - Class holder, - InjectMojo injectMojo, - Collection mojoParameters, - PluginDescriptor descriptor) - throws Exception { - String goal = injectMojo.goal(); - String pom = injectMojo.pom(); - String[] coord = mojoCoordinates(goal); - Xpp3Dom pomDom; + private Reader openPomUrl(Class holder, String pom) throws IOException { if (pom.startsWith("file:")) { Path path = Paths.get(getBasedir()).resolve(pom.substring("file:".length())); - pomDom = Xpp3DomBuilder.build(ReaderFactory.newXmlReader(path.toFile())); + return Files.newBufferedReader(path); } else if (pom.startsWith("classpath:")) { URL url = holder.getResource(pom.substring("classpath:".length())); if (url == null) { throw new IllegalStateException("Unable to find pom on classpath: " + pom); } - pomDom = Xpp3DomBuilder.build(ReaderFactory.newXmlReader(url.openStream())); + return new XmlStreamReader(url.openStream()); } else if (pom.contains("")) { - pomDom = Xpp3DomBuilder.build(new StringReader(pom)); + return new StringReader(pom); } else { Path path = Paths.get(getBasedir()).resolve(pom); - pomDom = Xpp3DomBuilder.build(ReaderFactory.newXmlReader(path.toFile())); - } - XmlNode pluginConfiguration = extractPluginConfiguration(coord[1], pomDom); - if (!mojoParameters.isEmpty()) { - List children = mojoParameters.stream() - .map(mp -> new XmlNodeImpl(mp.name(), mp.value())) - .collect(Collectors.toList()); - XmlNode config = new XmlNodeImpl("configuration", null, null, children, null); - pluginConfiguration = XmlNode.merge(config, pluginConfiguration); + return Files.newBufferedReader(path); } - Mojo mojo = lookupMojo(coord, pluginConfiguration, descriptor); - return mojo; + } + + protected String getPluginDescriptorLocation() { + return "META-INF/maven/plugin.xml"; } protected String[] mojoCoordinates(String goal) throws Exception { if (goal.matches(".*:.*:.*:.*")) { return goal.split(":"); } else { - Path pluginPom = Paths.get(getBasedir(), "pom.xml"); - Xpp3Dom pluginPomDom = Xpp3DomBuilder.build(ReaderFactory.newXmlReader(pluginPom.toFile())); + Path pluginPom = Paths.get(getPluginBasedir(), "pom.xml"); + Xpp3Dom pluginPomDom = Xpp3DomBuilder.build(Files.newBufferedReader(pluginPom)); String artifactId = pluginPomDom.getChild("artifactId").getValue(); String groupId = resolveFromRootThenParent(pluginPomDom, "groupId"); String version = resolveFromRootThenParent(pluginPomDom, "version"); @@ -217,55 +448,16 @@ protected String[] mojoCoordinates(String goal) throws Exception { } } - /** - * lookup the mojo while we have all the relevent information - */ - protected Mojo lookupMojo(String[] coord, XmlNode pluginConfiguration, PluginDescriptor descriptor) - throws Exception { - // pluginkey = groupId : artifactId : version : goal - Mojo mojo = lookup(Mojo.class, coord[0] + ":" + coord[1] + ":" + coord[2] + ":" + coord[3]); - for (MojoDescriptor mojoDescriptor : descriptor.getMojos()) { - if (Objects.equals( - mojoDescriptor.getImplementation(), mojo.getClass().getName())) { - if (pluginConfiguration != null) { - pluginConfiguration = finalizeConfig(pluginConfiguration, mojoDescriptor); - } - } - } - if (pluginConfiguration != null) { - Session session = getContainer().lookup(Session.class); - Project project; - try { - project = getContainer().lookup(Project.class); - } catch (ComponentLookupException e) { - project = null; - } - org.apache.maven.plugin.MojoExecution mojoExecution; - try { - MojoExecution me = getContainer().lookup(MojoExecution.class); - mojoExecution = new org.apache.maven.plugin.MojoExecution( - new org.apache.maven.model.Plugin(me.getPlugin()), me.getGoal(), me.getExecutionId()); - } catch (ComponentLookupException e) { - mojoExecution = null; - } - ExpressionEvaluator evaluator = new WrapEvaluator( - getContainer(), new PluginParameterExpressionEvaluatorV4(session, project, mojoExecution)); - ComponentConfigurator configurator = new EnhancedComponentConfigurator(); - configurator.configureComponent( - mojo, - new XmlPlexusConfiguration(new Xpp3Dom(pluginConfiguration)), - evaluator, - getContainer().getContainerRealm()); - } - - return mojo; - } - private XmlNode finalizeConfig(XmlNode config, MojoDescriptor mojoDescriptor) { List children = new ArrayList<>(); if (mojoDescriptor != null && mojoDescriptor.getParameters() != null) { - XmlNode defaultConfiguration = - MojoDescriptorCreator.convert(mojoDescriptor).getDom(); + XmlNode defaultConfiguration; + if (mojoDescriptor.isV4Api()) { + defaultConfiguration = MojoDescriptorCreator.convert(mojoDescriptor.getMojoDescriptorV4()); + } else { + defaultConfiguration = + MojoDescriptorCreator.convert(mojoDescriptor).getDom(); + } for (Parameter parameter : mojoDescriptor.getParameters()) { XmlNode parameterConfiguration = config.getChild(parameter.getName()); if (parameterConfiguration == null) { @@ -428,4 +620,33 @@ public File alignToBaseDirectory(File path) { return evaluator.alignToBaseDirectory(path); } } + + private Scope getScopeInstanceOrNull(final Injector injector, final Binding binding) { + return binding.acceptScopingVisitor(new DefaultBindingScopingVisitor() { + + @Override + public Scope visitScopeAnnotation(Class scopeAnnotation) { + throw new RuntimeException(String.format( + "I don't know how to handle the scopeAnnotation: %s", scopeAnnotation.getCanonicalName())); + } + + @Override + public Scope visitNoScoping() { + if (binding instanceof LinkedKeyBinding) { + Binding childBinding = injector.getBinding(((LinkedKeyBinding) binding).getLinkedKey()); + return getScopeInstanceOrNull(injector, childBinding); + } + return null; + } + + @Override + public Scope visitEagerSingleton() { + return Scopes.SINGLETON; + } + + public Scope visitScope(Scope scope) { + return scope; + } + }); + } } diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameter.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameter.java index 2a28f48..8c37804 100644 --- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameter.java +++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameter.java @@ -18,6 +18,7 @@ */ package org.apache.maven.api.plugin.testing; +import java.lang.annotation.Inherited; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -27,6 +28,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Repeatable(MojoParameters.class) +@Inherited public @interface MojoParameter { String name(); diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameters.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameters.java index 434abe1..373c926 100644 --- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameters.java +++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/MojoParameters.java @@ -18,6 +18,7 @@ */ package org.apache.maven.api.plugin.testing; +import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -25,6 +26,7 @@ * Mojo parameters container */ @Retention(RetentionPolicy.RUNTIME) +@Inherited public @interface MojoParameters { MojoParameter[] value(); } diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/MojoExecutionStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/MojoExecutionStub.java index a2d2b5b..2619b75 100644 --- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/MojoExecutionStub.java +++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/MojoExecutionStub.java @@ -21,24 +21,28 @@ import java.util.Optional; import org.apache.maven.api.MojoExecution; -import org.apache.maven.api.model.Plugin; +import org.apache.maven.api.Plugin; +import org.apache.maven.api.model.PluginExecution; +import org.apache.maven.api.plugin.descriptor.MojoDescriptor; import org.apache.maven.api.xml.XmlNode; /** * Stub for {@link MojoExecution}. */ public class MojoExecutionStub implements MojoExecution { - private final String artifactId; - private final String executionId; - private final String goal; - private final XmlNode dom; + private String executionId; + private String goal; + private XmlNode dom; + private Plugin plugin = new PluginStub(); + private PluginExecution model; + private MojoDescriptor descriptor; + private String lifecyclePhase; - public MojoExecutionStub(String artifactId, String executionId, String goal) { - this(artifactId, executionId, goal, null); + public MojoExecutionStub(String executionId, String goal) { + this(executionId, goal, null); } - public MojoExecutionStub(String artifactId, String executionId, String goal, XmlNode dom) { - this.artifactId = artifactId; + public MojoExecutionStub(String executionId, String goal, XmlNode dom) { this.executionId = executionId; this.goal = goal; this.dom = dom; @@ -46,7 +50,22 @@ public MojoExecutionStub(String artifactId, String executionId, String goal, Xml @Override public Plugin getPlugin() { - return Plugin.newBuilder().artifactId(artifactId).build(); + return plugin; + } + + @Override + public PluginExecution getModel() { + return model; + } + + @Override + public MojoDescriptor getDescriptor() { + return descriptor; + } + + @Override + public String getLifecyclePhase() { + return lifecyclePhase; } @Override @@ -63,4 +82,32 @@ public String getGoal() { public Optional getConfiguration() { return Optional.ofNullable(dom); } + + public void setExecutionId(String executionId) { + this.executionId = executionId; + } + + public void setGoal(String goal) { + this.goal = goal; + } + + public void setDom(XmlNode dom) { + this.dom = dom; + } + + public void setPlugin(Plugin plugin) { + this.plugin = plugin; + } + + public void setModel(PluginExecution model) { + this.model = model; + } + + public void setDescriptor(MojoDescriptor descriptor) { + this.descriptor = descriptor; + } + + public void setLifecyclePhase(String lifecyclePhase) { + this.lifecyclePhase = lifecyclePhase; + } } diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/PluginStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/PluginStub.java new file mode 100644 index 0000000..b7b4cfc --- /dev/null +++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/PluginStub.java @@ -0,0 +1,102 @@ +/* + * 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.plugin.testing.stubs; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.maven.api.Artifact; +import org.apache.maven.api.Dependency; +import org.apache.maven.api.Plugin; +import org.apache.maven.api.plugin.descriptor.PluginDescriptor; +import org.apache.maven.api.plugin.descriptor.lifecycle.Lifecycle; + +public class PluginStub implements Plugin { + + org.apache.maven.api.model.Plugin model; + PluginDescriptor descriptor; + List lifecycles = Collections.emptyList(); + ClassLoader classLoader; + Artifact artifact; + List dependencies = Collections.emptyList(); + Map dependenciesMap = Collections.emptyMap(); + + @Override + public org.apache.maven.api.model.Plugin getModel() { + return model; + } + + public void setModel(org.apache.maven.api.model.Plugin model) { + this.model = model; + } + + @Override + public PluginDescriptor getDescriptor() { + return descriptor; + } + + public void setDescriptor(PluginDescriptor descriptor) { + this.descriptor = descriptor; + } + + @Override + public List getLifecycles() { + return lifecycles; + } + + public void setLifecycles(List lifecycles) { + this.lifecycles = lifecycles; + } + + @Override + public ClassLoader getClassLoader() { + return classLoader; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public Artifact getArtifact() { + return artifact; + } + + public void setArtifact(Artifact artifact) { + this.artifact = artifact; + } + + @Override + public List getDependencies() { + return dependencies; + } + + public void setDependencies(List dependencies) { + this.dependencies = dependencies; + } + + public Map getDependenciesMap() { + return dependenciesMap; + } + + public void setDependenciesMap(Map dependenciesMap) { + this.dependenciesMap = dependenciesMap; + } +} diff --git a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/SessionStub.java b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/SessionStub.java index 2b05567..b3a6af1 100644 --- a/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/SessionStub.java +++ b/maven-plugin-testing-harness/src/main/java/org/apache/maven/api/plugin/testing/stubs/SessionStub.java @@ -27,29 +27,22 @@ import java.util.Map; import java.util.Optional; import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; import org.apache.maven.api.Artifact; import org.apache.maven.api.LocalRepository; import org.apache.maven.api.Project; import org.apache.maven.api.RemoteRepository; import org.apache.maven.api.Session; +import org.apache.maven.api.SessionData; import org.apache.maven.api.model.Model; import org.apache.maven.api.model.Repository; -import org.apache.maven.api.services.ArtifactDeployer; -import org.apache.maven.api.services.ArtifactDeployerRequest; -import org.apache.maven.api.services.ArtifactFactory; -import org.apache.maven.api.services.ArtifactFactoryRequest; -import org.apache.maven.api.services.ArtifactInstaller; -import org.apache.maven.api.services.ArtifactInstallerRequest; -import org.apache.maven.api.services.ArtifactManager; -import org.apache.maven.api.services.LocalRepositoryManager; -import org.apache.maven.api.services.ProjectBuilder; -import org.apache.maven.api.services.ProjectBuilderRequest; -import org.apache.maven.api.services.ProjectBuilderResult; -import org.apache.maven.api.services.ProjectManager; -import org.apache.maven.api.services.RepositoryFactory; +import org.apache.maven.api.services.*; import org.apache.maven.api.services.xml.ModelXmlFactory; import org.apache.maven.internal.impl.DefaultModelXmlFactory; +import org.apache.maven.internal.impl.DefaultVersionParser; +import org.apache.maven.internal.impl.InternalSession; import org.apache.maven.model.v4.MavenStaxReader; import org.mockito.ArgumentMatchers; @@ -67,15 +60,17 @@ */ public class SessionStub { - public static Session getMockSession(String localRepo) { + public static InternalSession getMockSession(String localRepo) { LocalRepository localRepository = mock(LocalRepository.class); when(localRepository.getId()).thenReturn("local"); when(localRepository.getPath()).thenReturn(Paths.get(localRepo)); return getMockSession(localRepository); } - public static Session getMockSession(LocalRepository localRepository) { - Session session = mock(Session.class); + public static InternalSession getMockSession(LocalRepository localRepository) { + InternalSession session = mock(InternalSession.class); + + when(session.getData()).thenReturn(new TestSessionData()); RepositoryFactory repositoryFactory = mock(RepositoryFactory.class); when(repositoryFactory.createRemote(any(Repository.class))).thenAnswer(iom -> { @@ -93,6 +88,10 @@ public static Session getMockSession(LocalRepository localRepository) { return remoteRepository; }); + when(session.getService(VersionParser.class)).thenReturn(new DefaultVersionParser()); + when(session.parseVersion(any())).thenAnswer(iom -> session.getService(VersionParser.class) + .parseVersion(iom.getArgument(0, String.class))); + LocalRepositoryManager localRepositoryManager = mock(LocalRepositoryManager.class); when(localRepositoryManager.getPathForLocalArtifact(any(), any(), any())) .thenAnswer(iom -> { @@ -275,4 +274,28 @@ static String getPathForArtifact(Artifact artifact, boolean local) { } return path.toString(); } + + static class TestSessionData implements SessionData { + private final Map map = new ConcurrentHashMap<>(); + + @Override + public void set(Object key, Object value) { + map.put(key, value); + } + + @Override + public boolean set(Object key, Object oldValue, Object newValue) { + return map.replace(key, oldValue, newValue); + } + + @Override + public Object get(Object key) { + return map.get(key); + } + + @Override + public Object computeIfAbsent(Object key, Supplier supplier) { + return map.computeIfAbsent(key, k -> supplier.get()); + } + } } diff --git a/maven-plugin-testing-harness/src/test/java/org/apache/maven/api/plugin/testing/ExpressionEvaluatorTest.java b/maven-plugin-testing-harness/src/test/java/org/apache/maven/api/plugin/testing/ExpressionEvaluatorTest.java index b287bbf..43722a9 100644 --- a/maven-plugin-testing-harness/src/test/java/org/apache/maven/api/plugin/testing/ExpressionEvaluatorTest.java +++ b/maven-plugin-testing-harness/src/test/java/org/apache/maven/api/plugin/testing/ExpressionEvaluatorTest.java @@ -20,12 +20,16 @@ import javax.inject.Named; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.Properties; import com.google.inject.Provides; +import org.apache.maven.api.Project; import org.apache.maven.api.Session; import org.apache.maven.api.plugin.MojoException; +import org.apache.maven.api.plugin.annotations.Mojo; +import org.apache.maven.api.plugin.testing.stubs.ProjectStub; import org.apache.maven.api.plugin.testing.stubs.SessionStub; import org.junit.jupiter.api.Test; @@ -42,16 +46,17 @@ public class ExpressionEvaluatorTest { private static final String LOCAL_REPO = "target/local-repo/"; + private static final String GROUP_ID = "org.apache.maven.plugins"; private static final String ARTIFACT_ID = "maven-test-mojo"; - private static final String COORDINATES = "groupId:" + ARTIFACT_ID + ":version:goal"; + private static final String COORDINATES = GROUP_ID + ":" + ARTIFACT_ID + ":version:goal"; private static final String CONFIG = "\n" + " \n" + " \n" + " \n" + " " + ARTIFACT_ID + "\n" + " \n" - + " ${basedir}\n" - + " ${basedir}/workDirectory\n" + + " ${project.basedir}\n" + + " ${project.basedir}/workDirectory\n" + " \n" + " \n" + " \n" @@ -68,6 +73,7 @@ public void testInjection(ExpressionEvaluatorMojo mojo) { @Test @InjectMojo(goal = COORDINATES, pom = CONFIG) + @Basedir("${basedir}/target/test-classes") @MojoParameter(name = "param", value = "paramValue") public void testParam(ExpressionEvaluatorMojo mojo) { assertNotNull(mojo.basedir); @@ -89,10 +95,11 @@ public void testParams(ExpressionEvaluatorMojo mojo) { } @Named(COORDINATES) + @Mojo(name = "goal") public static class ExpressionEvaluatorMojo implements org.apache.maven.api.plugin.Mojo { - private String basedir; + private Path basedir; - private String workdir; + private Path workdir; private String param; @@ -101,11 +108,11 @@ public static class ExpressionEvaluatorMojo implements org.apache.maven.api.plug /** {@inheritDoc} */ @Override public void execute() throws MojoException { - if (basedir == null || basedir.isEmpty()) { + if (basedir == null) { throw new MojoException("basedir was not injected."); } - if (workdir == null || workdir.isEmpty()) { + if (workdir == null) { throw new MojoException("workdir was not injected."); } else if (!workdir.startsWith(basedir)) { throw new MojoException("workdir does not start with basedir."); @@ -122,4 +129,11 @@ Session session() { doAnswer(iom -> Paths.get(MojoExtension.getBasedir())).when(session).getRootDirectory(); return session; } + + @Provides + Project project() { + ProjectStub project = new ProjectStub(); + project.setBasedir(Paths.get(MojoExtension.getBasedir())); + return project; + } } diff --git a/pom.xml b/pom.xml index 4cea8e8..e9dd565 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ under the License. org.apache.maven maven-parent - 40 + 41 @@ -65,7 +65,7 @@ under the License. 3.2.1 - 4.0.0-alpha-8 + 4.0.0-alpha-9 plugin-testing-archives/LATEST 8 2023-11-07T21:58:12Z