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