Skip to content

Commit 6aa4f06

Browse files
committed
[MNG-8134] Add a @resolution annotation to mojos to inject project dependencies collection / resolution result
1 parent b1590a5 commit 6aa4f06

File tree

6 files changed

+231
-13
lines changed

6 files changed

+231
-13
lines changed

api/maven-api-core/src/main/java/org/apache/maven/api/plugin/annotations/Mojo.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,23 @@
2929
import org.apache.maven.api.annotations.Nonnull;
3030

3131
/**
32-
* This annotation will mark your class as a Mojo (ie. goal in a Maven plugin).
33-
* The mojo can be annotated with {@code jakarta.inject.*} annotations.
32+
* This annotation will mark your class as a Mojo, which is the implementation of a goal in a Maven plugin.
33+
* <p>
34+
* The mojo can be annotated with {@code org.apache.maven.api.di.*} annotations to
35+
* control the lifecycle of the mojo itself, and to inject other beans.
36+
* </p>
37+
* <p>
38+
* The mojo class can also be injected with an {@link Execute} annotation to specify a
39+
* forked lifecycle.
40+
* </p>
41+
* <p>
3442
* The {@link Parameter} annotation can be added on fields to inject data
3543
* from the plugin configuration or from other components.
44+
* </p>
45+
* <p>
46+
* Fields can also be annotated with the {@link Resolution} annotation to be injected
47+
* with the dependency collection or resolution result for the project.
48+
* </p>
3649
*
3750
* @since 4.0.0
3851
*/
@@ -81,4 +94,22 @@
8194
*/
8295
@Nonnull
8396
String configurator() default "";
97+
98+
/**
99+
* Indicates whether dependency collection will be
100+
* required when executing the Mojo.
101+
* If not set, it will be inferred from the fields
102+
* annotated with the {@link Resolution} annotation.
103+
*/
104+
@Nonnull
105+
boolean dependencyCollection() default false;
106+
107+
/**
108+
* Comma separated list of path scopes that will be
109+
* required for dependency resolution.
110+
* If not set, it will be inferred from the fields
111+
* annotated with the {@link Resolution} annotation.
112+
*/
113+
@Nonnull
114+
String dependencyResolutionPathScopes() default "";
84115
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.api.plugin.annotations;
20+
21+
import java.lang.annotation.Documented;
22+
import java.lang.annotation.ElementType;
23+
import java.lang.annotation.Retention;
24+
import java.lang.annotation.RetentionPolicy;
25+
import java.lang.annotation.Target;
26+
27+
import org.apache.maven.api.annotations.Experimental;
28+
29+
/**
30+
* Indicates that a given field will be injected with the result of
31+
* a dependency collection or resolution request. Whether a collection
32+
* or resolution request is performed is controlled by the {@link #pathScope()}
33+
* field, the injected field type and the {@link #requestType()}.
34+
* <p>
35+
* If the {@code requestType} is not set explicitly, it will be inferred
36+
* from the {@code pathScope} and the injected field type. If the type
37+
* is {@link org.apache.maven.api.Node Node} and {@code pathScope == ""},
38+
* then the dependencies will be <i>collected</i>.
39+
* If the type is {@link org.apache.maven.api.Node Node} or
40+
* {@code List<}{@link org.apache.maven.api.Node Node}{@code >},
41+
* and {@code pathScope != ""}, the dependencies will be <i>flattened</i>.
42+
* Else the dependencies will be <i>resolved</i> and {@code pathScope} must be non empty,
43+
* and the field type can be {@link org.apache.maven.api.Node Node},
44+
* {@code List<}{@link org.apache.maven.api.Node Node}{@code >},
45+
* {@link org.apache.maven.api.services.DependencyResolverResult DependencyResolverResult},
46+
* {@code List<}{@link java.nio.file.Path Path}{@code >},
47+
* {@code Map<}{@link org.apache.maven.api.PathType PathType}{@code , List<}{@link java.nio.file.Path Path}{@code >>},
48+
* or {@code Map<}{@link org.apache.maven.api.Dependency Dependency}{@code , }{@link java.nio.file.Path Path}{@code >}.
49+
*
50+
* @since 4.0.0
51+
*/
52+
@Experimental
53+
@Documented
54+
@Retention(RetentionPolicy.RUNTIME)
55+
@Target(ElementType.FIELD)
56+
public @interface Resolution {
57+
58+
/**
59+
* The id of a {@link org.apache.maven.api.PathScope} enum value.
60+
* If specified, a dependency resolution request will be issued,
61+
* else a dependency collection request will be done.
62+
*
63+
* @return the id of the path scope
64+
*/
65+
String pathScope() default "";
66+
67+
/**
68+
* The request type, in case the default one is not correct.
69+
* Valid values are {@code collect}, {@code flatten}, or {@code resolve}.
70+
*
71+
* @return the request type
72+
*/
73+
String requestType() default "";
74+
}

