diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Mojo.java b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Mojo.java index a5768dfb4a..229232582e 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Mojo.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Mojo.java @@ -29,10 +29,23 @@ import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Nonnull; /** - * This annotation will mark your class as a Mojo (ie. goal in a Maven plugin). - * The mojo can be annotated with {@code jakarta.inject.*} annotations. + * This annotation will mark your class as a Mojo, which is the implementation of a goal in a Maven plugin. + *

+ * The mojo can be annotated with {@code org.apache.maven.api.di.*} annotations to + * control the lifecycle of the mojo itself, and to inject other beans. + *

+ *

+ * The mojo class can also be injected with an {@link Execute} annotation to specify a + * forked lifecycle. + *

+ *

* The {@link Parameter} annotation can be added on fields to inject data * from the plugin configuration or from other components. + *

+ *

+ * Fields can also be annotated with the {@link Resolution} annotation to be injected + * with the dependency collection or resolution result for the project. + *

* * @since 4.0.0 */ @@ -81,4 +94,22 @@ public @interface Mojo { */ @Nonnull String configurator() default ""; + + /** + * Indicates whether dependency collection will be + * required when executing the Mojo. + * If not set, it will be inferred from the fields + * annotated with the {@link Resolution} annotation. + */ + @Nonnull + boolean dependencyCollection() default false; + + /** + * Comma separated list of path scopes that will be + * required for dependency resolution. + * If not set, it will be inferred from the fields + * annotated with the {@link Resolution} annotation. + */ + @Nonnull + String dependencyResolutionPathScopes() default ""; } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Resolution.java b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Resolution.java new file mode 100644 index 0000000000..6e745a0dd4 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Resolution.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.api.plugin.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.apache.maven.api.annotations.Experimental; + +/** + * Indicates that a given field will be injected with the result of + * a dependency collection or resolution request. Whether a collection + * or resolution request is performed is controlled by the {@link #pathScope()} + * field, the injected field type and the {@link #requestType()}. + *

+ * If the {@code requestType} is not set explicitly, it will be inferred + * from the {@code pathScope} and the injected field type. If the type + * is {@link org.apache.maven.api.Node Node} and {@code pathScope == ""}, + * then the dependencies will be collected. + * If the type is {@link org.apache.maven.api.Node Node} or + * {@code List<}{@link org.apache.maven.api.Node Node}{@code >}, + * and {@code pathScope != ""}, the dependencies will be flattened. + * Else the dependencies will be resolved and {@code pathScope} must be non empty, + * and the field type can be {@link org.apache.maven.api.Node Node}, + * {@code List<}{@link org.apache.maven.api.Node Node}{@code >}, + * {@link org.apache.maven.api.services.DependencyResolverResult DependencyResolverResult}, + * {@code List<}{@link java.nio.file.Path Path}{@code >}, + * {@code Map<}{@link org.apache.maven.api.PathType PathType}{@code , List<}{@link java.nio.file.Path Path}{@code >>}, + * or {@code Map<}{@link org.apache.maven.api.Dependency Dependency}{@code , }{@link java.nio.file.Path Path}{@code >}. + * + * @since 4.0.0 + */ +@Experimental +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Resolution { + + /** + * The id of a {@link org.apache.maven.api.PathScope} enum value. + * If specified, a dependency resolution request will be issued, + * else a dependency collection request will be done. + * + * @return the id of the path scope + */ + String pathScope() default ""; + + /** + * The request type, in case the default one is not correct. + * Valid values are {@code collect}, {@code flatten}, or {@code resolve}. + * + * @return the request type + */ + String requestType() default ""; +} diff --git a/api/maven-api-plugin/src/main/mdo/plugin.mdo b/api/maven-api-plugin/src/main/mdo/plugin.mdo index 38bc1f917e..6d63d099af 100644 --- a/api/maven-api-plugin/src/main/mdo/plugin.mdo +++ b/api/maven-api-plugin/src/main/mdo/plugin.mdo @@ -397,6 +397,15 @@ under the License. * + + resolutions + 2.0.0+ + + + Resolution + * + + requirements 1.0.0/1.1.0 @@ -580,5 +589,34 @@ under the License. + + + Resolution + 2.0.0+ + Dependency collection or resolution injection. + + + field + false + 2.0.0+ + String + the name of the field to be injected + + + pathScope + false + 2.0.0+ + String + pathScope used to flatten dependencies + + + requestType + false + 2.0.0+ + String + either {@code collect}, {@code flatten}, or {@code resolve} + + + diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java index 5c250082a1..44864cea23 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java @@ -24,15 +24,27 @@ import javax.inject.Singleton; import java.io.*; import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; import java.util.jar.JarFile; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import org.apache.maven.RepositoryUtils; +import org.apache.maven.api.Dependency; +import org.apache.maven.api.Node; +import org.apache.maven.api.PathScope; +import org.apache.maven.api.PathType; import org.apache.maven.api.Project; import org.apache.maven.api.Session; +import org.apache.maven.api.plugin.descriptor.Resolution; +import org.apache.maven.api.services.DependencyResolver; +import org.apache.maven.api.services.DependencyResolverResult; +import org.apache.maven.api.services.PathScopeRegistry; import org.apache.maven.api.services.ProjectManager; import org.apache.maven.api.xml.XmlNode; import org.apache.maven.artifact.Artifact; @@ -575,6 +587,78 @@ public class DefaultMavenPluginManager implements MavenPluginManager { pomConfiguration, expressionEvaluator); + for (Resolution resolution : mojoDescriptor.getMojoDescriptorV4().getResolutions()) { + Field field = null; + for (Class clazz = mojo.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) { + try { + field = clazz.getDeclaredField(resolution.getField()); + break; + } catch (NoSuchFieldException e) { + // continue + } + } + if (field == null) { + throw new PluginConfigurationException( + pluginDescriptor, + "Unable to find field '" + resolution.getField() + "' annotated with @Resolution"); + } + field.setAccessible(true); + String pathScope = resolution.getPathScope(); + Object result = null; + if (pathScope != null && !pathScope.isEmpty()) { + // resolution + PathScope ps = sessionV4.getService(PathScopeRegistry.class).require(pathScope); + DependencyResolverResult res = + sessionV4.getService(DependencyResolver.class).resolve(sessionV4, project, ps); + if (field.getType() == DependencyResolverResult.class) { + result = res; + } else if (field.getType() == Node.class) { + result = res.getRoot(); + } else if (field.getType() == List.class && field.getGenericType() instanceof ParameterizedType pt) { + Type t = pt.getActualTypeArguments()[0]; + if (t == Node.class) { + result = res.getNodes(); + } else if (t == Path.class) { + result = res.getPaths(); + } + } else if (field.getType() == Map.class && field.getGenericType() instanceof ParameterizedType pt) { + Type k = pt.getActualTypeArguments()[0]; + Type v = pt.getActualTypeArguments()[1]; + if (k == PathType.class + && v instanceof ParameterizedType ptv + && ptv.getRawType() == List.class + && ptv.getActualTypeArguments()[0] == Path.class) { + result = res.getDispatchedPaths(); + } else if (k == Dependency.class && v == Path.class) { + result = res.getDependencies(); + } + } + } else { + // collection + DependencyResolverResult res = + sessionV4.getService(DependencyResolver.class).collect(sessionV4, project); + if (field.getType() == DependencyResolverResult.class) { + result = res; + } else if (field.getType() == Node.class) { + result = res.getRoot(); + } + } + if (result == null) { + throw new PluginConfigurationException( + pluginDescriptor, + "Unable to inject field '" + resolution.getField() + + "' annotated with @Dependencies. Unsupported type " + field.getGenericType()); + } + try { + field.set(mojo, result); + } catch (IllegalAccessException e) { + throw new PluginConfigurationException( + pluginDescriptor, + "Unable to inject field '" + resolution.getField() + "' annotated with @Dependencies", + e); + } + } + return mojo; } @@ -730,6 +814,7 @@ public class DefaultMavenPluginManager implements MavenPluginManager { validateParameters(mojoDescriptor, configuration, expressionEvaluator); } } + } catch (ComponentConfigurationException e) { String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId(); if (e.getFailedConfiguration() != null) { diff --git a/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/MojoDescriptor.java b/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/MojoDescriptor.java index 4c067effac..750ebddd66 100644 --- a/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/MojoDescriptor.java +++ b/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/MojoDescriptor.java @@ -166,7 +166,6 @@ public class MojoDescriptor extends ComponentDescriptor implements Cloneab this.setProjectRequired(md.isProjectRequired()); this.setSince(md.getSince()); this.setThreadSafe(true); - this.setV4Api(true); this.setImplementation(md.getImplementation()); try { this.setParameters(md.getParameters().stream().map(Parameter::new).collect(Collectors.toList())); @@ -174,6 +173,7 @@ public class MojoDescriptor extends ComponentDescriptor implements Cloneab throw new IllegalArgumentException(e); } this.mojoDescriptorV4 = md; + this.v4Api = true; } // ---------------------------------------------------------------------- // @@ -622,10 +622,6 @@ public class MojoDescriptor extends ComponentDescriptor implements Cloneab return v4Api; } - public void setV4Api(boolean v4Api) { - this.v4Api = v4Api; - } - /** * Creates a shallow copy of this mojo descriptor. */ diff --git a/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptorBuilder.java b/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptorBuilder.java index 8685cebf96..b29b96cd8e 100644 --- a/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptorBuilder.java +++ b/maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptorBuilder.java @@ -385,12 +385,6 @@ public class PluginDescriptorBuilder { mojo.setThreadSafe(Boolean.parseBoolean(threadSafe)); } - String v4Api = c.getChild("v4Api").getValue(); - - if (v4Api != null) { - mojo.setV4Api(Boolean.parseBoolean(v4Api)); - } - // ---------------------------------------------------------------------- // Configuration // ----------------------------------------------------------------------