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