api/maven-api-plugin/src/main/mdo/plugin.mdo

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,15 @@ under the License.
397397
<multiplicity>*</multiplicity>
398398
</association>
399399
</field>
400+
<field xdoc.separator="blank">
401+
<name>resolutions</name>
402+
<version>2.0.0+</version>
403+
<description></description>
404+
<association>
405+
<type>Resolution</type>
406+
<multiplicity>*</multiplicity>
407+
</association>
408+
</field>
400409
<field xdoc.separator="blank">
401410
<name>requirements</name>
402411
<version>1.0.0/1.1.0</version>
@@ -580,5 +589,34 @@ under the License.
580589
</field>
581590
</fields>
582591
</class>
592+
593+
<class xdoc.anchorName="resolution">
594+
<name>Resolution</name>
595+
<version>2.0.0+</version>
596+
<description>Dependency collection or resolution injection.</description>
597+
<fields>
598+
<field>
599+
<name>field</name>
600+
<required>false</required>
601+
<version>2.0.0+</version>
602+
<type>String</type>
603+
<description>the name of the field to be injected</description>
604+
</field>
605+
<field>
606+
<name>pathScope</name>
607+
<required>false</required>
608+
<version>2.0.0+</version>
609+
<type>String</type>
610+
<description>pathScope used to flatten dependencies</description>
611+
</field>
612+
<field>
613+
<name>requestType</name>
614+
<required>false</required>
615+
<version>2.0.0+</version>
616+
<type>String</type>
617+
<description>either {@code collect}, {@code flatten}, or {@code resolve}</description>
618+
</field>
619+
</fields>
620+
</class>
583621
</classes>
584622
</model>

maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,27 @@
2424

2525
import java.io.*;
2626
import java.lang.annotation.Annotation;
27+
import java.lang.reflect.Field;
28+
import java.lang.reflect.ParameterizedType;
29+
import java.lang.reflect.Type;
2730
import java.nio.file.Files;
31+
import java.nio.file.Path;
2832
import java.util.*;
2933
import java.util.jar.JarFile;
3034
import java.util.stream.Collectors;
3135
import java.util.zip.ZipEntry;
3236

3337
import org.apache.maven.RepositoryUtils;
38+
import org.apache.maven.api.Dependency;
39+
import org.apache.maven.api.Node;
40+
import org.apache.maven.api.PathScope;
41+
import org.apache.maven.api.PathType;
3442
import org.apache.maven.api.Project;
3543
import org.apache.maven.api.Session;
44+
import org.apache.maven.api.plugin.descriptor.Resolution;
45+
import org.apache.maven.api.services.DependencyResolver;
46+
import org.apache.maven.api.services.DependencyResolverResult;
47+
import org.apache.maven.api.services.PathScopeRegistry;
3648
import org.apache.maven.api.services.ProjectManager;
3749
import org.apache.maven.api.xml.XmlNode;
3850
import org.apache.maven.artifact.Artifact;
@@ -575,6 +587,78 @@ private <T> T loadV4Mojo(
575587
pomConfiguration,
576588
expressionEvaluator);
577589

