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