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