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 org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.repository.ArtifactRepository;
24  import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
25  import org.apache.maven.artifact.resolver.filter.IncludesArtifactFilter;
26  import org.apache.maven.plugin.AbstractMojo;
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException;
29  import org.apache.maven.plugin.descriptor.PluginDescriptor;
30  import org.apache.maven.plugins.annotations.Component;
31  import org.apache.maven.plugins.annotations.Parameter;
32  import org.apache.maven.project.MavenProject;
33  import org.apache.maven.tools.plugin.DefaultPluginToolsRequest;
34  import org.apache.maven.tools.plugin.PluginToolsRequest;
35  import org.apache.maven.tools.plugin.extractor.ExtractionException;
36  import org.apache.maven.tools.plugin.generator.Generator;
37  import org.apache.maven.tools.plugin.generator.GeneratorException;
38  import org.apache.maven.tools.plugin.generator.GeneratorUtils;
39  import org.apache.maven.tools.plugin.scanner.MojoScanner;
40  import org.codehaus.plexus.component.repository.ComponentDependency;
41  import org.codehaus.plexus.util.ReaderFactory;
42  import org.sonatype.plexus.build.incremental.BuildContext;
43  
44  import java.io.File;
45  import java.util.Arrays;
46  import java.util.Collections;
47  import java.util.LinkedHashSet;
48  import java.util.List;
49  import java.util.Set;
50  
51  /**
52   * Abstract class for this Plugin.
53   *
54   * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
55   *
56   */
57  public abstract class AbstractGeneratorMojo
58      extends AbstractMojo
59  {
60      /**
61       * The project currently being built.
62       */
63      @Parameter( defaultValue = "${project}", readonly = true )
64      protected MavenProject project;
65  
66      /**
67       * The component used for scanning the source tree for mojos.
68       */
69      @Component
70      protected MojoScanner mojoScanner;
71  
72      @Component
73      protected BuildContext buildContext;
74  
75      /**
76       * The file encoding of the source files.
77       *
78       * @since 2.5
79       */
80      @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
81      protected String encoding;
82  
83      /**
84       * The goal prefix that will appear before the ":".
85       */
86      @Parameter
87      protected String goalPrefix;
88  
89      /**
90       * By default an exception is throw if no mojo descriptor is found. As the maven-plugin is defined in core, the
91       * descriptor generator mojo is bound to generate-resources phase.
92       * But for annotations, the compiled classes are needed, so skip error
93       *
94       * @since 3.0
95       */
96      @Parameter( property = "maven.plugin.skipErrorNoDescriptorsFound", defaultValue = "false" )
97      protected boolean skipErrorNoDescriptorsFound;
98  
99      /**
100      * <p>
101      * The role names of mojo extractors to use.
102      * </p>
103      * <p>
104      * If not set, all mojo extractors will be used. If set to an empty extractor name, no mojo extractors
105      * will be used.
106      * </p>
107      * Example:
108      * <pre>
109      *  &lt;!-- Use all mojo extractors --&gt;
110      *  &lt;extractors/&gt;
111      *
112      *  &lt;!-- Use no mojo extractors --&gt;
113      *  &lt;extractors&gt;
114      *      &lt;extractor/&gt;
115      *  &lt;/extractors&gt;
116      *
117      *  &lt;!-- Use only bsh mojo extractor --&gt;
118      *  &lt;extractors&gt;
119      *      &lt;extractor&gt;bsh&lt;/extractor&gt;
120      *  &lt;/extractors&gt;
121      * </pre>
122      */
123     @Parameter
124     protected Set<String> extractors;
125 
126     /**
127      * Set this to "true" to skip invoking any goals or reports of the plugin.
128      *
129      * @since 2.8
130      */
131     @Parameter( defaultValue = "false", property = "maven.plugin.skip" )
132     protected boolean skip;
133 
134     /**
135      * Specify the dependencies as {@code groupId:artifactId} containing (abstract) Mojos, to filter
136      * dependencies scanned at runtime and focus on dependencies that are really useful to Mojo analysis.
137      * By default, the value is {@code null} and all dependencies are scanned (as before this parameter was added).
138      * If specified in the configuration with no children, no dependencies are scanned.
139      * 
140      * @since 3.5
141      */
142     @Parameter
143     private List<String> mojoDependencies = null;
144 
145     /**
146      * List of Remote Repositories used by the resolver
147      *
148      * @since 3.0
149      */
150     @Parameter( defaultValue = "${project.remoteArtifactRepositories}", required = true, readonly = true )
151     protected List<ArtifactRepository> remoteRepos;
152 
153     /**
154      * Location of the local repository.
155      *
156      * @since 3.0
157      */
158     @Parameter( defaultValue = "${localRepository}", required = true, readonly = true )
159     protected ArtifactRepository local;
160 
161     /**
162      * Maven plugin packaging types. Default is single "maven-plugin".
163      * 
164      * @since 3.3
165      */
166     @Parameter
167     protected List<String> packagingTypes = Collections.singletonList( "maven-plugin" );
168 
169     /**
170      * Flag controlling is "expected dependencies in provided scope" check to be performed or not. Default value:
171      * {@code true}.
172      *
173      * @since 3.6.3
174      */
175     @Parameter( defaultValue = "true", property = "maven.plugin.checkExpectedProvidedScope" )
176     private boolean checkExpectedProvidedScope = true;
177 
178     /**
179      * List of {@code groupId} strings of artifact coordinates that are expected to be in "provided" scope. Default
180      * value: {@code ["org.apache.maven"]}.
181      *
182      * @since 3.6.3
183      */
184     @Parameter
185     private List<String> expectedProvidedScopeGroupIds = Collections.singletonList( "org.apache.maven" );
186 
187     /**
188      * List of {@code groupId:artifactId} strings of artifact coordinates that are to be excluded from "expected
189      * provided scope" check. Default value: {@code ["org.apache.maven:maven-archiver", "org.apache.maven:maven-jxr"]}.
190      *
191      * @since 3.6.3
192      */
193     @Parameter
194     private List<String> expectedProvidedScopeExclusions = Arrays.asList(
195             "org.apache.maven:maven-archiver",
196             "org.apache.maven:maven-jxr" );
197 
198     /**
199      * @return the output directory where files will be generated.
200      */
201     protected abstract File getOutputDirectory();
202 
203     /**
204      * @return the wanted <code>Generator</code> implementation.
205      */
206     protected abstract Generator createGenerator();
207 
208     /**
209      * System/OS line separator: used to format console messages.
210      */
211     private static final String LS = System.lineSeparator();
212 
213     /**
214      * {@inheritDoc}
215      */
216     @Override
217     public void execute()
218         throws MojoExecutionException
219     {
220         if ( !packagingTypes.contains( project.getPackaging() ) )
221         {
222             getLog().info( "Unsupported packaging type " + project.getPackaging() + ", execution skipped" );
223             return;
224         }
225         if ( skip )
226         {
227             getLog().warn( "Execution skipped" );
228             return;
229         }
230 
231         if ( !"maven-plugin".equalsIgnoreCase( project.getArtifactId() )
232             && project.getArtifactId().toLowerCase().startsWith( "maven-" )
233             && project.getArtifactId().toLowerCase().endsWith( "-plugin" ) 
234             && !"org.apache.maven.plugins".equals( project.getGroupId() ) )
235         {
236             getLog().error( LS + LS + "Artifact Ids of the format maven-___-plugin are reserved for" + LS
237                                 + "plugins in the Group Id org.apache.maven.plugins" + LS
238                                 + "Please change your artifactId to the format ___-maven-plugin" + LS
239                                 + "In the future this error will break the build." + LS + LS );
240         }
241 
242         if ( checkExpectedProvidedScope )
243         {
244             Set<Artifact> wrongScopedArtifacts = dependenciesNotInProvidedScope();
245             if ( !wrongScopedArtifacts.isEmpty() )
246             {
247                 StringBuilder errorMessage = new StringBuilder(
248                         LS + LS + "Some dependencies of Maven Plugins are expected to be in provided scope." + LS
249                         + "Please make sure that dependencies listed below declared in POM" + LS
250                         + "have set '<scope>provided</scope>' as well." + LS + LS
251                         + "The following dependencies are in wrong scope:" + LS
252                 );
253                 for ( Artifact artifact : wrongScopedArtifacts )
254                 {
255                     errorMessage.append( " * " ).append( artifact ).append( LS );
256                 }
257                 errorMessage.append( LS ).append( LS );
258 
259                 getLog().error( errorMessage.toString() );
260             }
261         }
262 
263         String defaultGoalPrefix = getDefaultGoalPrefix( project );
264           
265         if ( goalPrefix == null )
266         {
267             goalPrefix = defaultGoalPrefix;
268         }
269         else if ( !goalPrefix.equals( defaultGoalPrefix ) )
270         {
271             getLog().warn(
272                 LS + LS + "Goal prefix is specified as: '" + goalPrefix + "'. " + "Maven currently expects it to be '"
273                     + defaultGoalPrefix + "'." + LS );
274         }
275 
276         mojoScanner.setActiveExtractors( extractors );
277 
278         // TODO: could use this more, eg in the writing of the plugin descriptor!
279         PluginDescriptor pluginDescriptor = new PluginDescriptor();
280 
281         pluginDescriptor.setGroupId( project.getGroupId() );
282 
283         pluginDescriptor.setArtifactId( project.getArtifactId() );
284 
285         pluginDescriptor.setVersion( project.getVersion() );
286 
287         pluginDescriptor.setGoalPrefix( goalPrefix );
288 
289         pluginDescriptor.setName( project.getName() );
290 
291         pluginDescriptor.setDescription( project.getDescription() );
292 
293         if ( encoding == null || encoding.length() < 1 )
294         {
295             getLog().warn( "Using platform encoding (" + ReaderFactory.FILE_ENCODING
296                                + " actually) to read mojo source files, i.e. build is platform dependent!" );
297         }
298         else
299         {
300             getLog().info( "Using '" + encoding + "' encoding to read mojo source files." );
301         }
302 
303         try
304         {
305             List<ComponentDependency> deps = GeneratorUtils.toComponentDependencies( project.getArtifacts() );
306             pluginDescriptor.setDependencies( deps );
307 
308             PluginToolsRequest request = new DefaultPluginToolsRequest( project, pluginDescriptor );
309             request.setEncoding( encoding );
310             request.setSkipErrorNoDescriptorsFound( skipErrorNoDescriptorsFound );
311             request.setDependencies( filterMojoDependencies() );
312             request.setLocal( this.local );
313             request.setRemoteRepos( this.remoteRepos );
314 
315             mojoScanner.populatePluginDescriptor( request );
316 
317             File outputDirectory = getOutputDirectory();
318             outputDirectory.mkdirs();
319 
320             createGenerator().execute( outputDirectory, request );
321             buildContext.refresh( outputDirectory );
322         }
323         catch ( GeneratorException e )
324         {
325             throw new MojoExecutionException( "Error writing plugin descriptor", e );
326         }
327         catch ( InvalidPluginDescriptorException | ExtractionException e )
328         {
329             throw new MojoExecutionException( "Error extracting plugin descriptor: '" + e.getLocalizedMessage() + "'",
330                                               e );
331         }
332         catch ( LinkageError e )
333         {
334             throw new MojoExecutionException( "The API of the mojo scanner is not compatible with this plugin version."
335                 + " Please check the plugin dependencies configured in the POM and ensure the versions match.", e );
336         }
337     }
338 
339     static String getDefaultGoalPrefix( MavenProject project )
340     {
341         String defaultGoalPrefix;
342         if ( "maven-plugin".equalsIgnoreCase( project.getArtifactId() ) )
343         {
344             defaultGoalPrefix = project.getGroupId().substring( project.getGroupId().lastIndexOf( '.' ) + 1 );
345         }
346         else
347         {
348             defaultGoalPrefix = PluginDescriptor.getGoalPrefixFromArtifactId( project.getArtifactId() );
349         }
350         return defaultGoalPrefix;
351     }
352 
353     /**
354      * Collects all dependencies expected to be in "provided" scope but are NOT in "provided" scope.
355      */
356     private Set<Artifact> dependenciesNotInProvidedScope()
357     {
358         LinkedHashSet<Artifact> wrongScopedDependencies = new LinkedHashSet<>();
359 
360         for ( Artifact dependency : project.getArtifacts() )
361         {
362             String ga = dependency.getGroupId() + ":" + dependency.getArtifactId();
363             if ( expectedProvidedScopeGroupIds.contains( dependency.getGroupId() )
364                 && !expectedProvidedScopeExclusions.contains( ga )
365                 && !Artifact.SCOPE_PROVIDED.equals( dependency.getScope() ) )
366             {
367                 wrongScopedDependencies.add( dependency );
368             }
369         }
370 
371         return wrongScopedDependencies;
372     }
373 
374     /**
375      * Get dependencies filtered with mojoDependencies configuration.
376      * 
377      * @return eventually filtered dependencies, or even <code>null</code> if configured with empty mojoDependencies
378      * list
379      * @see #mojoDependencies
380      */
381     private Set<Artifact> filterMojoDependencies()
382     {
383         Set<Artifact> filteredArtifacts;
384         if ( mojoDependencies == null )
385         {
386             filteredArtifacts = new LinkedHashSet<>( project.getArtifacts() );
387         }
388         else if ( mojoDependencies.size() == 0 )
389         {
390             filteredArtifacts = null;
391         }
392         else
393         {
394             filteredArtifacts = new LinkedHashSet<>();
395             
396             ArtifactFilter filter = new IncludesArtifactFilter( mojoDependencies );
397 
398             for ( Artifact artifact : project.getArtifacts() )
399             {
400                 if ( filter.include( artifact ) )
401                 {
402                     filteredArtifacts.add( artifact );
403                 }
404             }
405         }
406 
407         return filteredArtifacts;
408     }
409 }