View Javadoc
1   package org.apache.maven.plugin.plugin;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.net.URI;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Set;
29  
30  import org.apache.maven.artifact.Artifact;
31  import org.apache.maven.artifact.repository.ArtifactRepository;
32  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
33  import org.apache.maven.artifact.resolver.filter.IncludesArtifactFilter;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
36  import org.apache.maven.plugin.descriptor.PluginDescriptor;
37  import org.apache.maven.plugins.annotations.Component;
38  import org.apache.maven.plugins.annotations.LifecyclePhase;
39  import org.apache.maven.plugins.annotations.Mojo;
40  import org.apache.maven.plugins.annotations.Parameter;
41  import org.apache.maven.plugins.annotations.ResolutionScope;
42  import org.apache.maven.settings.Settings;
43  import org.apache.maven.tools.plugin.DefaultPluginToolsRequest;
44  import org.apache.maven.tools.plugin.PluginToolsRequest;
45  import org.apache.maven.tools.plugin.extractor.ExtractionException;
46  import org.apache.maven.tools.plugin.generator.GeneratorException;
47  import org.apache.maven.tools.plugin.generator.GeneratorUtils;
48  import org.apache.maven.tools.plugin.generator.PluginDescriptorFilesGenerator;
49  import org.apache.maven.tools.plugin.scanner.MojoScanner;
50  import org.codehaus.plexus.component.repository.ComponentDependency;
51  import org.codehaus.plexus.util.ReaderFactory;
52  import org.sonatype.plexus.build.incremental.BuildContext;
53  
54  /**
55   * <p>
56   * Generate a plugin descriptor.
57   * </p>
58   * <p>
59   * <b>Note:</b> Since 3.0, for Java plugin annotations support,
60   * default <a href="http://maven.apache.org/ref/current/maven-core/lifecycles.html">phase</a>
61   * defined by this goal is after the "compilation" of any scripts. This doesn't override
62   * <a href="/ref/current/maven-core/default-bindings.html#Bindings_for_maven-plugin_packaging">the default binding coded
63   * at generate-resources phase</a> in Maven core.
64   * </p>
65   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
66   * @since 2.0
67   */
68  @Mojo( name = "descriptor", defaultPhase = LifecyclePhase.PROCESS_CLASSES,
69         requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, threadSafe = true )
70  public class DescriptorGeneratorMojo
71      extends AbstractGeneratorMojo
72  {
73      /**
74       * The directory where the generated <code>plugin.xml</code> file will be put.
75       */
76      @Parameter( defaultValue = "${project.build.outputDirectory}/META-INF/maven", readonly = true )
77      private File outputDirectory;
78  
79      /**
80       * The file encoding of the source files.
81       *
82       * @since 2.5
83       */
84      @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
85      private String encoding;
86  
87      /**
88       * A flag to disable generation of the <code>plugin.xml</code> in favor of a hand authored plugin descriptor.
89       *
90       * @since 2.6
91       */
92      @Parameter( defaultValue = "false" )
93      private boolean skipDescriptor;
94  
95      /**
96       * <p>
97       * The role names of mojo extractors to use.
98       * </p>
99       * <p>
100      * If not set, all mojo extractors will be used. If set to an empty extractor name, no mojo extractors
101      * will be used.
102      * </p>
103      * Example:
104      * <pre>
105      *  &lt;!-- Use all mojo extractors --&gt;
106      *  &lt;extractors/&gt;
107      *
108      *  &lt;!-- Use no mojo extractors --&gt;
109      *  &lt;extractors&gt;
110      *      &lt;extractor/&gt;
111      *  &lt;/extractors&gt;
112      *
113      *  &lt;!-- Use only bsh mojo extractor --&gt;
114      *  &lt;extractors&gt;
115      *      &lt;extractor&gt;bsh&lt;/extractor&gt;
116      *  &lt;/extractors&gt;
117      * </pre>
118      */
119     @Parameter
120     private Set<String> extractors;
121 
122     /**
123      * By default, an exception is throw if no mojo descriptor is found. As the maven-plugin is defined in core, the
124      * descriptor generator mojo is bound to generate-resources phase.
125      * But for annotations, the compiled classes are needed, so skip error
126      *
127      * @since 3.0
128      */
129     @Parameter( property = "maven.plugin.skipErrorNoDescriptorsFound", defaultValue = "false" )
130     private boolean skipErrorNoDescriptorsFound;
131 
132     /**
133      * Flag controlling is "expected dependencies in provided scope" check to be performed or not. Default value:
134      * {@code true}.
135      *
136      * @since 3.6.3
137      */
138     @Parameter( defaultValue = "true", property = "maven.plugin.checkExpectedProvidedScope" )
139     private boolean checkExpectedProvidedScope = true;
140 
141     /**
142      * List of {@code groupId} strings of artifact coordinates that are expected to be in "provided" scope. Default
143      * value: {@code ["org.apache.maven"]}.
144      *
145      * @since 3.6.3
146      */
147     @Parameter
148     private List<String> expectedProvidedScopeGroupIds = Collections.singletonList( "org.apache.maven" );
149 
150     /**
151      * List of {@code groupId:artifactId} strings of artifact coordinates that are to be excluded from "expected
152      * provided scope" check. Default value: {@code ["org.apache.maven:maven-archiver", "org.apache.maven:maven-jxr"]}.
153      *
154      * @since 3.6.3
155      */
156     @Parameter
157     private List<String> expectedProvidedScopeExclusions = Arrays.asList(
158         "org.apache.maven:maven-archiver",
159         "org.apache.maven:maven-jxr" );
160 
161     /**
162      * Specify the dependencies as {@code groupId:artifactId} containing (abstract) Mojos, to filter
163      * dependencies scanned at runtime and focus on dependencies that are really useful to Mojo analysis.
164      * By default, the value is {@code null} and all dependencies are scanned (as before this parameter was added).
165      * If specified in the configuration with no children, no dependencies are scanned.
166      *
167      * @since 3.5
168      */
169     @Parameter
170     private List<String> mojoDependencies = null;
171 
172     /**
173      * Creates links to existing external javadoc-generated documentation.
174      * <br>
175      * <b>Notes</b>:
176      * all given links should have a fetchable {@code /package-list} or {@code /element-list} file.
177      * For instance:
178      * <pre>
179      * &lt;links&gt;
180      *   &lt;link&gt;https://docs.oracle.com/javase/8/docs/api/&lt;/link&gt;
181      * &lt;links&gt;
182      * </pre>
183      * is valid because <code>https://docs.oracle.com/javase/8/docs/api/package-list</code> exists.
184      * See <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">
185      * link option of the javadoc tool</a>.
186      * Using this parameter requires connectivity to the given URLs during the goal execution.
187      * @since 3.7.0
188      */
189     @Parameter( property = "externalJavadocBaseUrls", alias = "links" )
190     protected List<URI> externalJavadocBaseUrls;
191 
192     /**
193      * The base URL for the Javadoc site containing the current project's API documentation.
194      * This may be relative to the root of the generated Maven site.
195      * It does not need to exist yet at the time when this goal is executed.
196      * Must end with a slash.
197      * <b>In case this is set the javadoc reporting goal should be executed prior to
198      * <a href="../maven-plugin-report-plugin/index.html">Plugin Report</a>.</b>
199      * @since 3.7.0
200      */
201     @Parameter( property = "internalJavadocBaseUrl" )
202     protected URI internalJavadocBaseUrl;
203 
204     /**
205      * The version of the javadoc tool (equal to the container JDK version) used to generate the internal javadoc
206      * Only relevant if {@link #internalJavadocBaseUrl} is set.
207      * The default value needs to be overwritten in case toolchains are being used for generating Javadoc.
208      * 
209      * @since 3.7.0
210      */
211     @Parameter( property = "internalJavadocVersion", defaultValue = "${java.version}" )
212     protected String internalJavadocVersion;
213 
214     /**
215      * The Maven Settings, for evaluating proxy settings used to access {@link #javadocLinks}
216      *
217      * @since 3.7.0
218      */
219     @Parameter( defaultValue = "${settings}", readonly = true, required = true )
220     private Settings settings;
221 
222     /**
223      * List of Remote Repositories used by the resolver
224      *
225      * @since 3.0
226      */
227     @Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )
228     private List<ArtifactRepository> remoteRepos;
229 
230     /**
231      * Location of the local repository.
232      *
233      * @since 3.0
234      */
235     @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
236     private ArtifactRepository local;
237 
238     /**
239      * The component used for scanning the source tree for mojos.
240      */
241     @Component
242     private MojoScanner mojoScanner;
243 
244     @Component
245     protected BuildContext buildContext;
246 
247     public void generate()
248         throws MojoExecutionException
249     {
250 
251         if ( !"maven-plugin".equalsIgnoreCase( project.getArtifactId() )
252                         && project.getArtifactId().toLowerCase().startsWith( "maven-" )
253                         && project.getArtifactId().toLowerCase().endsWith( "-plugin" )
254                         && !"org.apache.maven.plugins".equals( project.getGroupId() ) )
255         {
256             getLog().error( LS + LS + "Artifact Ids of the format maven-___-plugin are reserved for" + LS
257                                 + "plugins in the Group Id org.apache.maven.plugins" + LS
258                                 + "Please change your artifactId to the format ___-maven-plugin" + LS
259                                 + "In the future this error will break the build." + LS + LS );
260         }
261 
262         if ( skipDescriptor )
263         {
264             getLog().warn( "Execution skipped" );
265             return;
266         }
267 
268         if ( checkExpectedProvidedScope )
269         {
270             Set<Artifact> wrongScopedArtifacts = dependenciesNotInProvidedScope();
271             if ( !wrongScopedArtifacts.isEmpty() )
272             {
273                 StringBuilder errorMessage = new StringBuilder(
274                     LS + LS + "Some dependencies of Maven Plugins are expected to be in provided scope." + LS
275                         + "Please make sure that dependencies listed below declared in POM" + LS
276                         + "have set '<scope>provided</scope>' as well." + LS + LS
277                         + "The following dependencies are in wrong scope:" + LS
278                 );
279                 for ( Artifact artifact : wrongScopedArtifacts )
280                 {
281                     errorMessage.append( " * " ).append( artifact ).append( LS );
282                 }
283                 errorMessage.append( LS ).append( LS );
284 
285                 getLog().error( errorMessage.toString() );
286             }
287         }
288 
289         mojoScanner.setActiveExtractors( extractors );
290 
291         // TODO: could use this more, eg in the writing of the plugin descriptor!
292         PluginDescriptor pluginDescriptor = new PluginDescriptor();
293 
294         pluginDescriptor.setGroupId( project.getGroupId() );
295 
296         pluginDescriptor.setArtifactId( project.getArtifactId() );
297 
298         pluginDescriptor.setVersion( project.getVersion() );
299 
300         pluginDescriptor.setGoalPrefix( goalPrefix );
301 
302         pluginDescriptor.setName( project.getName() );
303 
304         pluginDescriptor.setDescription( project.getDescription() );
305 
306         if ( encoding == null || encoding.length() < 1 )
307         {
308             getLog().warn( "Using platform encoding (" + ReaderFactory.FILE_ENCODING
309                                + " actually) to read mojo source files, i.e. build is platform dependent!" );
310         }
311         else
312         {
313             getLog().info( "Using '" + encoding + "' encoding to read mojo source files." );
314         }
315 
316         if ( internalJavadocBaseUrl != null && !internalJavadocBaseUrl.getPath().endsWith( "/" ) )
317         {
318             throw new MojoExecutionException( "Given parameter 'internalJavadocBaseUrl' must end with a slash but is '"
319                                               + internalJavadocBaseUrl + "'" );
320         }
321         try
322         {
323             List<ComponentDependency> deps = GeneratorUtils.toComponentDependencies( project.getArtifacts() );
324             pluginDescriptor.setDependencies( deps );
325 
326             PluginToolsRequest request = new DefaultPluginToolsRequest( project, pluginDescriptor );
327             request.setEncoding( encoding );
328             request.setSkipErrorNoDescriptorsFound( skipErrorNoDescriptorsFound );
329             request.setDependencies( filterMojoDependencies() );
330             request.setLocal( this.local );
331             request.setRemoteRepos( this.remoteRepos );
332             request.setInternalJavadocBaseUrl( internalJavadocBaseUrl );
333             request.setInternalJavadocVersion( internalJavadocVersion );
334             request.setExternalJavadocBaseUrls( externalJavadocBaseUrls );
335             request.setSettings( settings );
336 
337             mojoScanner.populatePluginDescriptor( request );
338 
339             outputDirectory.mkdirs();
340 
341             PluginDescriptorFilesGenerator pluginDescriptorGenerator = new PluginDescriptorFilesGenerator();
342             pluginDescriptorGenerator.execute( outputDirectory, request );
343 
344             buildContext.refresh( outputDirectory );
345         }
346         catch ( GeneratorException e )
347         {
348             throw new MojoExecutionException( "Error writing plugin descriptor", e );
349         }
350         catch ( InvalidPluginDescriptorException | ExtractionException e )
351         {
352             throw new MojoExecutionException( "Error extracting plugin descriptor: '" + e.getLocalizedMessage() + "'",
353                                               e );
354         }
355         catch ( LinkageError e )
356         {
357             throw new MojoExecutionException( "The API of the mojo scanner is not compatible with this plugin version."
358                                                   + " Please check the plugin dependencies configured"
359                                                   + " in the POM and ensure the versions match.",
360                                               e );
361         }
362     }
363 
364     /**
365      * Collects all dependencies expected to be in "provided" scope but are NOT in "provided" scope.
366      */
367     private Set<Artifact> dependenciesNotInProvidedScope()
368     {
369         LinkedHashSet<Artifact> wrongScopedDependencies = new LinkedHashSet<>();
370 
371         for ( Artifact dependency : project.getArtifacts() )
372         {
373             String ga = dependency.getGroupId() + ":" + dependency.getArtifactId();
374             if ( expectedProvidedScopeGroupIds.contains( dependency.getGroupId() )
375                 && !expectedProvidedScopeExclusions.contains( ga )
376                 && !Artifact.SCOPE_PROVIDED.equals( dependency.getScope() ) )
377             {
378                 wrongScopedDependencies.add( dependency );
379             }
380         }
381 
382         return wrongScopedDependencies;
383     }
384 
385     /**
386      * Get dependencies filtered with mojoDependencies configuration.
387      *
388      * @return eventually filtered dependencies, or even <code>null</code> if configured with empty mojoDependencies
389      * list
390      * @see #mojoDependencies
391      */
392     private Set<Artifact> filterMojoDependencies()
393     {
394         Set<Artifact> filteredArtifacts;
395         if ( mojoDependencies == null )
396         {
397             filteredArtifacts = new LinkedHashSet<>( project.getArtifacts() );
398         }
399         else if ( mojoDependencies.isEmpty() )
400         {
401             filteredArtifacts = null;
402         }
403         else
404         {
405             filteredArtifacts = new LinkedHashSet<>();
406 
407             ArtifactFilter filter = new IncludesArtifactFilter( mojoDependencies );
408 
409             for ( Artifact artifact : project.getArtifacts() )
410             {
411                 if ( filter.include( artifact ) )
412                 {
413                     filteredArtifacts.add( artifact );
414                 }
415             }
416         }
417 
418         return filteredArtifacts;
419     }
420 }