590+
for (Resolution resolution : mojoDescriptor.getMojoDescriptorV4().getResolutions()) {
591+
Field field = null;
592+
for (Class<?> clazz = mojo.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
593+
try {
594+
field = clazz.getDeclaredField(resolution.getField());
595+
break;
596+
} catch (NoSuchFieldException e) {
597+
// continue
598+
}
599+
}
600+
if (field == null) {
601+
throw new PluginConfigurationException(
602+
pluginDescriptor,
603+
"Unable to find field '" + resolution.getField() + "' annotated with @Resolution");
604+
}
605+
field.setAccessible(true);
606+
String pathScope = resolution.getPathScope();
607+
Object result = null;
608+
if (pathScope != null && !pathScope.isEmpty()) {
609+
// resolution
610+
PathScope ps = sessionV4.getService(PathScopeRegistry.class).require(pathScope);
611+
DependencyResolverResult res =
612+
sessionV4.getService(DependencyResolver.class).resolve(sessionV4, project, ps);
613+
if (field.getType() == DependencyResolverResult.class) {
614+
result = res;
615+
} else if (field.getType() == Node.class) {
616+
result = res.getRoot();
617+
} else if (field.getType() == List.class && field.getGenericType() instanceof ParameterizedType pt) {
618+
Type t = pt.getActualTypeArguments()[0];
619+
if (t == Node.class) {
620+
result = res.getNodes();
621+
} else if (t == Path.class) {
622+
result = res.getPaths();
623+
}
624+
} else if (field.getType() == Map.class && field.getGenericType() instanceof ParameterizedType pt) {
625+
Type k = pt.getActualTypeArguments()[0];
626+
Type v = pt.getActualTypeArguments()[1];
627+
if (k == PathType.class
628+
&& v instanceof ParameterizedType ptv
629+
&& ptv.getRawType() == List.class
630+
&& ptv.getActualTypeArguments()[0] == Path.class) {
631+
result = res.getDispatchedPaths();
632+
} else if (k == Dependency.class && v == Path.class) {
633+
result = res.getDependencies();
634+
}
635+
}
636+
} else {
637+
// collection
638+
DependencyResolverResult res =
639+
sessionV4.getService(DependencyResolver.class).collect(sessionV4, project);
640+
if (field.getType() == DependencyResolverResult.class) {
641+
result = res;
642+
} else if (field.getType() == Node.class) {
643+
result = res.getRoot();
644+
}
645+
}
646+
if (result == null) {
647+
throw new PluginConfigurationException(
648+
pluginDescriptor,
649+
"Unable to inject field '" + resolution.getField()
650+
+ "' annotated with @Dependencies. Unsupported type " + field.getGenericType());
651+
}
652+
try {
653+
field.set(mojo, result);
654+
} catch (IllegalAccessException e) {
655+
throw new PluginConfigurationException(
656+
pluginDescriptor,
657+
"Unable to inject field '" + resolution.getField() + "' annotated with @Dependencies",
658+
e);
659+
}
660+
}
661+
578662
return mojo;
579663
}
580664

@@ -730,6 +814,7 @@ private void populateMojoExecutionFields(
730814
validateParameters(mojoDescriptor, configuration, expressionEvaluator);
731815
}
732816
}
817+
733818
} catch (ComponentConfigurationException e) {
734819
String message = "Unable to parse configuration of mojo " + mojoDescriptor.getId();
735820
if (e.getFailedConfiguration() != null) {

maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/MojoDescriptor.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,14 +166,14 @@ public MojoDescriptor(PluginDescriptor pd, org.apache.maven.api.plugin.descripto
166166
this.setProjectRequired(md.isProjectRequired());
167167
this.setSince(md.getSince());
168168
this.setThreadSafe(true);
169-
this.setV4Api(true);
170169
this.setImplementation(md.getImplementation());
171170
try {
172171
this.setParameters(md.getParameters().stream().map(Parameter::new).collect(Collectors.toList()));
173172
} catch (DuplicateParameterException e) {
174173
throw new IllegalArgumentException(e);
175174
}
176175
this.mojoDescriptorV4 = md;
176+
this.v4Api = true;
177177
}
178178
// ----------------------------------------------------------------------
179179
//
@@ -622,10 +622,6 @@ public boolean isV4Api() {
622622
return v4Api;
623623
}
624624

625-
public void setV4Api(boolean v4Api) {
626-
this.v4Api = v4Api;
627-
}
628-
629625
/**
630626
* Creates a shallow copy of this mojo descriptor.
631627
*/

maven-plugin-api/src/main/java/org/apache/maven/plugin/descriptor/PluginDescriptorBuilder.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -385,12 +385,6 @@ public MojoDescriptor buildComponentDescriptor(PlexusConfiguration c, PluginDesc
385385
mojo.setThreadSafe(Boolean.parseBoolean(threadSafe));
386386
}
387387

388-
String v4Api = c.getChild("v4Api").getValue();
389-
390-
if (v4Api != null) {
391-
mojo.setV4Api(Boolean.parseBoolean(v4Api));
392-
}
393-
394388
// ----------------------------------------------------------------------
395389
// Configuration
396390
// ----------------------------------------------------------------------

0 commit comments

Comments
 (0)