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