1 package org.apache.maven.plugins.jmod;
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.LinkedHashMap;
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.shared.utils.StringUtils;
43 import org.apache.maven.shared.utils.logging.MessageUtils;
44 import org.apache.maven.toolchain.Toolchain;
45 import org.apache.maven.toolchain.java.DefaultJavaToolChain;
46 import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
47 import org.codehaus.plexus.languages.java.jpms.LocationManager;
48 import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
49 import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
50 import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult.ModuleNameSource;
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 = "create", requiresDependencyResolution = ResolutionScope.COMPILE, defaultPhase = LifecyclePhase.PACKAGE, requiresProject = true )
63
64 public class JModCreateMojo
65 extends AbstractJModMojo
66 {
67 private static final String JMODS = "jmods";
68
69 private List<String> classpathElements;
70
71 private List<String> modulepathElements;
72
73 private Map<String, JavaModuleDescriptor> pathElements;
74
75 @Parameter( defaultValue = "${project.compileClasspathElements}", readonly = true, required = true )
76 private List<String> compilePath;
77
78 @Component
79 private LocationManager locationManager;
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 @Parameter
100 private List<String> cmds;
101
102 private static final String DEFAULT_CMD_DIRECTORY = "src/main/cmds";
103
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> configs;
125
126 private static final String DEFAULT_CONFIG_DIRECTORY = "src/main/configs";
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141 @Parameter
142 private List<String> excludes;
143
144
145
146
147 @Parameter
148 private String mainClass;
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168 @Parameter
169 private List<String> libs;
170
171 private static final String DEFAULT_LIB_DIRECTORY = "src/main/libs";
172
173
174
175
176 @Parameter( defaultValue = "${project.version}" )
177 private String moduleVersion;
178
179
180
181
182 @Parameter( defaultValue = "false" )
183 private boolean doNotResolveByDefault;
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204 @Parameter
205 private List<String> headerFiles;
206
207 private static final String DEFAULT_HEADER_FILES_DIRECTORY = "src/main/headerfiles";
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227 @Parameter
228 private List<String> manPages;
229
230 private static final String DEFAULT_MAN_PAGES_DIRECTORY = "src/main/manpages";
231
232
233
234
235 @Parameter( defaultValue = "${project.artifactId}", required = true, readonly = true )
236 private String outputFileName;
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256 @Parameter
257 private List<String> legalNotices;
258
259 private static final String DEFAULT_LEGAL_NOTICES_DIRECTORY = "src/main/legalnotices";
260
261
262
263
264 @Parameter
265 private String targetPlatform;
266
267
268
269
270
271
272
273
274
275 @Parameter
276 private String warnIfResolved;
277
278 @Parameter( defaultValue = "${project.build.outputDirectory}", required = true, readonly = true )
279 private File targetClassesDirectory;
280
281 @Parameter( defaultValue = "${project.build.directory}", required = true, readonly = true )
282 private File outputDirectory;
283
284 private List<String> modulePaths;
285
286 public void execute()
287 throws MojoExecutionException, MojoFailureException
288 {
289
290 String jModExecutable;
291 try
292 {
293 jModExecutable = getJModExecutable();
294 }
295 catch ( IOException e )
296 {
297 throw new MojoFailureException( "Unable to find jmod command: " + e.getMessage(), e );
298 }
299
300 File jModExecuteableFile = new File( jModExecutable );
301 File jModExecutableParent = jModExecuteableFile.getParentFile().getParentFile();
302 File jmodsFolderJDK = new File( jModExecutableParent, JMODS );
303 getLog().debug( "Parent: " + jModExecutableParent.getAbsolutePath() );
304 getLog().debug( "jmodsFolder: " + jmodsFolderJDK.getAbsolutePath() );
305
306 preparePaths();
307
308 failIfParametersAreNotInTheirValidValueRanges();
309
310 getLog().info( "Toolchain in maven-jmod-plugin: jmod [ " + jModExecutable + " ]" );
311
312
313
314
315 File modsFolder = new File( outputDirectory, "jmods" );
316 File resultingJModFile = new File( modsFolder, outputFileName + ".jmod" );
317
318 deleteOutputIfAlreadyExists( resultingJModFile );
319
320
321 modsFolder.mkdirs();
322
323 this.modulePaths = new ArrayList<>();
324 for ( Entry<String, JavaModuleDescriptor> item : pathElements.entrySet() )
325 {
326
327 if ( item.getValue() == null )
328 {
329 String message = "The given dependency " + item.getKey()
330 + " does not have a module-info.java file. So it can't be linked.";
331 getLog().error( message );
332 throw new MojoFailureException( message );
333 }
334 getLog().debug( "pathElements Item:" + item.getKey() + " v:" + item.getValue().name() );
335 getLog().info( " -> module: " + item.getValue().name() + " ( " + item.getKey() + " )" );
336
337 this.modulePaths.add( item.getKey() );
338 }
339
340 this.modulePaths.add( jmodsFolderJDK.getAbsolutePath() );
341
342 Commandline cmd;
343 try
344 {
345 cmd = createJModCreateCommandLine( resultingJModFile );
346 }
347 catch ( IOException e )
348 {
349 throw new MojoExecutionException( e.getMessage() );
350 }
351 cmd.setExecutable( jModExecutable );
352
353 executeCommand( cmd, outputDirectory );
354
355 if ( projectHasAlreadySetAnArtifact() )
356 {
357 throw new MojoExecutionException( "You have to use a classifier "
358 + "to attach supplemental artifacts to the project instead of replacing them." );
359 }
360
361 getProject().getArtifact().setFile( resultingJModFile );
362 }
363
364 private void deleteOutputIfAlreadyExists( File resultingJModFile )
365 throws MojoFailureException
366 {
367 if ( resultingJModFile.exists() && resultingJModFile.isFile() )
368 {
369 try
370 {
371 getLog().debug( "Deleting the existing " + resultingJModFile.getAbsolutePath() + " file." );
372 FileUtils.forceDelete( resultingJModFile );
373 }
374 catch ( IOException e )
375 {
376 String message = "Failure during deleting of file " + resultingJModFile.getAbsolutePath();
377 getLog().error( message );
378 throw new MojoFailureException( message );
379 }
380 }
381 }
382
383 private void failIfParametersAreNotInTheirValidValueRanges()
384 throws MojoFailureException
385 {
386 if ( warnIfResolved != null )
387 {
388 String x = warnIfResolved.toLowerCase().trim();
389 if ( !"deprecated".equals( x ) && "deprecated-for-removal".equals( x ) && "incubating".equals( x ) )
390 {
391 String message = "The parameter warnIfResolved does not contain a valid value. "
392 + "Valid values are 'deprecated', 'deprecated-for-removal' or 'incubating'.";
393 getLog().error( message );
394 throw new MojoFailureException( message );
395
396 }
397 }
398
399 throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( cmds, DEFAULT_CMD_DIRECTORY ),
400 "cmd" );
401 throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( configs,
402 DEFAULT_CONFIG_DIRECTORY ),
403 "config" );
404 throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( libs, DEFAULT_LIB_DIRECTORY ),
405 "lib" );
406 throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( headerFiles,
407 DEFAULT_HEADER_FILES_DIRECTORY ),
408 "headerFile" );
409 throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( legalNotices,
410 DEFAULT_LEGAL_NOTICES_DIRECTORY ),
411 "legalNotice" );
412 throwExceptionIfNotExistOrNotADirectory( handleConfigurationListWithDefault( manPages,
413 DEFAULT_MAN_PAGES_DIRECTORY ),
414 "manPage" );
415 }
416
417 private void throwExceptionIfNotExistOrNotADirectory( List<String> configurations, String partialMessage )
418 throws MojoFailureException
419 {
420 for ( String configLocation : configurations )
421 {
422 File dir = new File( configLocation );
423 if ( !dir.exists() || !dir.isDirectory() )
424 {
425 String message = "The directory " + configLocation + " for " + partialMessage
426 + " parameter does not exist " + "or is not a directory. ";
427 getLog().error( message );
428 throw new MojoFailureException( message );
429 }
430 }
431 }
432
433 private List<File> getCompileClasspathElements( MavenProject project )
434 {
435 List<File> list = new ArrayList<File>( project.getArtifacts().size() + 1 );
436
437 list.add( new File( project.getBuild().getOutputDirectory() ) );
438
439 for ( Artifact a : project.getArtifacts() )
440 {
441 list.add( a.getFile() );
442 }
443 return list;
444 }
445
446 private void preparePaths()
447 {
448 assert compilePath != null;
449
450 boolean hasModuleDescriptor = false;
451
452
453 File moduleInfo = new File( targetClassesDirectory, "module-info.class" );
454
455 if ( moduleInfo.exists() && moduleInfo.isFile() )
456 {
457 getLog().debug( "We have found a module-info.class file." );
458 hasModuleDescriptor = true;
459 }
460
461 if ( hasModuleDescriptor )
462 {
463
464
465
466
467 modulepathElements = new ArrayList<String>();
468 classpathElements = new ArrayList<String>();
469 pathElements = new LinkedHashMap<String, JavaModuleDescriptor>();
470
471 ResolvePathsResult<File> resolvePathsResult;
472 try
473 {
474 Collection<File> dependencyArtifacts = getCompileClasspathElements( getProject() );
475
476 ResolvePathsRequest<File> request = ResolvePathsRequest.withFiles( dependencyArtifacts );
477
478 Toolchain toolchain = getToolchain();
479 if ( toolchain != null && toolchain instanceof DefaultJavaToolChain )
480 {
481 request.setJdkHome( new File( ( (DefaultJavaToolChain) toolchain ).getJavaHome() ) );
482 }
483
484 resolvePathsResult = locationManager.resolvePaths( request );
485
486 JavaModuleDescriptor moduleDescriptor = resolvePathsResult.getMainModuleDescriptor();
487
488 for ( Map.Entry<File, ModuleNameSource> entry : resolvePathsResult.getModulepathElements().entrySet() )
489 {
490 getLog().debug( "File: " + entry.getKey().getAbsolutePath() + " " + entry.getValue().name() );
491 if ( ModuleNameSource.FILENAME.equals( entry.getValue() ) )
492 {
493 final String message = "Required filename-based automodules detected. "
494 + "Please don't publish this project to a public artifact repository!";
495
496 if ( moduleDescriptor.exports().isEmpty() )
497 {
498
499 getLog().info( message );
500 }
501 else
502 {
503
504 writeBoxedWarning( message );
505 }
506 break;
507 }
508 }
509
510 for ( Map.Entry<File, JavaModuleDescriptor> entry : resolvePathsResult.getPathElements().entrySet() )
511 {
512 getLog().debug( "pathElements: " + entry.getKey().getPath() + " " + entry.getValue().name() );
513 pathElements.put( entry.getKey().getPath(), entry.getValue() );
514 }
515
516 for ( File file : resolvePathsResult.getClasspathElements() )
517 {
518 getLog().debug( "classpathElements: File: " + file.getPath() );
519 classpathElements.add( file.getPath() );
520 }
521
522 for ( File file : resolvePathsResult.getModulepathElements().keySet() )
523 {
524 getLog().debug( "modulepathElements: File: " + file.getPath() );
525 modulepathElements.add( file.getPath() );
526 }
527 }
528 catch ( IOException e )
529 {
530 getLog().warn( e.getMessage() );
531 }
532
533 }
534 else
535 {
536 classpathElements = compilePath;
537 modulepathElements = Collections.emptyList();
538 }
539 }
540
541 private Commandline createJModCreateCommandLine( File resultingJModFile )
542 throws IOException
543 {
544 File file = new File( outputDirectory, "jmodCreateArgs" );
545 if ( !getLog().isDebugEnabled() )
546 {
547 file.deleteOnExit();
548 }
549 file.getParentFile().mkdirs();
550 file.createNewFile();
551
552 PrintStream argsFile = new PrintStream( file );
553
554 argsFile.println( "create" );
555
556 if ( moduleVersion != null )
557 {
558 argsFile.println( "--module-version" );
559 argsFile.println( moduleVersion );
560 }
561
562 if ( !pathElements.isEmpty() )
563 {
564 argsFile.println( "--class-path" );
565
566
567 ArrayList<String> x = new ArrayList<>();
568 for ( String string : pathElements.keySet() )
569 {
570 x.add( string );
571 }
572 argsFile.println( getPlatformSeparatedList( x ) );
573 }
574
575 if ( excludes != null && !excludes.isEmpty() )
576 {
577 argsFile.println( "--exclude" );
578 String commaSeparatedList = getCommaSeparatedList( excludes );
579 argsFile.append( '"' ).append( commaSeparatedList.replace( "\\", "\\\\" ) ).println( '"' );
580 }
581
582 List<String> configList = handleConfigurationListWithDefault( configs, DEFAULT_CONFIG_DIRECTORY );
583 if ( !configList.isEmpty() )
584 {
585 argsFile.println( "--config" );
586
587 argsFile.println( getPlatformSeparatedList( configList ) );
588 }
589
590 List<String> cmdsList = handleConfigurationListWithDefault( cmds, DEFAULT_CMD_DIRECTORY );
591 if ( !cmdsList.isEmpty() )
592 {
593 argsFile.println( "--cmds" );
594 argsFile.println( getPlatformSeparatedList( cmdsList ) );
595 }
596
597 List<String> libsList = handleConfigurationListWithDefault( libs, DEFAULT_LIB_DIRECTORY );
598 if ( !libsList.isEmpty() )
599 {
600 argsFile.println( "--libs" );
601 argsFile.println( getPlatformSeparatedList( libsList ) );
602 }
603
604 List<String> headerFilesList =
605 handleConfigurationListWithDefault( headerFiles, DEFAULT_HEADER_FILES_DIRECTORY );
606 if ( !headerFilesList.isEmpty() )
607 {
608 argsFile.println( "--header-files" );
609 argsFile.println( getPlatformSeparatedList( headerFilesList ) );
610 }
611
612 List<String> legalNoticesList =
613 handleConfigurationListWithDefault( legalNotices, DEFAULT_LEGAL_NOTICES_DIRECTORY );
614 if ( !legalNoticesList.isEmpty() )
615 {
616 argsFile.println( "--legal-notices" );
617 argsFile.println( getPlatformSeparatedList( legalNoticesList ) );
618 }
619
620 List<String> manPagesList = handleConfigurationListWithDefault( manPages, DEFAULT_MAN_PAGES_DIRECTORY );
621 if ( !manPagesList.isEmpty() )
622 {
623 argsFile.println( "--man-pages" );
624 argsFile.println( getPlatformSeparatedList( manPagesList ) );
625 }
626
627 if ( modulePaths != null )
628 {
629
630 argsFile.println( "--module-path" );
631 argsFile
632 .append( '"' )
633 .append( getPlatformSeparatedList( modulePaths ).replace( "\\", "\\\\" ) )
634 .println( '"' );
635
636 }
637
638 if ( targetPlatform != null )
639 {
640 argsFile.println( "--target-platform" );
641 argsFile.println( targetPlatform );
642 }
643
644 if ( warnIfResolved != null )
645 {
646 argsFile.println( "--warn-if-resolved" );
647 argsFile.println( warnIfResolved );
648 }
649
650 if ( doNotResolveByDefault )
651 {
652 argsFile.println( "--do-not-resolve-by-default" );
653 }
654
655 argsFile.println( resultingJModFile.getAbsolutePath() );
656 argsFile.close();
657
658 Commandline cmd = new Commandline();
659 cmd.createArg().setValue( '@' + file.getAbsolutePath() );
660
661 return cmd;
662 }
663
664 private boolean isConfigurationDefinedInPOM( List<String> configuration )
665 {
666 return configuration != null && !configuration.isEmpty();
667 }
668
669 private List<String> handleConfigurationListWithDefault( List<String> configuration, String defaultLocation )
670 {
671 List<String> commands = new ArrayList<String>();
672 if ( isConfigurationDefinedInPOM( configuration ) )
673 {
674 commands.addAll( configuration );
675 }
676 else
677 {
678 if ( doDefaultsExist( defaultLocation ) )
679 {
680 commands.add( defaultLocation );
681 }
682 }
683
684 commands = resolveAgainstProjectBaseDir( commands );
685 return commands;
686 }
687
688 private List<String> resolveAgainstProjectBaseDir( List<String> relativeDirectories )
689 {
690 List<String> result = new ArrayList<>();
691
692 for ( String configLocation : relativeDirectories )
693 {
694 File dir = new File( getProject().getBasedir(), configLocation );
695 result.add( dir.getAbsolutePath() );
696 }
697 return result;
698 }
699
700 private boolean doDefaultsExist( String defaultLocation )
701 {
702 boolean result = false;
703 File dir = new File( getProject().getBasedir(), defaultLocation );
704 if ( dir.exists() && dir.isDirectory() )
705 {
706 result = true;
707 }
708 return result;
709 }
710
711 private String getPlatformSeparatedList( List<String> paths )
712 {
713 StringBuilder sb = new StringBuilder();
714 for ( String module : paths )
715 {
716 if ( sb.length() > 0 )
717 {
718 sb.append( File.pathSeparatorChar );
719 }
720 sb.append( module );
721 }
722 return sb.toString();
723 }
724
725 private void writeBoxedWarning( String message )
726 {
727 String line = StringUtils.repeat( "*", message.length() + 4 );
728 getLog().warn( line );
729 getLog().warn( "* " + MessageUtils.buffer().strong( message ) + " *" );
730 getLog().warn( line );
731 }
732
733 }