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