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 }