This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git

commit b852f740841fe0be142d191f8364518948425798
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue Jun 4 21:09:03 2024 +0200

    [MNG-8134] Add a @Resolution annotation to mojos to inject project 
dependencies collection / resolution result
---
 .../apache/maven/api/plugin/annotations/Mojo.java  | 35 ++++++++-
 .../maven/api/plugin/annotations/Resolution.java   | 74 +++++++++++++++++++
 api/maven-api-plugin/src/main/mdo/plugin.mdo       | 38 ++++++++++
 .../plugin/internal/DefaultMavenPluginManager.java | 85 ++++++++++++++++++++++
 .../maven/plugin/descriptor/MojoDescriptor.java    |  6 +-
 .../plugin/descriptor/PluginDescriptorBuilder.java |  6 --
 6 files changed, 231 insertions(+), 13 deletions(-)

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.
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * The mojo class can also be injected with an {@link Execute} annotation to 
specify a
+ * forked lifecycle.
+ * </p>
+ * <p>
  * The {@link Parameter} annotation can be added on fields to inject data
  * from the plugin configuration or from other components.
+ * </p>
+ * <p>
+ * Fields can also be annotated with the {@link Resolution} annotation to be 
injected
+ * with the dependency collection or resolution result for the project.
+ * </p>
  *
  * @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()}.
+ * <p>
+ * 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 <i>collected</i>.
+ * 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 <i>flattened</i>.
+ * Else the dependencies will be <i>resolved</i> 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.
             <multiplicity>*</multiplicity>
           </association>
         </field>
+        <field xdoc.separator="blank">
+          <name>resolutions</name>
+          <version>2.0.0+</version>
+          <description></description>
+          <association>
+            <type>Resolution</type>
+            <multiplicity>*</multiplicity>
+          </association>
+        </field>
         <field xdoc.separator="blank">
           <name>requirements</name>
           <version>1.0.0/1.1.0</version>
@@ -580,5 +589,34 @@ under the License.
         </field>
       </fields>
     </class>
+
+    <class xdoc.anchorName="resolution">
+      <name>Resolution</name>
+      <version>2.0.0+</version>
+      <description>Dependency collection or resolution injection.</description>
+      <fields>
+        <field>
+          <name>field</name>
+          <required>false</required>
+          <version>2.0.0+</version>
+          <type>String</type>
+          <description>the name of the field to be injected</description>
+        </field>
+        <field>
+          <name>pathScope</name>
+          <required>false</required>
+          <version>2.0.0+</version>
+          <type>String</type>
+          <description>pathScope used to flatten dependencies</description>
+        </field>
+        <field>
+          <name>requestType</name>
+          <required>false</required>
+          <version>2.0.0+</version>
+          <type>String</type>
+          <description>either {@code collect}, {@code flatten}, or {@code 
resolve}</description>
+        </field>
+      </fields>
+    </class>
   </classes>
 </model>
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<Mojo> 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<Mojo> implements Cloneab
             throw new IllegalArgumentException(e);
         }
         this.mojoDescriptorV4 = md;
+        this.v4Api = true;
     }
     // ----------------------------------------------------------------------
     //
@@ -622,10 +622,6 @@ public class MojoDescriptor extends 
ComponentDescriptor<Mojo> 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
         // 
----------------------------------------------------------------------

Reply via email to