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