View Javadoc
1   package org.apache.maven.plugin.assembly.archive.phase;
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.Collections;
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.LinkedHashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Set;
32  
33  import javax.annotation.Nonnull;
34  
35  import org.apache.maven.artifact.Artifact;
36  import org.apache.maven.artifact.ArtifactUtils;
37  import org.apache.maven.plugin.assembly.AssemblerConfigurationSource;
38  import org.apache.maven.plugin.assembly.InvalidAssemblerConfigurationException;
39  import org.apache.maven.plugin.assembly.archive.ArchiveCreationException;
40  import org.apache.maven.plugin.assembly.archive.task.AddArtifactTask;
41  import org.apache.maven.plugin.assembly.archive.task.AddDependencySetsTask;
42  import org.apache.maven.plugin.assembly.archive.task.AddFileSetsTask;
43  import org.apache.maven.plugin.assembly.format.AssemblyFormattingException;
44  import org.apache.maven.plugin.assembly.model.DependencySet;
45  import org.apache.maven.plugin.assembly.model.FileSet;
46  import org.apache.maven.plugin.assembly.model.ModuleBinaries;
47  import org.apache.maven.plugin.assembly.model.ModuleSet;
48  import org.apache.maven.plugin.assembly.model.ModuleSources;
49  import org.apache.maven.plugin.assembly.resolved.ResolvedAssembly;
50  import org.apache.maven.plugin.assembly.resolved.ResolvedModuleSet;
51  import org.apache.maven.plugin.assembly.resolved.functions.ResolvedModuleSetConsumer;
52  import org.apache.maven.plugin.assembly.utils.AssemblyFormatUtils;
53  import org.apache.maven.plugin.assembly.utils.FilterUtils;
54  import org.apache.maven.plugin.assembly.utils.ProjectUtils;
55  import org.apache.maven.plugin.assembly.utils.TypeConversionUtils;
56  import org.apache.maven.project.MavenProject;
57  import org.apache.maven.project.MavenProjectBuilder;
58  import org.codehaus.plexus.archiver.Archiver;
59  import org.codehaus.plexus.archiver.manager.ArchiverManager;
60  import org.codehaus.plexus.component.annotations.Component;
61  import org.codehaus.plexus.component.annotations.Requirement;
62  import org.codehaus.plexus.logging.AbstractLogEnabled;
63  import org.codehaus.plexus.logging.Logger;
64  
65  /**
66   * Handles the <moduleSets/> top-level section of the assembly descriptor.
67   * 
68   * @version $Id: ModuleSetAssemblyPhase.java 1633832 2014-10-23 15:03:47Z krosenvold $
69   */
70  @Component( role = AssemblyArchiverPhase.class, hint = "module-sets" )
71  public class ModuleSetAssemblyPhase
72      extends AbstractLogEnabled
73      implements AssemblyArchiverPhase
74  {
75  
76      // TODO: Remove if using something like commons-lang instead.
77      /**
78       * The line separator.
79       */
80      private static final String LINE_SEPARATOR = System.getProperty( "line.separator" );
81  
82      @Requirement
83      private MavenProjectBuilder projectBuilder;
84  
85      @Requirement
86      private ArchiverManager archiverManager;
87  
88      /**
89       * Create an instance.
90       */
91      public ModuleSetAssemblyPhase()
92      {
93          // needed for plexus
94      }
95  
96      /**
97       * @param projectBuilder The project builder.
98       * @param logger The logger.
99       */
100     public ModuleSetAssemblyPhase( final MavenProjectBuilder projectBuilder, final Logger logger )
101     {
102         this.projectBuilder = projectBuilder;
103         enableLogging( logger );
104     }
105 
106     /**
107      * {@inheritDoc}
108      */
109     public void execute( final ResolvedAssembly assembly, final Archiver archiver,
110                          final AssemblerConfigurationSource configSource )
111         throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException
112     {
113         assembly.forEachResolvedModule( new ResolvedModuleSetConsumer()
114         {
115             public void accept( ResolvedModuleSet resolvedModule )
116                 throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException
117             {
118                 validate( resolvedModule.getModuleSet(), configSource );
119 
120                 final Set<MavenProject> moduleProjects =
121                     getModuleProjects( resolvedModule.getModuleSet(), configSource, getLogger() );
122 
123                 final ModuleSources sources = resolvedModule.getModuleSet().getSources();
124                 addModuleSourceFileSets( sources, moduleProjects, archiver, configSource );
125 
126                 final ModuleBinaries binaries = resolvedModule.getModuleSet().getBinaries();
127                 addModuleBinaries( resolvedModule, binaries, moduleProjects, archiver, configSource );
128             }
129         } );
130     }
131 
132     private void validate( final ModuleSet moduleSet, final AssemblerConfigurationSource configSource )
133     {
134         if ( ( moduleSet.getSources() == null ) && ( moduleSet.getBinaries() == null ) )
135         {
136             getLogger().warn( "Encountered ModuleSet with no sources or binaries specified. Skipping." );
137         }
138 
139         if ( moduleSet.isUseAllReactorProjects() && !moduleSet.isIncludeSubModules() )
140         {
141             getLogger().warn( "includeSubModules == false is incompatible with useAllReactorProjects. Ignoring."
142                                   + "\n\nTo refactor, remove the <includeSubModules/> flag, and use the <includes/> "
143                                   + "and <excludes/> sections to fine-tune the modules included." );
144         }
145 
146         final List<MavenProject> projects = configSource.getReactorProjects();
147         if ( projects != null && projects.size() > 1 && projects.indexOf( configSource.getProject() ) == 0
148             && moduleSet.getBinaries() != null )
149         {
150             getLogger().warn( "[DEPRECATION] moduleSet/binaries section detected in root-project assembly."
151                                   + "\n\nMODULE BINARIES MAY NOT BE AVAILABLE FOR THIS ASSEMBLY!"
152                                   + "\n\n To refactor, move this assembly into a child project and use the flag "
153                                   + "<useAllReactorProjects>true</useAllReactorProjects> in each moduleSet." );
154         }
155 
156         if ( moduleSet.getSources() != null )
157         {
158             final ModuleSources sources = moduleSet.getSources();
159             if ( isDeprecatedModuleSourcesConfigPresent( sources ) )
160             {
161                 getLogger().warn( "[DEPRECATION] Use of <moduleSources/> as a file-set is deprecated. "
162                                       + "Please use the <fileSets/> sub-element of <moduleSources/> instead." );
163             }
164             else if ( !sources.isUseDefaultExcludes() )
165             {
166                 getLogger().warn( "[DEPRECATION] Use of directoryMode, fileMode, or useDefaultExcludes "
167                                       + "elements directly within <moduleSources/> are all deprecated. "
168                                       + "Please use the <fileSets/> sub-element of <moduleSources/> instead." );
169             }
170         }
171     }
172 
173     void addModuleBinaries( ResolvedModuleSet resolvedModule, final ModuleBinaries binaries,
174                             final Set<MavenProject> projects, final Archiver archiver,
175                             final AssemblerConfigurationSource configSource )
176         throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException
177     {
178         if ( binaries == null )
179         {
180             return;
181         }
182 
183         final Set<MavenProject> moduleProjects = new LinkedHashSet<MavenProject>( projects );
184 
185         for ( final Iterator<MavenProject> it = moduleProjects.iterator(); it.hasNext(); )
186         {
187             final MavenProject project = it.next();
188 
189             if ( "pom".equals( project.getPackaging() ) )
190             {
191                 final String projectId = ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() );
192 
193                 getLogger().debug( "Excluding POM-packaging module: " + projectId );
194 
195                 it.remove();
196             }
197         }
198 
199         final String classifier = binaries.getAttachmentClassifier();
200 
201         final Map<MavenProject, Artifact> chosenModuleArtifacts = new HashMap<MavenProject, Artifact>();
202 
203         for ( final MavenProject project : moduleProjects )
204         {
205             Artifact artifact = null;
206 
207             if ( classifier == null )
208             {
209                 getLogger().debug( "Processing binary artifact for module project: " + project.getId() );
210 
211                 artifact = project.getArtifact();
212             }
213             else
214             {
215                 getLogger().debug( "Processing binary attachment: " + classifier + " for module project: "
216                                        + project.getId() );
217 
218                 @SuppressWarnings( "unchecked" )
219                 final List<Artifact> attachments = project.getAttachedArtifacts();
220                 if ( ( attachments != null ) && !attachments.isEmpty() )
221                 {
222                     for ( final Artifact attachment : attachments )
223                     {
224                         if ( classifier.equals( attachment.getClassifier() ) )
225                         {
226                             artifact = attachment;
227                             break;
228                         }
229                     }
230                 }
231 
232                 if ( artifact == null )
233                 {
234                     throw new InvalidAssemblerConfigurationException( "Cannot find attachment with classifier: "
235                         + classifier + " in module project: " + project.getId()
236                         + ". Please exclude this module from the module-set." );
237                 }
238             }
239 
240             chosenModuleArtifacts.put( project, artifact );
241             addModuleArtifact( artifact, project, archiver, configSource, binaries );
242         }
243 
244         final List<DependencySet> depSets = getDependencySets( binaries );
245 
246         if ( depSets != null )
247         {
248             for ( final DependencySet ds : depSets )
249             {
250                 // NOTE: Disabling useProjectArtifact flag, since module artifact has already been handled!
251                 ds.setUseProjectArtifact( false );
252             }
253 
254             // TODO: The following should be moved into a shared component, cause this
255             // test is the same as in maven-enforce rules ReactorModuleConvergence.
256             List<MavenProject> validateModuleVersions = validateModuleVersions( moduleProjects );
257             if ( !validateModuleVersions.isEmpty() )
258             {
259 
260                 StringBuilder sb =
261                     new StringBuilder().append( "The current modules seemed to be having different versions." );
262                 sb.append( LINE_SEPARATOR );
263                 for ( MavenProject mavenProject : validateModuleVersions )
264                 {
265                     sb.append( " --> " );
266                     sb.append( mavenProject.getId() );
267                     sb.append( LINE_SEPARATOR );
268                 }
269                 getLogger().warn( sb.toString() );
270             }
271 
272             for ( final MavenProject moduleProject : moduleProjects )
273             {
274                 getLogger().debug( "Processing binary dependencies for module project: " + moduleProject.getId() );
275 
276                 final AddDependencySetsTask task =
277                     new AddDependencySetsTask( depSets, resolvedModule.getArtifacts(), moduleProject, projectBuilder,
278                                                archiverManager, getLogger() );
279 
280                 task.setModuleProject( moduleProject );
281                 task.setModuleArtifact( chosenModuleArtifacts.get( moduleProject ) );
282                 task.setDefaultOutputDirectory( binaries.getOutputDirectory() );
283                 task.setDefaultOutputFileNameMapping( binaries.getOutputFileNameMapping() );
284 
285                 task.execute( archiver, configSource );
286             }
287         }
288     }
289 
290     private List<MavenProject> validateModuleVersions( Set<MavenProject> moduleProjects )
291     {
292         List<MavenProject> result = new ArrayList<MavenProject>();
293 
294         if ( moduleProjects != null && !moduleProjects.isEmpty() )
295         {
296             String version = moduleProjects.iterator().next().getVersion();
297             getLogger().debug( "First version:" + version );
298             for ( MavenProject mavenProject : moduleProjects )
299             {
300                 getLogger().debug( " -> checking " + mavenProject.getId() );
301                 if ( !version.equals( mavenProject.getVersion() ) )
302                 {
303                     result.add( mavenProject );
304                 }
305             }
306         }
307         return result;
308     }
309 
310     public static List<DependencySet> getDependencySets( final ModuleBinaries binaries )
311     {
312         List<DependencySet> depSets = binaries.getDependencySets();
313 
314         if ( ( ( depSets == null ) || depSets.isEmpty() ) && binaries.isIncludeDependencies() )
315         {
316             final DependencySet impliedDependencySet = new DependencySet();
317 
318             impliedDependencySet.setOutputDirectory( binaries.getOutputDirectory() );
319             impliedDependencySet.setOutputFileNameMapping( binaries.getOutputFileNameMapping() );
320             impliedDependencySet.setFileMode( binaries.getFileMode() );
321             impliedDependencySet.setDirectoryMode( binaries.getDirectoryMode() );
322             impliedDependencySet.setExcludes( binaries.getExcludes() );
323             impliedDependencySet.setIncludes( binaries.getIncludes() );
324             impliedDependencySet.setUnpack( binaries.isUnpack() );
325             // unpackOptions is handled in the first stage of dependency-set handling, below.
326 
327             depSets = Collections.singletonList( impliedDependencySet );
328         }
329 
330         return depSets;
331     }
332 
333     // protected List<String> collectExcludesFromQueuedArtifacts( final Set visitedArtifacts, final List binaryExcludes
334     // )
335     // {
336     // List excludes = binaryExcludes;
337     //
338     // if ( excludes == null )
339     // {
340     // excludes = new ArrayList();
341     // }
342     // else
343     // {
344     // excludes = new ArrayList( excludes );
345     // }
346     //
347     // for ( final Iterator it = visitedArtifacts.iterator(); it.hasNext(); )
348     // {
349     // excludes.add( it.next() );
350     // }
351     //
352     // return excludes;
353     // }
354 
355     void addModuleArtifact( final Artifact artifact, final MavenProject project, final Archiver archiver,
356                             final AssemblerConfigurationSource configSource, final ModuleBinaries binaries )
357         throws ArchiveCreationException, AssemblyFormattingException
358     {
359         if ( artifact.getFile() == null )
360         {
361             throw new ArchiveCreationException( "Artifact: " + artifact.getId()
362                 + " (included by module) does not have an artifact with a file. "
363                 + "Please ensure the package phase is run before the assembly is generated." );
364         }
365 
366         final AddArtifactTask task = new AddArtifactTask( artifact, getLogger() );
367 
368         task.setFileNameMapping( binaries.getOutputFileNameMapping() );
369         task.setOutputDirectory( binaries.getOutputDirectory() );
370         task.setProject( project );
371         task.setModuleProject( project );
372         task.setModuleArtifact( artifact );
373 
374         final int dirMode = TypeConversionUtils.modeToInt( binaries.getDirectoryMode(), getLogger() );
375         if ( dirMode != -1 )
376         {
377             task.setDirectoryMode( dirMode );
378         }
379 
380         final int fileMode = TypeConversionUtils.modeToInt( binaries.getFileMode(), getLogger() );
381         if ( fileMode != -1 )
382         {
383             task.setFileMode( fileMode );
384         }
385 
386         task.setUnpack( binaries.isUnpack() );
387 
388         if ( binaries.isUnpack() && binaries.getUnpackOptions() != null )
389         {
390             task.setIncludes( binaries.getUnpackOptions().getIncludes() );
391             task.setExcludes( binaries.getUnpackOptions().getExcludes() );
392         }
393 
394         task.execute( archiver, configSource );
395     }
396 
397     void addModuleSourceFileSets( final ModuleSources sources, final Set<MavenProject> moduleProjects,
398                                   final Archiver archiver, final AssemblerConfigurationSource configSource )
399         throws ArchiveCreationException, AssemblyFormattingException
400     {
401         if ( sources == null )
402         {
403             return;
404         }
405 
406         final List<FileSet> fileSets = new ArrayList<FileSet>();
407 
408         if ( isDeprecatedModuleSourcesConfigPresent( sources ) )
409         {
410             final FileSet fs = new FileSet();
411             fs.setOutputDirectory( sources.getOutputDirectory() );
412             fs.setIncludes( sources.getIncludes() );
413             fs.setExcludes( sources.getExcludes() );
414             fs.setUseDefaultExcludes( sources.isUseDefaultExcludes() );
415 
416             fileSets.add( fs );
417         }
418 
419         List<FileSet> subFileSets = sources.getFileSets();
420 
421         if ( ( subFileSets == null ) || subFileSets.isEmpty() )
422         {
423             final FileSet fs = new FileSet();
424             fs.setDirectory( "src" );
425 
426             subFileSets = Collections.singletonList( fs );
427         }
428 
429         fileSets.addAll( subFileSets );
430 
431         for ( final MavenProject moduleProject : moduleProjects )
432         {
433             getLogger().info( "Processing sources for module project: " + moduleProject.getId() );
434 
435             final List<FileSet> moduleFileSets = new ArrayList<FileSet>();
436 
437             for ( final FileSet fileSet : fileSets )
438             {
439                 moduleFileSets.add( createFileSet( fileSet, sources, moduleProject, configSource ) );
440             }
441 
442             final AddFileSetsTask task = new AddFileSetsTask( moduleFileSets );
443 
444             task.setProject( moduleProject );
445             task.setModuleProject( moduleProject );
446             task.setLogger( getLogger() );
447 
448             task.execute( archiver, configSource );
449         }
450     }
451 
452     /**
453      * Determine whether the deprecated file-set configuration directly within the ModuleSources object is present.
454      */
455     boolean isDeprecatedModuleSourcesConfigPresent( @Nonnull final ModuleSources sources )
456     {
457         boolean result = false;
458 
459         if ( sources.getOutputDirectory() != null )
460         {
461             result = true;
462         }
463         else if ( ( sources.getIncludes() != null ) && !sources.getIncludes().isEmpty() )
464         {
465             result = true;
466         }
467         else if ( ( sources.getExcludes() != null ) && !sources.getExcludes().isEmpty() )
468         {
469             result = true;
470         }
471 
472         return result;
473     }
474 
475     @Nonnull
476     FileSet createFileSet( @Nonnull final FileSet fileSet, @Nonnull final ModuleSources sources,
477                            @Nonnull final MavenProject moduleProject,
478                            @Nonnull final AssemblerConfigurationSource configSource )
479         throws AssemblyFormattingException
480     {
481         final FileSet fs = new FileSet();
482 
483         String sourcePath = fileSet.getDirectory();
484 
485         final File moduleBasedir = moduleProject.getBasedir();
486 
487         if ( sourcePath != null )
488         {
489             final File sourceDir = new File( sourcePath );
490 
491             if ( !sourceDir.isAbsolute() )
492             {
493                 sourcePath = new File( moduleBasedir, sourcePath ).getAbsolutePath();
494             }
495         }
496         else
497         {
498             sourcePath = moduleBasedir.getAbsolutePath();
499         }
500 
501         fs.setDirectory( sourcePath );
502         fs.setDirectoryMode( fileSet.getDirectoryMode() );
503 
504         final List<String> excludes = new ArrayList<String>();
505 
506         final List<String> originalExcludes = fileSet.getExcludes();
507         if ( ( originalExcludes != null ) && !originalExcludes.isEmpty() )
508         {
509             excludes.addAll( originalExcludes );
510         }
511 
512         if ( sources.isExcludeSubModuleDirectories() )
513         {
514             @SuppressWarnings( "unchecked" )
515             final List<String> modules = moduleProject.getModules();
516             for ( final String moduleSubPath : modules )
517             {
518                 excludes.add( moduleSubPath + "/**" );
519             }
520         }
521 
522         fs.setExcludes( excludes );
523         fs.setFiltered( fileSet.isFiltered() );
524         fs.setFileMode( fileSet.getFileMode() );
525         fs.setIncludes( fileSet.getIncludes() );
526         fs.setLineEnding( fileSet.getLineEnding() );
527 
528         String destPathPrefix = "";
529         if ( sources.isIncludeModuleDirectory() )
530         {
531             destPathPrefix =
532                 AssemblyFormatUtils.evaluateFileNameMapping( sources.getOutputDirectoryMapping(),
533                                                              moduleProject.getArtifact(), configSource.getProject(),
534                                                              moduleProject, moduleProject.getArtifact(), moduleProject,
535                                                              configSource );
536 
537             if ( !destPathPrefix.endsWith( "/" ) )
538             {
539                 destPathPrefix += "/";
540             }
541         }
542 
543         String destPath = fileSet.getOutputDirectory();
544 
545         if ( destPath == null )
546         {
547             destPath = destPathPrefix;
548         }
549         else
550         {
551             destPath = destPathPrefix + destPath;
552         }
553 
554         destPath =
555             AssemblyFormatUtils.getOutputDirectory( destPath, configSource.getProject(), moduleProject, moduleProject,
556                                                     configSource.getFinalName(), configSource );
557 
558         fs.setOutputDirectory( destPath );
559 
560         getLogger().debug( "module source directory is: " + sourcePath );
561         getLogger().debug( "module dest directory is: " + destPath + " (assembly basedir may be prepended)" );
562 
563         return fs;
564     }
565 
566     @Nonnull
567     public static Set<MavenProject> getModuleProjects( final ModuleSet moduleSet,
568                                                        final AssemblerConfigurationSource configSource,
569                                                        final Logger logger )
570         throws ArchiveCreationException
571     {
572         MavenProject project = configSource.getProject();
573         Set<MavenProject> moduleProjects = null;
574 
575         if ( moduleSet.isUseAllReactorProjects() )
576         {
577             if ( !moduleSet.isIncludeSubModules() )
578             {
579                 moduleProjects = new LinkedHashSet<MavenProject>( configSource.getReactorProjects() );
580             }
581 
582             project = configSource.getReactorProjects().get( 0 );
583         }
584 
585         if ( moduleProjects == null )
586         {
587             try
588             {
589                 moduleProjects =
590                     ProjectUtils.getProjectModules( project, configSource.getReactorProjects(),
591                                                     moduleSet.isIncludeSubModules(), logger );
592             }
593             catch ( final IOException e )
594             {
595                 throw new ArchiveCreationException( "Error retrieving module-set for project: " + project.getId()
596                     + ": " + e.getMessage(), e );
597             }
598         }
599 
600         FilterUtils.filterProjects( moduleProjects, moduleSet.getIncludes(), moduleSet.getExcludes(), true, logger );
601         return moduleProjects;
602     }
603 
604 }