1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.source;
20
21 import java.io.File;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Objects;
30
31 import org.apache.maven.api.Artifact;
32 import org.apache.maven.api.Project;
33 import org.apache.maven.api.Session;
34 import org.apache.maven.api.Type;
35 import org.apache.maven.api.di.Inject;
36 import org.apache.maven.api.model.Resource;
37 import org.apache.maven.api.plugin.Log;
38 import org.apache.maven.api.plugin.Mojo;
39 import org.apache.maven.api.plugin.MojoException;
40 import org.apache.maven.api.plugin.annotations.Parameter;
41 import org.apache.maven.api.services.ArtifactManager;
42 import org.apache.maven.api.services.ProjectManager;
43 import org.apache.maven.archiver.MavenArchiveConfiguration;
44 import org.apache.maven.archiver.MavenArchiver;
45 import org.codehaus.plexus.archiver.Archiver;
46 import org.codehaus.plexus.archiver.ArchiverException;
47 import org.codehaus.plexus.archiver.jar.JarArchiver;
48 import org.codehaus.plexus.archiver.util.DefaultFileSet;
49 import org.codehaus.plexus.util.FileUtils;
50
51
52
53
54
55
56 public abstract class AbstractSourceJarMojo implements Mojo {
57 private static final String[] DEFAULT_INCLUDES = new String[] {"**/**"};
58
59 private static final String[] DEFAULT_EXCLUDES = new String[] {};
60
61
62
63
64
65
66
67 @Parameter
68 protected String[] includes;
69
70
71
72
73
74
75
76 @Parameter
77 protected String[] excludes;
78
79
80
81
82
83
84
85 @Parameter(property = "maven.source.useDefaultExcludes", defaultValue = "true")
86 protected boolean useDefaultExcludes;
87
88
89
90
91 @Inject
92 protected Project project;
93
94
95
96
97 @Inject
98 protected JarArchiver jarArchiver;
99
100
101
102
103
104
105
106
107
108
109 @Parameter
110 protected MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
111
112
113
114
115
116
117
118
119 @Parameter(
120 defaultValue = "${project.build.outputDirectory}/META-INF/MANIFEST.MF",
121 readonly = false,
122 required = true)
123
124 protected Path defaultManifestFile;
125
126
127
128
129
130
131 @Parameter(property = "maven.source.useDefaultManifestFile", defaultValue = "false")
132 protected boolean useDefaultManifestFile;
133
134
135
136
137 @Parameter(property = "maven.source.attach", defaultValue = "true")
138 protected boolean attach;
139
140
141
142
143
144
145
146 @Parameter(property = "maven.source.excludeResources", defaultValue = "false")
147 protected boolean excludeResources;
148
149
150
151
152
153
154 @Parameter(property = "maven.source.includePom", defaultValue = "false")
155 protected boolean includePom;
156
157
158
159
160 @Parameter(defaultValue = "${project.build.directory}")
161 protected Path outputDirectory;
162
163
164
165
166
167 @Parameter(defaultValue = "${project.build.finalName}")
168 protected String finalName;
169
170
171
172
173 @Parameter(defaultValue = "${session.projects}", readonly = true)
174 protected List<Project> reactorProjects;
175
176
177
178
179
180
181
182 @Parameter(property = "maven.source.forceCreation", defaultValue = "false")
183 protected boolean forceCreation;
184
185
186
187
188
189
190
191 @Parameter(property = "maven.source.skip", defaultValue = "false")
192 protected boolean skipSource;
193
194
195
196
197 @Inject
198 protected Session session;
199
200
201
202
203
204
205
206
207 @Parameter(defaultValue = "${project.build.outputTimestamp}")
208 protected String outputTimestamp;
209
210 @Inject
211 protected Log log;
212
213
214
215
216 protected ProjectManager projectManager;
217
218
219
220
221
222 public Log getLog() {
223 return log;
224 }
225
226
227
228
229 public void execute() throws MojoException {
230 if (skipSource) {
231 getLog().info("Skipping source per configuration.");
232 return;
233 }
234
235 projectManager = session.getService(ProjectManager.class);
236 doExecute();
237 }
238
239 protected void doExecute() {
240 packageSources(project);
241 }
242
243
244
245
246
247
248
249
250 protected abstract String getClassifier();
251
252
253
254
255
256
257 protected abstract List<Path> getSources(Project p) throws MojoException;
258
259
260
261
262
263
264 protected abstract List<Resource> getResources(Project p) throws MojoException;
265
266
267
268
269
270 protected void packageSources(Project p) throws MojoException {
271 String type = p.getPackaging().type().id();
272 if (!Type.POM.equals(type) && !Type.BOM.equals(type)) {
273 packageSources(Collections.singletonList(p));
274 }
275 }
276
277
278
279
280
281 protected void packageSources(List<Project> theProjects) throws MojoException {
282 Artifact currentProjectArtifact = project.getMainArtifact().get();
283 if (!currentProjectArtifact.getClassifier().isEmpty()) {
284 getLog().warn("NOT adding sources to artifacts with classifier as Maven only supports one classifier "
285 + "per artifact. Current artifact [" + currentProjectArtifact.key() + "] has a ["
286 + currentProjectArtifact.getClassifier() + "] classifier.");
287
288 return;
289 }
290
291 MavenArchiver archiver = createArchiver();
292
293 for (Project pItem : theProjects) {
294 Project subProject = getProject(pItem);
295
296 String type = subProject.getPackaging().type().id();
297 if (Type.POM.equals(type) || Type.BOM.equals(type)) {
298 continue;
299 }
300
301 archiveProjectContent(subProject, archiver.getArchiver());
302 }
303
304 if (archiver.getArchiver().getResources().hasNext() || forceCreation) {
305
306 if (useDefaultManifestFile && Files.exists(defaultManifestFile) && archive.getManifestFile() == null) {
307 getLog().info("Adding existing MANIFEST to archive. Found under: " + defaultManifestFile);
308 archive.setManifestFile(defaultManifestFile);
309 }
310
311 Path outputFile = outputDirectory.resolve(finalName + "-" + getClassifier() + getExtension());
312
313 try {
314 archiver.setOutputFile(outputFile.toFile());
315 archive.setForced(forceCreation);
316
317 getLog().debug("create archive " + outputFile);
318 archiver.createArchive(session, project, archive);
319 } catch (ArchiverException e) {
320 throw new MojoException("Error creating source archive: " + e.getMessage(), e);
321 }
322
323 if (attach) {
324 Artifact artifact = session.createArtifact(
325 project.getGroupId(),
326 project.getArtifactId(),
327 project.getVersion(),
328 getClassifier(),
329 null,
330 getType());
331 boolean requiresAttach = true;
332 for (Artifact attachedArtifact : projectManager.getAttachedArtifacts(project)) {
333 if (Objects.equals(artifact.key(), attachedArtifact.key())) {
334 Path attachedFile = session.getService(ArtifactManager.class)
335 .getPath(attachedArtifact)
336 .orElse(null);
337 if (attachedFile != null && !outputFile.equals(attachedFile)) {
338 getLog().error("Artifact " + attachedArtifact.key()
339 + " already attached to a file " + relative(attachedFile) + ": attach to "
340 + relative(outputFile) + " should be done with another classifier");
341 throw new MojoException("Presumably you have configured maven-source-plugin "
342 + "to execute twice in your build to different output files. "
343 + "You have to configure a classifier for at least one of them.");
344 }
345 requiresAttach = false;
346 getLog().info("Artifact " + attachedArtifact.key() + " already attached to "
347 + relative(outputFile) + ": ignoring same re-attach (same artifact, same file)");
348 }
349 }
350 if (requiresAttach) {
351 projectManager.attachArtifact(project, artifact, outputFile);
352 }
353 } else {
354 getLog().info("NOT adding java-sources to attached artifacts list.");
355 }
356 } else {
357 getLog().info("No sources in project. Archive not created.");
358 }
359 }
360
361 private String relative(Path to) {
362 Path basedir = project.getBasedir().toAbsolutePath();
363 return basedir.relativize(to.toAbsolutePath()).toString();
364 }
365
366 private Artifact getPreviouslyAttached(Artifact artifact, Project checkProject, String classifier) {
367 return artifact.getExtension().equals(getExtension())
368 && artifact.getGroupId().equals(checkProject.getGroupId())
369 && artifact.getArtifactId().equals(checkProject.getArtifactId())
370 && artifact.getVersion().toString().equals(checkProject.getVersion())
371 && Objects.equals(artifact.getClassifier(), classifier)
372 ? artifact
373 : null;
374 }
375
376
377
378
379
380
381 protected void archiveProjectContent(Project project, Archiver archiver) throws MojoException {
382 if (includePom) {
383 try {
384 File pom = project.getPomPath().toFile();
385 archiver.addFile(pom, pom.getName());
386 } catch (ArchiverException e) {
387 throw new MojoException("Error adding POM file to target jar file.", e);
388 }
389 }
390
391 for (Path sourceDirectory : getSources(project)) {
392 if (Files.exists(sourceDirectory)) {
393 addDirectory(archiver, sourceDirectory, getCombinedIncludes(null), getCombinedExcludes(null));
394 }
395 }
396
397
398 for (Resource resource : getResources(project)) {
399
400 Path sourceDirectory = Paths.get(resource.getDirectory());
401
402 if (!Files.exists(sourceDirectory)) {
403 continue;
404 }
405
406 List<String> resourceIncludes = resource.getIncludes();
407
408 String[] combinedIncludes = getCombinedIncludes(resourceIncludes);
409
410 List<String> resourceExcludes = resource.getExcludes();
411
412 String[] combinedExcludes = getCombinedExcludes(resourceExcludes);
413
414 String targetPath = resource.getTargetPath();
415 if (targetPath != null) {
416 if (!targetPath.trim().endsWith("/")) {
417 targetPath += "/";
418 }
419 addDirectory(archiver, sourceDirectory, targetPath, combinedIncludes, combinedExcludes);
420 } else {
421 addDirectory(archiver, sourceDirectory, combinedIncludes, combinedExcludes);
422 }
423 }
424 }
425
426
427
428
429
430 protected MavenArchiver createArchiver() throws MojoException {
431 MavenArchiver archiver = new MavenArchiver();
432 archiver.setArchiver(jarArchiver);
433 archiver.setCreatedBy("Maven Source Plugin", "org.apache.maven.plugins", "maven-source-plugin");
434 archiver.setBuildJdkSpecDefaultEntry(false);
435
436
437 archiver.configureReproducibleBuild(outputTimestamp);
438
439 if (project.getBuild() != null) {
440 List<org.apache.maven.api.model.Resource> resources =
441 project.getBuild().getResources();
442
443 for (org.apache.maven.api.model.Resource r : resources) {
444 if (r.getDirectory().endsWith("maven-shared-archive-resources")) {
445 addDirectory(
446 archiver.getArchiver(),
447 Paths.get(r.getDirectory()),
448 getCombinedIncludes(null),
449 getCombinedExcludes(null));
450 }
451 }
452 }
453
454 return archiver;
455 }
456
457
458
459
460
461
462
463
464 protected void addDirectory(Archiver archiver, Path sourceDirectory, String[] pIncludes, String[] pExcludes)
465 throws MojoException {
466 try {
467 getLog().debug("add directory " + sourceDirectory + " to archiver");
468 archiver.addFileSet(DefaultFileSet.fileSet(sourceDirectory.toFile()).includeExclude(pIncludes, pExcludes));
469 } catch (ArchiverException e) {
470 throw new MojoException("Error adding directory to source archive.", e);
471 }
472 }
473
474
475
476
477
478
479
480
481
482 protected void addDirectory(
483 Archiver archiver, Path sourceDirectory, String prefix, String[] pIncludes, String[] pExcludes)
484 throws MojoException {
485 try {
486 getLog().debug("add directory " + sourceDirectory + " to archiver with prefix " + prefix);
487 archiver.addFileSet(DefaultFileSet.fileSet(sourceDirectory.toFile())
488 .prefixed(prefix)
489 .includeExclude(pIncludes, pExcludes));
490 } catch (ArchiverException e) {
491 throw new MojoException("Error adding directory to source archive.", e);
492 }
493 }
494
495
496
497
498 protected String getExtension() {
499 return ".jar";
500 }
501
502
503
504
505
506 protected Project getProject(Project p) {
507 return projectManager.getExecutionProject(p).orElse(p);
508 }
509
510
511
512
513 protected String getType() {
514 return "java-source";
515 }
516
517
518
519
520
521
522
523
524 private String[] getCombinedIncludes(List<String> additionalIncludes) {
525 List<String> combinedIncludes = new ArrayList<>();
526
527 if (includes != null && includes.length > 0) {
528 combinedIncludes.addAll(Arrays.asList(includes));
529 }
530
531 if (additionalIncludes != null && !additionalIncludes.isEmpty()) {
532 combinedIncludes.addAll(additionalIncludes);
533 }
534
535
536 if (combinedIncludes.isEmpty()) {
537 combinedIncludes.addAll(Arrays.asList(DEFAULT_INCLUDES));
538 }
539
540 return combinedIncludes.toArray(new String[0]);
541 }
542
543
544
545
546
547
548
549
550 private String[] getCombinedExcludes(List<String> additionalExcludes) {
551 List<String> combinedExcludes = new ArrayList<>();
552
553 if (useDefaultExcludes) {
554 combinedExcludes.addAll(FileUtils.getDefaultExcludesAsList());
555 }
556
557 if (excludes != null && excludes.length > 0) {
558 combinedExcludes.addAll(Arrays.asList(excludes));
559 }
560
561 if (additionalExcludes != null && !additionalExcludes.isEmpty()) {
562 combinedExcludes.addAll(additionalExcludes);
563 }
564
565 if (combinedExcludes.isEmpty()) {
566 combinedExcludes.addAll(Arrays.asList(DEFAULT_EXCLUDES));
567 }
568
569 return combinedExcludes.toArray(new String[0]);
570 }
571
572
573
574
575 protected Project getProject() {
576 return project;
577 }
578
579
580
581
582 protected void setProject(Project project) {
583 this.project = project;
584 }
585 }