1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.jlink;
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 import java.io.File;
41 import java.io.IOException;
42 import java.util.ArrayList;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.Date;
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Map.Entry;
50 import java.util.NoSuchElementException;
51 import java.util.Optional;
52
53 import org.apache.commons.io.FileUtils;
54 import org.apache.maven.archiver.MavenArchiver;
55 import org.apache.maven.artifact.Artifact;
56 import org.apache.maven.plugin.MojoExecutionException;
57 import org.apache.maven.plugin.MojoFailureException;
58 import org.apache.maven.plugins.annotations.Component;
59 import org.apache.maven.plugins.annotations.LifecyclePhase;
60 import org.apache.maven.plugins.annotations.Mojo;
61 import org.apache.maven.plugins.annotations.Parameter;
62 import org.apache.maven.plugins.annotations.ResolutionScope;
63 import org.apache.maven.project.MavenProject;
64 import org.apache.maven.project.MavenProjectHelper;
65 import org.apache.maven.toolchain.Toolchain;
66 import org.apache.maven.toolchain.ToolchainPrivate;
67 import org.apache.maven.toolchain.java.DefaultJavaToolChain;
68 import org.codehaus.plexus.archiver.Archiver;
69 import org.codehaus.plexus.archiver.ArchiverException;
70 import org.codehaus.plexus.archiver.zip.ZipArchiver;
71 import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
72 import org.codehaus.plexus.languages.java.jpms.LocationManager;
73 import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
74 import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
75 import org.codehaus.plexus.languages.java.version.JavaVersion;
76
77 import static java.util.Collections.singletonMap;
78
79
80
81
82
83
84
85
86 @Mojo(name = "jlink", requiresDependencyResolution = ResolutionScope.RUNTIME, defaultPhase = LifecyclePhase.PACKAGE)
87 public class JLinkMojo extends AbstractJLinkMojo {
88 @Component
89 private LocationManager locationManager;
90
91
92
93
94
95
96
97
98 @Parameter
99 private Map<String, String> jdkToolchain;
100
101
102
103
104
105 @Parameter(defaultValue = "false")
106 private boolean stripDebug;
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 @Parameter
128 private String compress;
129
130
131
132
133
134
135 @Parameter
136 private String launcher;
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156 @Parameter
157 private List<String> addOptions;
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 @Parameter
176 private List<String> limitModules;
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198 @Parameter
199 private List<String> addModules;
200
201
202
203
204
205 @Parameter
206 private String pluginModulePath;
207
208
209
210
211
212
213
214
215
216 @Parameter(defaultValue = "${project.build.directory}/maven-jlink", required = true, readonly = true)
217 private File outputDirectoryImage;
218
219 @Parameter(defaultValue = "${project.build.directory}", required = true, readonly = true)
220 private File buildDirectory;
221
222 @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
223 private File outputDirectory;
224
225
226
227
228
229
230 @Parameter
231 private String endian;
232
233
234
235
236
237 @Parameter
238 private List<String> modulePaths;
239
240
241
242
243 @Parameter(defaultValue = "false")
244 private boolean bindServices;
245
246
247
248
249 @Parameter
250 private String disablePlugin;
251
252
253
254
255 @Parameter(defaultValue = "false")
256 private boolean ignoreSigningInformation;
257
258
259
260
261
262 @Parameter(defaultValue = "false")
263 private boolean noHeaderFiles;
264
265
266
267
268
269 @Parameter(defaultValue = "false")
270 private boolean noManPages;
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286 @Parameter
287 private List<String> suggestProviders;
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307 @Parameter
308 private List<String> includeLocales;
309
310
311
312
313 @Parameter(defaultValue = "false")
314 private boolean verbose;
315
316
317
318
319 @Component(role = Archiver.class, hint = "zip")
320 private ZipArchiver zipArchiver;
321
322
323
324
325 @Parameter
326 private File sourceJdkModules;
327
328
329
330
331
332
333
334 @Parameter
335 private String classifier;
336
337
338
339
340
341 @Parameter(defaultValue = "${project.build.finalName}", readonly = true)
342 private String finalName;
343
344
345
346
347
348
349
350
351 @Parameter(defaultValue = "${project.build.outputTimestamp}")
352 private String outputTimestamp;
353
354
355
356
357 @Component
358 private MavenProjectHelper projectHelper;
359
360 @Override
361 public void execute() throws MojoExecutionException, MojoFailureException {
362 failIfParametersAreNotInTheirValidValueRanges();
363
364 setOutputDirectoryImage();
365
366 ifOutputDirectoryExistsDelteIt();
367
368 JLinkExecutor jLinkExec = getExecutor();
369 Collection<String> modulesToAdd = new ArrayList<>();
370 if (addModules != null) {
371 modulesToAdd.addAll(addModules);
372 }
373 jLinkExec.addAllModules(modulesToAdd);
374
375 Collection<String> pathsOfModules = new ArrayList<>();
376 if (modulePaths != null) {
377 pathsOfModules.addAll(modulePaths);
378 }
379
380 for (Entry<String, File> item : getModulePathElements().entrySet()) {
381 getLog().info(" -> module: " + item.getKey() + " ( "
382 + item.getValue().getPath() + " )");
383
384
385 modulesToAdd.add(item.getKey());
386 pathsOfModules.add(item.getValue().getPath());
387 }
388
389
390 jLinkExec
391 .getJmodsFolder(this.sourceJdkModules)
392 .ifPresent(jmodsFolder -> pathsOfModules.add(jmodsFolder.getAbsolutePath()));
393 jLinkExec.addAllModulePaths(pathsOfModules);
394
395 List<String> jlinkArgs = createJlinkArgs(pathsOfModules, modulesToAdd);
396
397 try {
398 jLinkExec.executeJlink(jlinkArgs);
399 } catch (IllegalStateException e) {
400 throw new MojoFailureException("Unable to find jlink command: " + e.getMessage(), e);
401 }
402
403 File createZipArchiveFromImage = createZipArchiveFromImage(buildDirectory, outputDirectoryImage);
404
405 if (hasClassifier()) {
406 projectHelper.attachArtifact(getProject(), "jlink", getClassifier(), createZipArchiveFromImage);
407 } else {
408 if (projectHasAlreadySetAnArtifact()) {
409 throw new MojoExecutionException("You have to use a classifier "
410 + "to attach supplemental artifacts to the project instead of replacing them.");
411 }
412 getProject().getArtifact().setFile(createZipArchiveFromImage);
413 }
414 }
415
416 private List<File> getCompileClasspathElements(MavenProject project) {
417 List<File> list = new ArrayList<>(project.getArtifacts().size() + 1);
418
419 for (Artifact a : project.getArtifacts()) {
420 getLog().debug("Artifact: " + a.getGroupId() + ":" + a.getArtifactId() + ":" + a.getVersion());
421 list.add(a.getFile());
422 }
423 return list;
424 }
425
426 private Map<String, File> getModulePathElements() throws MojoFailureException {
427
428
429
430
431 Map<String, File> modulepathElements = new HashMap<>();
432
433 try {
434 Collection<File> dependencyArtifacts = getCompileClasspathElements(getProject());
435
436 ResolvePathsRequest<File> request = ResolvePathsRequest.ofFiles(dependencyArtifacts);
437
438 Optional<Toolchain> toolchain = getToolchain();
439 if (toolchain.isPresent()
440 && toolchain.orElseThrow(NoSuchElementException::new) instanceof DefaultJavaToolChain) {
441 Toolchain toolcahin1 = toolchain.orElseThrow(NoSuchElementException::new);
442 request.setJdkHome(new File(((DefaultJavaToolChain) toolcahin1).getJavaHome()));
443 }
444
445 ResolvePathsResult<File> resolvePathsResult = locationManager.resolvePaths(request);
446
447 for (Map.Entry<File, JavaModuleDescriptor> entry :
448 resolvePathsResult.getPathElements().entrySet()) {
449 JavaModuleDescriptor descriptor = entry.getValue();
450 if (descriptor == null) {
451 String message = "The given dependency " + entry.getKey()
452 + " does not have a module-info.java file. So it can't be linked.";
453 getLog().error(message);
454 throw new MojoFailureException(message);
455 }
456
457
458 getLog().debug(" module: " + descriptor.name() + " automatic: " + descriptor.isAutomatic());
459 if (modulepathElements.containsKey(descriptor.name())) {
460 getLog().warn("The module name " + descriptor.name() + " does already exists.");
461 }
462 modulepathElements.put(descriptor.name(), entry.getKey());
463 }
464
465
466
467 if (outputDirectory.exists()) {
468 List<File> singletonList = Collections.singletonList(outputDirectory);
469
470 ResolvePathsRequest<File> singleModuls = ResolvePathsRequest.ofFiles(singletonList);
471
472 ResolvePathsResult<File> resolvePaths = locationManager.resolvePaths(singleModuls);
473 for (Entry<File, JavaModuleDescriptor> entry :
474 resolvePaths.getPathElements().entrySet()) {
475 JavaModuleDescriptor descriptor = entry.getValue();
476 if (descriptor == null) {
477 String message = "The given project " + entry.getKey()
478 + " does not contain a module-info.java file. So it can't be linked.";
479 getLog().error(message);
480 throw new MojoFailureException(message);
481 }
482 if (modulepathElements.containsKey(descriptor.name())) {
483 getLog().warn("The module name " + descriptor.name() + " does already exists.");
484 }
485 modulepathElements.put(descriptor.name(), entry.getKey());
486 }
487 }
488
489 } catch (IOException e) {
490 getLog().error(e.getMessage());
491 throw new MojoFailureException(e.getMessage());
492 }
493
494 return modulepathElements;
495 }
496
497 private JLinkExecutor getExecutor() {
498 return getJlinkExecutor();
499 }
500
501 private boolean projectHasAlreadySetAnArtifact() {
502 if (getProject().getArtifact().getFile() != null) {
503 return getProject().getArtifact().getFile().isFile();
504 } else {
505 return false;
506 }
507 }
508
509
510
511
512 protected boolean hasClassifier() {
513 boolean result = false;
514 if (getClassifier() != null && !getClassifier().isEmpty()) {
515 result = true;
516 }
517
518 return result;
519 }
520
521 private File createZipArchiveFromImage(File outputDirectory, File outputDirectoryImage)
522 throws MojoExecutionException {
523 zipArchiver.addDirectory(outputDirectoryImage);
524
525
526 Date lastModified = new MavenArchiver().parseOutputTimestamp(outputTimestamp);
527 if (lastModified != null) {
528 zipArchiver.configureReproducible(lastModified);
529 }
530
531 File resultArchive = getArchiveFile(outputDirectory, finalName, getClassifier(), "zip");
532
533 zipArchiver.setDestFile(resultArchive);
534 try {
535 zipArchiver.createArchive();
536 } catch (ArchiverException | IOException e) {
537 getLog().error(e.getMessage(), e);
538 throw new MojoExecutionException(e.getMessage(), e);
539 }
540
541 return resultArchive;
542 }
543
544 private void failIfParametersAreNotInTheirValidValueRanges() throws MojoFailureException {
545 if (endian != null && (!"big".equals(endian) && !"little".equals(endian))) {
546 String message = "The given endian parameter " + endian
547 + " does not contain one of the following values: 'little' or 'big'.";
548 getLog().error(message);
549 throw new MojoFailureException(message);
550 }
551
552 if (addOptions != null && !addOptions.isEmpty()) {
553 requireJdk14();
554 }
555 }
556
557 private void requireJdk14() throws MojoFailureException {
558
559 Optional<Toolchain> optToolchain = getToolchain();
560 String java14reqMsg = "parameter 'addOptions' needs at least a Java 14 runtime or a Java 14 toolchain.";
561
562 if (optToolchain.isPresent()) {
563 Toolchain toolchain = optToolchain.orElseThrow(NoSuchElementException::new);
564 if (!(toolchain instanceof ToolchainPrivate)) {
565 getLog().warn("Unable to check toolchain java version.");
566 return;
567 }
568 ToolchainPrivate toolchainPrivate = (ToolchainPrivate) toolchain;
569 if (!toolchainPrivate.matchesRequirements(singletonMap("jdk", "14"))) {
570 throw new MojoFailureException(java14reqMsg);
571 }
572 } else if (!JavaVersion.JAVA_VERSION.isAtLeast("14")) {
573 throw new MojoFailureException(java14reqMsg);
574 }
575 }
576
577
578
579
580
581
582
583 private void setOutputDirectoryImage() {
584 if (hasClassifier()) {
585 final File classifiersDirectory = new File(outputDirectoryImage, "classifiers");
586 outputDirectoryImage = new File(classifiersDirectory, classifier);
587 } else {
588 outputDirectoryImage = new File(outputDirectoryImage, "default");
589 }
590 }
591
592 private void ifOutputDirectoryExistsDelteIt() throws MojoExecutionException {
593 if (outputDirectoryImage.exists()) {
594
595
596 try {
597 getLog().debug("Deleting existing " + outputDirectoryImage.getAbsolutePath());
598 FileUtils.forceDelete(outputDirectoryImage);
599 } catch (IOException e) {
600 getLog().error("IOException", e);
601 throw new MojoExecutionException(
602 "Failure during deletion of " + outputDirectoryImage.getAbsolutePath() + " occured.");
603 }
604 }
605 }
606
607 protected List<String> createJlinkArgs(Collection<String> pathsOfModules, Collection<String> modulesToAdd) {
608 List<String> jlinkArgs = new ArrayList<>();
609
610 if (stripDebug) {
611 jlinkArgs.add("--strip-debug");
612 }
613
614 if (bindServices) {
615 jlinkArgs.add("--bind-services");
616 }
617
618 if (endian != null) {
619 jlinkArgs.add("--endian");
620 jlinkArgs.add(endian);
621 }
622 if (ignoreSigningInformation) {
623 jlinkArgs.add("--ignore-signing-information");
624 }
625 if (compress != null) {
626 jlinkArgs.add("--compress");
627 jlinkArgs.add(compress);
628 }
629 if (launcher != null) {
630 jlinkArgs.add("--launcher");
631 jlinkArgs.add(launcher);
632 }
633 if (addOptions != null && !addOptions.isEmpty()) {
634 jlinkArgs.add("--add-options");
635 jlinkArgs.add(String.format("\"%s\"", String.join(" ", addOptions)));
636 }
637
638 if (disablePlugin != null) {
639 jlinkArgs.add("--disable-plugin");
640 jlinkArgs.add(disablePlugin);
641 }
642 if (pathsOfModules != null && !pathsOfModules.isEmpty()) {
643
644 jlinkArgs.add("--module-path");
645 jlinkArgs.add(getPlatformDependSeparateList(pathsOfModules).replace("\\", "\\\\"));
646
647 }
648
649 if (noHeaderFiles) {
650 jlinkArgs.add("--no-header-files");
651 }
652
653 if (noManPages) {
654 jlinkArgs.add("--no-man-pages");
655 }
656
657 if (hasSuggestProviders()) {
658 jlinkArgs.add("--suggest-providers");
659 String sb = getCommaSeparatedList(suggestProviders);
660 jlinkArgs.add(sb);
661 }
662
663 if (hasLimitModules()) {
664 jlinkArgs.add("--limit-modules");
665 String sb = getCommaSeparatedList(limitModules);
666 jlinkArgs.add(sb);
667 }
668
669 if (!modulesToAdd.isEmpty()) {
670 jlinkArgs.add("--add-modules");
671
672
673 String sb = getCommaSeparatedList(modulesToAdd);
674 jlinkArgs.add(sb.replace("\\", "\\\\"));
675 }
676
677 if (hasIncludeLocales()) {
678 jlinkArgs.add("--add-modules");
679 jlinkArgs.add("jdk.localedata");
680 jlinkArgs.add("--include-locales");
681 String sb = getCommaSeparatedList(includeLocales);
682 jlinkArgs.add(sb);
683 }
684
685 if (pluginModulePath != null) {
686 jlinkArgs.add("--plugin-module-path");
687 StringBuilder sb = convertSeparatedModulePathToPlatformSeparatedModulePath(pluginModulePath);
688 jlinkArgs.add(sb.toString().replace("\\", "\\\\"));
689 }
690
691 if (buildDirectory != null) {
692 jlinkArgs.add("--output");
693 jlinkArgs.add(outputDirectoryImage.getAbsolutePath());
694 }
695
696 if (verbose) {
697 jlinkArgs.add("--verbose");
698 }
699
700 return Collections.unmodifiableList(jlinkArgs);
701 }
702
703 private boolean hasIncludeLocales() {
704 return includeLocales != null && !includeLocales.isEmpty();
705 }
706
707 private boolean hasSuggestProviders() {
708 return suggestProviders != null && !suggestProviders.isEmpty();
709 }
710
711 private boolean hasLimitModules() {
712 return limitModules != null && !limitModules.isEmpty();
713 }
714
715
716
717
718 protected String getClassifier() {
719 return classifier;
720 }
721 }