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