1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.assembly.archive.task;
20
21 import java.io.File;
22 import java.nio.charset.Charset;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashSet;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Set;
29
30 import org.apache.maven.artifact.Artifact;
31 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
32 import org.apache.maven.model.Dependency;
33 import org.apache.maven.model.Model;
34 import org.apache.maven.plugins.assembly.AssemblerConfigurationSource;
35 import org.apache.maven.plugins.assembly.InvalidAssemblerConfigurationException;
36 import org.apache.maven.plugins.assembly.archive.ArchiveCreationException;
37 import org.apache.maven.plugins.assembly.format.AssemblyFormattingException;
38 import org.apache.maven.plugins.assembly.format.ReaderFormatter;
39 import org.apache.maven.plugins.assembly.model.DependencySet;
40 import org.apache.maven.plugins.assembly.model.UnpackOptions;
41 import org.apache.maven.plugins.assembly.utils.AssemblyFormatUtils;
42 import org.apache.maven.plugins.assembly.utils.FilterUtils;
43 import org.apache.maven.plugins.assembly.utils.TypeConversionUtils;
44 import org.apache.maven.project.DefaultProjectBuildingRequest;
45 import org.apache.maven.project.MavenProject;
46 import org.apache.maven.project.ProjectBuilder;
47 import org.apache.maven.project.ProjectBuildingException;
48 import org.apache.maven.project.ProjectBuildingRequest;
49 import org.apache.maven.project.ProjectBuildingResult;
50 import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
51 import org.apache.maven.shared.artifact.filter.resolve.transform.ArtifactIncludeFilterTransformer;
52 import org.codehaus.plexus.archiver.Archiver;
53 import org.codehaus.plexus.archiver.ArchiverException;
54 import org.codehaus.plexus.components.io.functions.InputStreamTransformer;
55 import org.codehaus.plexus.interpolation.fixed.FixedStringSearchInterpolator;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59
60
61
62 public class AddDependencySetsTask {
63 private static final Logger LOGGER = LoggerFactory.getLogger(AddDependencySetsTask.class);
64
65 private static final List<String> NON_ARCHIVE_DEPENDENCY_TYPES;
66
67 static {
68 final List<String> nonArch = new ArrayList<>();
69
70 nonArch.add("pom");
71
72 NON_ARCHIVE_DEPENDENCY_TYPES = Collections.unmodifiableList(nonArch);
73 }
74
75 private final List<DependencySet> dependencySets;
76
77 private final MavenProject project;
78
79 private final ProjectBuilder projectBuilder1;
80
81 private final Set<Artifact> resolvedArtifacts;
82
83 private MavenProject moduleProject;
84
85 private String defaultOutputDirectory;
86
87 private String defaultOutputFileNameMapping;
88
89 private Artifact moduleArtifact;
90
91 public AddDependencySetsTask(
92 final List<DependencySet> dependencySets,
93 final Set<Artifact> resolvedArtifacts,
94 final MavenProject project,
95 ProjectBuilder projectBuilder) {
96 this.dependencySets = dependencySets;
97 this.resolvedArtifacts = resolvedArtifacts;
98 this.project = project;
99 this.projectBuilder1 = projectBuilder;
100 }
101
102 public void execute(final Archiver archiver, final AssemblerConfigurationSource configSource)
103 throws ArchiveCreationException, AssemblyFormattingException, InvalidAssemblerConfigurationException {
104 if ((dependencySets == null) || dependencySets.isEmpty()) {
105 LOGGER.debug("No dependency sets specified.");
106 return;
107 }
108
109 final List<Dependency> deps = project.getDependencies();
110 if ((deps == null) || deps.isEmpty()) {
111 LOGGER.debug("Project " + project.getId() + " has no dependencies. Skipping dependency set addition.");
112 }
113
114 for (final DependencySet dependencySet : dependencySets) {
115 addDependencySet(dependencySet, archiver, configSource);
116 }
117 }
118
119 void addDependencySet(
120 final DependencySet dependencySet, final Archiver archiver, final AssemblerConfigurationSource configSource)
121 throws AssemblyFormattingException, ArchiveCreationException, InvalidAssemblerConfigurationException {
122 LOGGER.debug("Processing DependencySet (output=" + dependencySet.getOutputDirectory() + ")");
123
124 if (!dependencySet.isUseTransitiveDependencies() && dependencySet.isUseTransitiveFiltering()) {
125 LOGGER.warn("DependencySet has nonsensical configuration: useTransitiveDependencies == false "
126 + "AND useTransitiveFiltering == true. Transitive filtering flag will be ignored.");
127 }
128
129 final Set<Artifact> dependencyArtifacts = resolveDependencyArtifacts(dependencySet);
130
131 if (!unpackTransformsContent(dependencySet) && dependencyArtifacts.size() > 1) {
132 checkMultiArtifactOutputConfig(dependencySet);
133 }
134
135 LOGGER.debug("Adding " + dependencyArtifacts.size() + " dependency artifacts.");
136
137 UnpackOptions unpackOptions = dependencySet.getUnpackOptions();
138 InputStreamTransformer fileSetTransformers = isUnpackWithOptions(dependencySet)
139 ? ReaderFormatter.getFileSetTransformers(
140 configSource,
141 unpackOptions.isFiltered(),
142 new HashSet<>(unpackOptions.getNonFilteredFileExtensions()),
143 unpackOptions.getLineEnding())
144 : null;
145
146 for (final Artifact depArtifact : dependencyArtifacts) {
147 ProjectBuildingRequest pbr = getProjectBuildingRequest(configSource);
148 MavenProject depProject;
149 try {
150 ProjectBuildingResult build = projectBuilder1.build(depArtifact, pbr);
151 depProject = build.getProject();
152 } catch (final ProjectBuildingException e) {
153 LOGGER.debug("Error retrieving POM of module-dependency: " + depArtifact.getId() + "; Reason: "
154 + e.getMessage() + "\n\nBuilding stub project instance.");
155
156 depProject = buildProjectStub(depArtifact);
157 }
158
159 if (NON_ARCHIVE_DEPENDENCY_TYPES.contains(depArtifact.getType())) {
160 addNonArchiveDependency(depArtifact, depProject, dependencySet, archiver, configSource);
161 } else {
162 addNormalArtifact(dependencySet, depArtifact, depProject, archiver, configSource, fileSetTransformers);
163 }
164 }
165 }
166
167 private ProjectBuildingRequest getProjectBuildingRequest(AssemblerConfigurationSource configSource) {
168 return new DefaultProjectBuildingRequest(configSource.getMavenSession().getProjectBuildingRequest())
169 .setProcessPlugins(false);
170 }
171
172 private boolean isUnpackWithOptions(DependencySet dependencySet) {
173 return dependencySet.isUnpack() && dependencySet.getUnpackOptions() != null;
174 }
175
176 private boolean unpackTransformsContent(DependencySet dependencySet) {
177 return isUnpackWithOptions(dependencySet) && isContentModifyingOption(dependencySet.getUnpackOptions());
178 }
179
180 private boolean isContentModifyingOption(UnpackOptions opts) {
181 return (opts.isFiltered() || opts.getLineEnding() != null);
182 }
183
184 private void checkMultiArtifactOutputConfig(final DependencySet dependencySet) {
185 String dir = dependencySet.getOutputDirectory();
186 if (dir == null) {
187 dir = defaultOutputDirectory;
188 }
189
190 String mapping = dependencySet.getOutputFileNameMapping();
191 if (mapping == null) {
192 mapping = defaultOutputFileNameMapping;
193 }
194
195 if ((dir == null || !dir.contains("${")) && (mapping == null || !mapping.contains("${"))) {
196 LOGGER.warn("NOTE: Your assembly specifies a dependencySet that matches multiple artifacts, but "
197 + "specifies a concrete output format. THIS MAY RESULT IN ONE OR MORE ARTIFACTS BEING "
198 + "OBSCURED!\n\n" + "Output directory: '" + dir + "'\nOutput filename mapping: '" + mapping
199 + "'");
200 }
201 }
202
203 private void addNormalArtifact(
204 final DependencySet dependencySet,
205 final Artifact depArtifact,
206 final MavenProject depProject,
207 final Archiver archiver,
208 final AssemblerConfigurationSource configSource,
209 InputStreamTransformer fileSetTransformers)
210 throws AssemblyFormattingException, ArchiveCreationException {
211 LOGGER.debug("Adding dependency artifact " + depArtifact.getId() + ".");
212
213 String encoding = isUnpackWithOptions(dependencySet)
214 ? dependencySet.getUnpackOptions().getEncoding()
215 : null;
216 Charset charset = encoding != null ? Charset.forName(encoding) : null;
217 final AddArtifactTask task = new AddArtifactTask(depArtifact, fileSetTransformers, charset);
218
219 task.setProject(depProject);
220 task.setModuleProject(moduleProject);
221 task.setModuleArtifact(moduleArtifact);
222 task.setOutputDirectory(dependencySet.getOutputDirectory(), defaultOutputDirectory);
223 task.setFileNameMapping(dependencySet.getOutputFileNameMapping(), defaultOutputFileNameMapping);
224
225 final int dirMode = TypeConversionUtils.modeToInt(dependencySet.getDirectoryMode(), LOGGER);
226 if (dirMode != -1) {
227 task.setDirectoryMode(dirMode);
228 }
229
230 final int fileMode = TypeConversionUtils.modeToInt(dependencySet.getFileMode(), LOGGER);
231 if (fileMode != -1) {
232 task.setFileMode(fileMode);
233 }
234
235 task.setUnpack(dependencySet.isUnpack());
236
237 final UnpackOptions opts = dependencySet.getUnpackOptions();
238 if (isUnpackWithOptions(dependencySet)) {
239 task.setIncludes(opts.getIncludes());
240 task.setExcludes(opts.getExcludes());
241 task.setUsingDefaultExcludes(opts.isUseDefaultExcludes());
242 }
243
244 task.execute(archiver, configSource);
245 }
246
247 private MavenProject buildProjectStub(final Artifact depArtifact) {
248 final Model model = new Model();
249 model.setGroupId(depArtifact.getGroupId());
250 model.setArtifactId(depArtifact.getArtifactId());
251 model.setVersion(depArtifact.getBaseVersion());
252 model.setPackaging(depArtifact.getType());
253
254 model.setDescription("Stub for " + depArtifact.getId());
255
256 final MavenProject project = new MavenProject(model);
257 project.setArtifact(depArtifact);
258
259 return project;
260 }
261
262 Set<Artifact> resolveDependencyArtifacts(final DependencySet dependencySet)
263 throws InvalidAssemblerConfigurationException {
264 final Set<Artifact> dependencyArtifacts = new LinkedHashSet<>();
265 if (resolvedArtifacts != null) {
266 dependencyArtifacts.addAll(resolvedArtifacts);
267 }
268
269 if (dependencySet.isUseProjectArtifact()) {
270 final Artifact projectArtifact = project.getArtifact();
271 if ((projectArtifact != null) && (projectArtifact.getFile() != null)) {
272 dependencyArtifacts.add(projectArtifact);
273 } else {
274 LOGGER.warn("Cannot include project artifact: " + projectArtifact
275 + "; it doesn't have an associated file or directory.");
276 }
277 }
278
279 if (dependencySet.isUseProjectAttachments()) {
280 final List<Artifact> attachments = project.getAttachedArtifacts();
281 if (attachments != null) {
282 for (final Artifact attachment : attachments) {
283 if (attachment.getFile() != null) {
284 dependencyArtifacts.add(attachment);
285 } else {
286 LOGGER.warn("Cannot include attached artifact: " + project.getId() + " for project: "
287 + project.getId() + "; it doesn't have an associated file or directory.");
288 }
289 }
290 }
291 }
292
293 if (dependencySet.isUseTransitiveFiltering()) {
294 LOGGER.debug("Filtering dependency artifacts USING transitive dependency path information.");
295 } else {
296 LOGGER.debug("Filtering dependency artifacts WITHOUT transitive dependency path information.");
297 }
298
299 final ScopeFilter scopeFilter = FilterUtils.newScopeFilter(dependencySet.getScope());
300
301 final ArtifactFilter filter = new ArtifactIncludeFilterTransformer().transform(scopeFilter);
302
303 FilterUtils.filterArtifacts(
304 dependencyArtifacts,
305 dependencySet.getIncludes(),
306 dependencySet.getExcludes(),
307 dependencySet.isUseStrictFiltering(),
308 dependencySet.isUseTransitiveFiltering(),
309 LOGGER,
310 filter);
311
312 return dependencyArtifacts;
313 }
314
315 private void addNonArchiveDependency(
316 final Artifact depArtifact,
317 final MavenProject depProject,
318 final DependencySet dependencySet,
319 final Archiver archiver,
320 final AssemblerConfigurationSource configSource)
321 throws AssemblyFormattingException, ArchiveCreationException {
322 final File source = depArtifact.getFile();
323
324 String outputDirectory = dependencySet.getOutputDirectory();
325
326 FixedStringSearchInterpolator moduleProjectInterpolator =
327 AssemblyFormatUtils.moduleProjectInterpolator(moduleProject);
328 FixedStringSearchInterpolator artifactProjectInterpolator =
329 AssemblyFormatUtils.artifactProjectInterpolator(depProject);
330 outputDirectory = AssemblyFormatUtils.getOutputDirectory(
331 outputDirectory,
332 depProject.getBuild().getFinalName(),
333 configSource,
334 moduleProjectInterpolator,
335 artifactProjectInterpolator);
336
337 final String destName = AssemblyFormatUtils.evaluateFileNameMapping(
338 dependencySet.getOutputFileNameMapping(),
339 depArtifact,
340 configSource.getProject(),
341 moduleArtifact,
342 configSource,
343 moduleProjectInterpolator,
344 artifactProjectInterpolator);
345
346 String target;
347
348
349 if (outputDirectory.endsWith("/") || outputDirectory.endsWith("\\")) {
350 target = outputDirectory + destName;
351 } else {
352 target = outputDirectory + "/" + destName;
353 }
354
355 try {
356 final int mode = TypeConversionUtils.modeToInt(dependencySet.getFileMode(), LOGGER);
357 if (mode > -1) {
358 archiver.addFile(source, target, mode);
359 } else {
360 archiver.addFile(source, target);
361 }
362 } catch (final ArchiverException e) {
363 throw new ArchiveCreationException("Error adding file to archive: " + e.getMessage(), e);
364 }
365 }
366
367 public List<DependencySet> getDependencySets() {
368 return dependencySets;
369 }
370
371 public void setDefaultOutputDirectory(final String defaultOutputDirectory) {
372 this.defaultOutputDirectory = defaultOutputDirectory;
373 }
374
375 public void setDefaultOutputFileNameMapping(final String defaultOutputFileNameMapping) {
376 this.defaultOutputFileNameMapping = defaultOutputFileNameMapping;
377 }
378
379 public void setModuleProject(final MavenProject moduleProject) {
380 this.moduleProject = moduleProject;
381 }
382
383 public void setModuleArtifact(final Artifact moduleArtifact) {
384 this.moduleArtifact = moduleArtifact;
385 }
386 }