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      * Timestamp for reproducible output archive entries, either formatted as ISO 8601
201      * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
202      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
203      *
204      * @since 3.2.0
205      */
206     @Parameter( defaultValue = "${project.build.outputTimestamp}" )
207     private String outputTimestamp;
208 
209     // ----------------------------------------------------------------------
210     // Public methods
211     // ----------------------------------------------------------------------
212 
213     /**
214      * {@inheritDoc}
215      */
216     public void execute()
217         throws MojoExecutionException
218     {
219         if ( skipSource )
220         {
221             getLog().info( "Skipping source per configuration." );
222             return;
223         }
224 
225         packageSources( project );
226     }
227 
228     // ----------------------------------------------------------------------
229     // Protected methods
230     // ----------------------------------------------------------------------
231 
232     /**
233      * @return the wanted classifier, ie <code>sources</code> or <code>test-sources</code>
234      */
235     protected abstract String getClassifier();
236 
237     /**
238      * @param p {@link MavenProject} not null
239      * @return the compile or test sources
240      * @throws MojoExecutionException in case of an error.
241      */
242     protected abstract List<String> getSources( MavenProject p )
243         throws MojoExecutionException;
244 
245     /**
246      * @param p {@link MavenProject} not null
247      * @return the compile or test resources
248      * @throws MojoExecutionException in case of an error.
249      */
250     protected abstract List<Resource> getResources( MavenProject p )
251         throws MojoExecutionException;
252 
253     /**
254      * @param p {@link MavenProject}
255      * @throws MojoExecutionException in case of an error.
256      */
257     protected void packageSources( MavenProject p )
258         throws MojoExecutionException
259     {
260         if ( !"pom".equals( p.getPackaging() ) )
261         {
262             packageSources( Arrays.asList( p ) );
263         }
264     }
265 
266     /**
267      * @param theProjects {@link MavenProject}
268      * @throws MojoExecutionException in case of an error.
269      */
270     protected void packageSources( List<MavenProject> theProjects )
271         throws MojoExecutionException
272     {
273         if ( project.getArtifact().getClassifier() != null )
274         {
275             getLog().warn( "NOT adding sources to artifacts with classifier as Maven only supports one classifier "
276                 + "per artifact. Current artifact [" + project.getArtifact().getId() + "] has a ["
277                 + project.getArtifact().getClassifier() + "] classifier." );
278 
279             return;
280         }
281 
282         MavenArchiver archiver = createArchiver();
283         
284         // configure for Reproducible Builds based on outputTimestamp value
285         archiver.configureReproducible( outputTimestamp );
286 
287         for ( MavenProject pItem : theProjects )
288         {
289             MavenProject subProject = getProject( pItem );
290 
291             if ( "pom".equals( subProject.getPackaging() ) )
292             {
293                 continue;
294             }
295 
296             archiveProjectContent( subProject, archiver.getArchiver() );
297         }
298 
299         if ( archiver.getArchiver().getResources().hasNext() || forceCreation )
300         {
301 
302             if ( useDefaultManifestFile && defaultManifestFile.exists() && archive.getManifestFile() == null )
303             {
304                 getLog().info( "Adding existing MANIFEST to archive. Found under: " + defaultManifestFile.getPath() );
305                 archive.setManifestFile( defaultManifestFile );
306             }
307 
308             File outputFile = new File( outputDirectory, finalName + "-" + getClassifier() + getExtension() );
309 
310             try
311             {
312                 archiver.setOutputFile( outputFile );
313                 archive.setForced( forceCreation );
314 
315                 archiver.createArchive( session, project, archive );
316             }
317             catch ( IOException e )
318             {
319                 throw new MojoExecutionException( "Error creating source archive: " + e.getMessage(), e );
320             }
321             catch ( ArchiverException e )
322             {
323                 throw new MojoExecutionException( "Error creating source archive: " + e.getMessage(), e );
324             }
325             catch ( DependencyResolutionRequiredException e )
326             {
327                 throw new MojoExecutionException( "Error creating source archive: " + e.getMessage(), e );
328             }
329             catch ( ManifestException e )
330             {
331                 throw new MojoExecutionException( "Error creating source archive: " + e.getMessage(), e );
332             }
333 
334             if ( attach )
335             {
336                 projectHelper.attachArtifact( project, getType(), getClassifier(), outputFile );
337             }
338             else
339             {
340                 getLog().info( "NOT adding java-sources to attached artifacts list." );
341             }
342         }
343         else
344         {
345             getLog().info( "No sources in project. Archive not created." );
346         }
347     }
348 
349     /**
350      * @param p {@link MavenProject}
351      * @param archiver {@link Archiver}
352      * @throws MojoExecutionException in case of an error.
353      */
354     protected void archiveProjectContent( MavenProject p, Archiver archiver )
355         throws MojoExecutionException
356     {
357         if ( includePom )
358         {
359             try
360             {
361                 archiver.addFile( p.getFile(), p.getFile().getName() );
362             }
363             catch ( ArchiverException e )
364             {
365                 throw new MojoExecutionException( "Error adding POM file to target jar file.", e );
366             }
367         }
368 
369         for ( String s : getSources( p ) )
370         {
371 
372             File sourceDirectory = new File( s );
373 
374             if ( sourceDirectory.exists() )
375             {
376                 addDirectory( archiver, sourceDirectory, getCombinedIncludes( null ), getCombinedExcludes( null ) );
377             }
378         }
379 
380         // MAPI: this should be taken from the resources plugin
381         for ( Resource resource : getResources( p ) )
382         {
383 
384             File sourceDirectory = new File( resource.getDirectory() );
385 
386             if ( !sourceDirectory.exists() )
387             {
388                 continue;
389             }
390 
391             List<String> resourceIncludes = resource.getIncludes();
392 
393             String[] combinedIncludes = getCombinedIncludes( resourceIncludes );
394 
395             List<String> resourceExcludes = resource.getExcludes();
396 
397             String[] combinedExcludes = getCombinedExcludes( resourceExcludes );
398 
399             String targetPath = resource.getTargetPath();
400             if ( targetPath != null )
401             {
402                 if ( !targetPath.trim().endsWith( "/" ) )
403                 {
404                     targetPath += "/";
405                 }
406                 addDirectory( archiver, sourceDirectory, targetPath, combinedIncludes, combinedExcludes );
407             }
408             else
409             {
410                 addDirectory( archiver, sourceDirectory, combinedIncludes, combinedExcludes );
411             }
412         }
413     }
414 
415     /**
416      * @return {@link MavenArchiver}
417      * @throws MojoExecutionException in case of an error.
418      */
419     protected MavenArchiver createArchiver()
420         throws MojoExecutionException
421     {
422         MavenArchiver archiver = new MavenArchiver();
423         archiver.setArchiver( jarArchiver );
424         archiver.setCreatedBy( "Maven Source Plugin", "org.apache.maven.plugins", "maven-source-plugin" );
425         archiver.setBuildJdkSpecDefaultEntry( false );
426 
427         if ( project.getBuild() != null )
428         {
429             List<Resource> resources = project.getBuild().getResources();
430 
431             for ( Resource r : resources )
432             {
433 
434                 if ( r.getDirectory().endsWith( "maven-shared-archive-resources" ) )
435                 {
436                     addDirectory( archiver.getArchiver(), new File( r.getDirectory() ), getCombinedIncludes( null ),
437                                   getCombinedExcludes( null ) );
438                 }
439             }
440         }
441 
442         return archiver;
443     }
444 
445     /**
446      * @param archiver {@link Archiver}
447      * @param sourceDirectory {@link File}
448      * @param pIncludes The list of includes.
449      * @param pExcludes The list of excludes.
450      * @throws MojoExecutionException in case of an error.
451      */
452     protected void addDirectory( Archiver archiver, File sourceDirectory, String[] pIncludes, String[] pExcludes )
453         throws MojoExecutionException
454     {
455         try
456         {
457             // archiver.addFileSet( fileSet );
458             archiver.addDirectory( sourceDirectory, pIncludes, pExcludes );
459         }
460         catch ( ArchiverException e )
461         {
462             throw new MojoExecutionException( "Error adding directory to source archive.", e );
463         }
464     }
465 
466     /**
467      * @param archiver {@link Archiver}
468      * @param sourceDirectory {@link File}
469      * @param prefix The prefix.
470      * @param pIncludes the includes.
471      * @param pExcludes the excludes.
472      * @throws MojoExecutionException in case of an error.
473      */
474     protected void addDirectory( Archiver archiver, File sourceDirectory, String prefix, String[] pIncludes,
475                                  String[] pExcludes )
476                                      throws MojoExecutionException
477     {
478         try
479         {
480             archiver.addDirectory( sourceDirectory, prefix, pIncludes, pExcludes );
481         }
482         catch ( ArchiverException e )
483         {
484             throw new MojoExecutionException( "Error adding directory to source archive.", e );
485         }
486     }
487 
488     /**
489      * @return The extension {@code .jar}
490      */
491     protected String getExtension()
492     {
493         return ".jar";
494     }
495 
496     /**
497      * @param p {@link MavenProject}
498      * @return The execution projet.
499      */
500     protected MavenProject getProject( MavenProject p )
501     {
502         if ( p.getExecutionProject() != null )
503         {
504             return p.getExecutionProject();
505         }
506 
507         return p;
508     }
509 
510     /**
511      * @return The type {@code java-source}
512      */
513     protected String getType()
514     {
515         return "java-source";
516     }
517 
518     /**
519      * Combines the includes parameter and additional includes. Defaults to {@link #DEFAULT_INCLUDES} If the
520      * additionalIncludes parameter is null, it is not added to the combined includes.
521      *
522      * @param additionalIncludes The includes specified in the pom resources section
523      * @return The combined array of includes.
524      */
525     private String[] getCombinedIncludes( List<String> additionalIncludes )
526     {
527         List<String> combinedIncludes = new ArrayList<String>();
528 
529         if ( includes != null && includes.length > 0 )
530         {
531             combinedIncludes.addAll( Arrays.asList( includes ) );
532         }
533 
534         if ( additionalIncludes != null && additionalIncludes.size() > 0 )
535         {
536             combinedIncludes.addAll( additionalIncludes );
537         }
538 
539         // If there are no other includes, use the default.
540         if ( combinedIncludes.size() == 0 )
541         {
542             combinedIncludes.addAll( Arrays.asList( DEFAULT_INCLUDES ) );
543         }
544 
545         return combinedIncludes.toArray( new String[combinedIncludes.size()] );
546     }
547 
548     /**
549      * Combines the user parameter {@link #excludes}, the default excludes from plexus FileUtils, and the contents of
550      * the parameter addionalExcludes.
551      *
552      * @param additionalExcludes Additional excludes to add to the array
553      * @return The combined list of excludes.
554      */
555 
556     private String[] getCombinedExcludes( List<String> additionalExcludes )
557     {
558         List<String> combinedExcludes = new ArrayList<String>();
559 
560         if ( useDefaultExcludes )
561         {
562             combinedExcludes.addAll( FileUtils.getDefaultExcludesAsList() );
563         }
564 
565         if ( excludes != null && excludes.length > 0 )
566         {
567             combinedExcludes.addAll( Arrays.asList( excludes ) );
568         }
569 
570         if ( additionalExcludes != null && additionalExcludes.size() > 0 )
571         {
572             combinedExcludes.addAll( additionalExcludes );
573         }
574 
575         if ( combinedExcludes.size() == 0 )
576         {
577             combinedExcludes.addAll( Arrays.asList( DEFAULT_EXCLUDES ) );
578         }
579 
580         return combinedExcludes.toArray( new String[combinedExcludes.size()] );
581     }
582 
583     /**
584      * @return The current project.
585      */
586     protected MavenProject getProject()
587     {
588         return project;
589     }
590 
591     /**
592      * @param project {@link MavenProject}
593      */
594     protected void setProject( MavenProject project )
595     {
596         this.project = project;
597     }
598 }