View Javadoc
1   package org.apache.maven.plugins.source;
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.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.List;
27  
28  import org.apache.maven.archiver.MavenArchiveConfiguration;
29  import org.apache.maven.archiver.MavenArchiver;
30  import org.apache.maven.artifact.DependencyResolutionRequiredException;
31  import org.apache.maven.execution.MavenSession;
32  import org.apache.maven.model.Resource;
33  import org.apache.maven.plugin.AbstractMojo;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugins.annotations.Component;
36  import org.apache.maven.plugins.annotations.Parameter;
37  import org.apache.maven.project.MavenProject;
38  import org.apache.maven.project.MavenProjectHelper;
39  import org.codehaus.plexus.archiver.Archiver;
40  import org.codehaus.plexus.archiver.ArchiverException;
41  import org.codehaus.plexus.archiver.jar.JarArchiver;
42  import org.codehaus.plexus.archiver.jar.ManifestException;
43  import org.codehaus.plexus.util.FileUtils;
44  
45  /**
46   * Base class for bundling sources into a jar archive.
47   *
48   * @since 2.0.3
49   */
50  public abstract class AbstractSourceJarMojo
51      extends AbstractMojo
52  {
53      private static final String[] DEFAULT_INCLUDES = new String[] { "**/*" };
54  
55      private static final String[] DEFAULT_EXCLUDES = new String[] {};
56  
57      /**
58       * List of files to include. Specified as fileset patterns which are relative to the input directory whose contents
59       * is being packaged into the JAR.
60       *
61       * @since 2.1
62       */
63      @Parameter
64      private String[] includes;
65  
66      /**
67       * List of files to exclude. Specified as fileset patterns which are relative to the input directory whose contents
68       * is being packaged into the JAR.
69       *
70       * @since 2.1
71       */
72      @Parameter
73      private String[] excludes;
74  
75      /**
76       * Exclude commonly excluded files such as SCM configuration. These are defined in the plexus
77       * FileUtils.getDefaultExcludes()
78       *
79       * @since 2.1
80       */
81      @Parameter( property = "maven.source.useDefaultExcludes", defaultValue = "true" )
82      private boolean useDefaultExcludes;
83  
84      /**
85       * The Maven Project Object
86       */
87      @Parameter( defaultValue = "${project}", readonly = true, required = true )
88      private MavenProject project;
89  
90      /**
91       * The Jar archiver.
92       */
93      @Component( role = Archiver.class, hint = "jar" )
94      private JarArchiver jarArchiver;
95  
96      /**
97       * The archive configuration to use. See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven
98       * Archiver Reference</a>. <br/>
99       * <b>Note: Since 3.0.0 the resulting archives contain a maven descriptor. If you need to suppress the generation of
100      * the maven descriptor you can simply achieve this by using the
101      * <a href="http://maven.apache.org/shared/maven-archiver/index.html#archive">archiver configuration</a>.</b>.
102      * 
103      * @since 2.1
104      */
105     @Parameter
106     private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
107 
108     /**
109      * Path to the default MANIFEST file to use. It will be used if <code>useDefaultManifestFile</code> is set to
110      * <code>true</code>.
111      *
112      * @since 2.1
113      */
114     // CHECKSTYLE_OFF: LineLength
115     @Parameter( defaultValue = "${project.build.outputDirectory}/META-INF/MANIFEST.MF", readonly = false, required = true )
116     // CHECKSTYLE_ON: LineLength
117     private File defaultManifestFile;
118 
119     /**
120      * Set this to <code>true</code> to enable the use of the <code>defaultManifestFile</code>. <br/>
121      *
122      * @since 2.1
123      */
124     @Parameter( property = "maven.source.useDefaultManifestFile", defaultValue = "false" )
125     private boolean useDefaultManifestFile;
126 
127     /**
128      * Specifies whether or not to attach the artifact to the project
129      */
130     @Parameter( property = "maven.source.attach", defaultValue = "true" )
131     private boolean attach;
132 
133     /**
134      * Specifies whether or not to exclude resources from the sources-jar. This can be convenient if your project
135      * includes large resources, such as images, and you don't want to include them in the sources-jar.
136      *
137      * @since 2.0.4
138      */
139     @Parameter( property = "maven.source.excludeResources", defaultValue = "false" )
140     protected boolean excludeResources;
141 
142     /**
143      * Specifies whether or not to include the POM file in the sources-jar.
144      *
145      * @since 2.1
146      */
147     @Parameter( property = "maven.source.includePom", defaultValue = "false" )
148     protected boolean includePom;
149 
150     /**
151      * Used for attaching the source jar to the project.
152      */
153     @Component
154     private MavenProjectHelper projectHelper;
155 
156     /**
157      * The directory where the generated archive file will be put.
158      */
159     @Parameter( defaultValue = "${project.build.directory}" )
160     protected File outputDirectory;
161 
162     /**
163      * The filename to be used for the generated archive file. For the source:jar goal, "-sources" is appended to this
164      * filename. For the source:test-jar goal, "-test-sources" is appended.
165      */
166     @Parameter( defaultValue = "${project.build.finalName}" )
167     protected String finalName;
168 
169     /**
170      * Contains the full list of projects in the reactor.
171      */
172     @Parameter( defaultValue = "${reactorProjects}", readonly = true )
173     protected List<MavenProject> reactorProjects;
174 
175     /**
176      * Whether creating the archive should be forced. If set to true, the jar will always be created. If set to false,
177      * the jar will only be created when the sources are newer than the jar.
178      *
179      * @since 2.1
180      */
181     @Parameter( property = "maven.source.forceCreation", defaultValue = "false" )
182     private boolean forceCreation;
183 
184     /**
185      * A flag used to disable the source procedure. This is primarily intended for usage from the command line to
186      * occasionally adjust the build.
187      *
188      * @since 2.2
189      */
190     @Parameter( property = "maven.source.skip", defaultValue = "false" )
191     private boolean skipSource;
192 
193     /**
194      * The Maven session.
195      */
196     @Parameter( defaultValue = "${session}", readonly = true, required = true )
197     private MavenSession session;
198 
199     // ----------------------------------------------------------------------
200     // Public methods
201     // ----------------------------------------------------------------------
202 
203     /**
204      * {@inheritDoc}
205      */
206     public void execute()
207         throws MojoExecutionException
208     {
209         if ( skipSource )
210         {
211             getLog().info( "Skipping source per configuration." );
212             return;
213         }
214 
215         packageSources( project );
216     }
217 
218     // ----------------------------------------------------------------------
219     // Protected methods
220     // ----------------------------------------------------------------------
221 
222     /**
223      * @return the wanted classifier, ie <code>sources</code> or <code>test-sources</code>
224      */
225     protected abstract String getClassifier();
226 
227     /**
228      * @param p {@link MavenProject} not null
229      * @return the compile or test sources
230      * @throws MojoExecutionException in case of an error.
231      */
232     protected abstract List<String> getSources( MavenProject p )
233         throws MojoExecutionException;
234 
235     /**
236      * @param p {@link MavenProject} not null
237      * @return the compile or test resources
238      * @throws MojoExecutionException in case of an error.
239      */
240     protected abstract List<Resource> getResources( MavenProject p )
241         throws MojoExecutionException;
242 
243     /**
244      * @param p {@link MavenProject}
245      * @throws MojoExecutionException in case of an error.
246      */
247     protected void packageSources( MavenProject p )
248         throws MojoExecutionException
249     {
250         if ( !"pom".equals( p.getPackaging() ) )
251         {
252             packageSources( Arrays.asList( p ) );
253         }
254     }
255 
256     /**
257      * @param theProjects {@link MavenProject}
258      * @throws MojoExecutionException in case of an error.
259      */
260     protected void packageSources( List<MavenProject> theProjects )
261         throws MojoExecutionException
262     {
263         if ( project.getArtifact().getClassifier() != null )
264         {
265             getLog().warn( "NOT adding sources to artifacts with classifier as Maven only supports one classifier "
266                 + "per artifact. Current artifact [" + project.getArtifact().getId() + "] has a ["
267                 + project.getArtifact().getClassifier() + "] classifier." );
268 
269             return;
270         }
271 
272         MavenArchiver archiver = createArchiver();
273 
274         for ( MavenProject pItem : theProjects )
275         {
276             MavenProject subProject = getProject( pItem );
277 
278             if ( "pom".equals( subProject.getPackaging() ) )
279             {
280                 continue;
281             }
282 
283             archiveProjectContent( subProject, archiver.getArchiver() );
284         }
285 
286         if ( archiver.getArchiver().getResources().hasNext() || forceCreation )
287         {
288 
289             if ( useDefaultManifestFile && defaultManifestFile.exists() && archive.getManifestFile() == null )
290             {
291                 getLog().info( "Adding existing MANIFEST to archive. Found under: " + defaultManifestFile.getPath() );
292                 archive.setManifestFile( defaultManifestFile );
293             }
294 
295             File outputFile = new File( outputDirectory, finalName + "-" + getClassifier() + getExtension() );
296 
297             try
298             {
299                 archiver.setOutputFile( outputFile );
300                 archive.setForced( forceCreation );
301 
302                 archiver.createArchive( session, project, archive );
303             }
304             catch ( IOException e )
305             {
306                 throw new MojoExecutionException( "Error creating source archive: " + e.getMessage(), e );
307             }
308             catch ( ArchiverException e )
309             {
310                 throw new MojoExecutionException( "Error creating source archive: " + e.getMessage(), e );
311             }
312             catch ( DependencyResolutionRequiredException e )
313             {
314                 throw new MojoExecutionException( "Error creating source archive: " + e.getMessage(), e );
315             }
316             catch ( ManifestException e )
317             {
318                 throw new MojoExecutionException( "Error creating source archive: " + e.getMessage(), e );
319             }
320 
321             if ( attach )
322             {
323                 projectHelper.attachArtifact( project, getType(), getClassifier(), outputFile );
324             }
325             else
326             {
327                 getLog().info( "NOT adding java-sources to attached artifacts list." );
328             }
329         }
330         else
331         {
332             getLog().info( "No sources in project. Archive not created." );
333         }
334     }
335 
336     /**
337      * @param p {@link MavenProject}
338      * @param archiver {@link Archiver}
339      * @throws MojoExecutionException in case of an error.
340      */
341     protected void archiveProjectContent( MavenProject p, Archiver archiver )
342         throws MojoExecutionException
343     {
344         if ( includePom )
345         {
346             try
347             {
348                 archiver.addFile( p.getFile(), p.getFile().getName() );
349             }
350             catch ( ArchiverException e )
351             {
352                 throw new MojoExecutionException( "Error adding POM file to target jar file.", e );
353             }
354         }
355 
356         for ( String s : getSources( p ) )
357         {
358 
359             File sourceDirectory = new File( s );
360 
361             if ( sourceDirectory.exists() )
362             {
363                 addDirectory( archiver, sourceDirectory, getCombinedIncludes( null ), getCombinedExcludes( null ) );
364             }
365         }
366 
367         // MAPI: this should be taken from the resources plugin
368         for ( Resource resource : getResources( p ) )
369         {
370 
371             File sourceDirectory = new File( resource.getDirectory() );
372 
373             if ( !sourceDirectory.exists() )
374             {
375                 continue;
376             }
377 
378             List<String> resourceIncludes = resource.getIncludes();
379 
380             String[] combinedIncludes = getCombinedIncludes( resourceIncludes );
381 
382             List<String> resourceExcludes = resource.getExcludes();
383 
384             String[] combinedExcludes = getCombinedExcludes( resourceExcludes );
385 
386             String targetPath = resource.getTargetPath();
387             if ( targetPath != null )
388             {
389                 if ( !targetPath.trim().endsWith( "/" ) )
390                 {
391                     targetPath += "/";
392                 }
393                 addDirectory( archiver, sourceDirectory, targetPath, combinedIncludes, combinedExcludes );
394             }
395             else
396             {
397                 addDirectory( archiver, sourceDirectory, combinedIncludes, combinedExcludes );
398             }
399         }
400     }
401 
402     /**
403      * @return {@link MavenArchiver}
404      * @throws MojoExecutionException in case of an error.
405      */
406     protected MavenArchiver createArchiver()
407         throws MojoExecutionException
408     {
409         MavenArchiver archiver = new MavenArchiver();
410         archiver.setArchiver( jarArchiver );
411 
412         if ( project.getBuild() != null )
413         {
414             List<Resource> resources = project.getBuild().getResources();
415 
416             for ( Resource r : resources )
417             {
418 
419                 if ( r.getDirectory().endsWith( "maven-shared-archive-resources" ) )
420                 {
421                     addDirectory( archiver.getArchiver(), new File( r.getDirectory() ), getCombinedIncludes( null ),
422                                   getCombinedExcludes( null ) );
423                 }
424             }
425         }
426 
427         return archiver;
428     }
429 
430     /**
431      * @param archiver {@link Archiver}
432      * @param sourceDirectory {@link File}
433      * @param pIncludes The list of includes.
434      * @param pExcludes The list of excludes.
435      * @throws MojoExecutionException in case of an error.
436      */
437     protected void addDirectory( Archiver archiver, File sourceDirectory, String[] pIncludes, String[] pExcludes )
438         throws MojoExecutionException
439     {
440         try
441         {
442             // archiver.addFileSet( fileSet );
443             archiver.addDirectory( sourceDirectory, pIncludes, pExcludes );
444         }
445         catch ( ArchiverException e )
446         {
447             throw new MojoExecutionException( "Error adding directory to source archive.", e );
448         }
449     }
450 
451     /**
452      * @param archiver {@link Archiver}
453      * @param sourceDirectory {@link File}
454      * @param prefix The prefix.
455      * @param pIncludes the includes.
456      * @param pExcludes the excludes.
457      * @throws MojoExecutionException in case of an error.
458      */
459     protected void addDirectory( Archiver archiver, File sourceDirectory, String prefix, String[] pIncludes,
460                                  String[] pExcludes )
461                                      throws MojoExecutionException
462     {
463         try
464         {
465             archiver.addDirectory( sourceDirectory, prefix, pIncludes, pExcludes );
466         }
467         catch ( ArchiverException e )
468         {
469             throw new MojoExecutionException( "Error adding directory to source archive.", e );
470         }
471     }
472 
473     /**
474      * @return The extension {@code .jar}
475      */
476     protected String getExtension()
477     {
478         return ".jar";
479     }
480 
481     /**
482      * @param p {@link MavenProject}
483      * @return The execution projet.
484      */
485     protected MavenProject getProject( MavenProject p )
486     {
487         if ( p.getExecutionProject() != null )
488         {
489             return p.getExecutionProject();
490         }
491 
492         return p;
493     }
494 
495     /**
496      * @return The type {@code java-source}
497      */
498     protected String getType()
499     {
500         return "java-source";
501     }
502 
503     /**
504      * Combines the includes parameter and additional includes. Defaults to {@link #DEFAULT_INCLUDES} If the
505      * additionalIncludes parameter is null, it is not added to the combined includes.
506      *
507      * @param additionalIncludes The includes specified in the pom resources section
508      * @return The combined array of includes.
509      */
510     private String[] getCombinedIncludes( List<String> additionalIncludes )
511     {
512         List<String> combinedIncludes = new ArrayList<String>();
513 
514         if ( includes != null && includes.length > 0 )
515         {
516             combinedIncludes.addAll( Arrays.asList( includes ) );
517         }
518 
519         if ( additionalIncludes != null && additionalIncludes.size() > 0 )
520         {
521             combinedIncludes.addAll( additionalIncludes );
522         }
523 
524         // If there are no other includes, use the default.
525         if ( combinedIncludes.size() == 0 )
526         {
527             combinedIncludes.addAll( Arrays.asList( DEFAULT_INCLUDES ) );
528         }
529 
530         return combinedIncludes.toArray( new String[combinedIncludes.size()] );
531     }
532 
533     /**
534      * Combines the user parameter {@link #excludes}, the default excludes from plexus FileUtils, and the contents of
535      * the parameter addionalExcludes.
536      *
537      * @param additionalExcludes Additional excludes to add to the array
538      * @return The combined list of excludes.
539      */
540 
541     private String[] getCombinedExcludes( List<String> additionalExcludes )
542     {
543         List<String> combinedExcludes = new ArrayList<String>();
544 
545         if ( useDefaultExcludes )
546         {
547             combinedExcludes.addAll( FileUtils.getDefaultExcludesAsList() );
548         }
549 
550         if ( excludes != null && excludes.length > 0 )
551         {
552             combinedExcludes.addAll( Arrays.asList( excludes ) );
553         }
554 
555         if ( additionalExcludes != null && additionalExcludes.size() > 0 )
556         {
557             combinedExcludes.addAll( additionalExcludes );
558         }
559 
560         if ( combinedExcludes.size() == 0 )
561         {
562             combinedExcludes.addAll( Arrays.asList( DEFAULT_EXCLUDES ) );
563         }
564 
565         return combinedExcludes.toArray( new String[combinedExcludes.size()] );
566     }
567 
568     /**
569      * @return The current project.
570      */
571     protected MavenProject getProject()
572     {
573         return project;
574     }
575 
576     /**
577      * @param project {@link MavenProject}
578      */
579     protected void setProject( MavenProject project )
580     {
581         this.project = project;
582     }
583 }