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