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