1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.jmod;
20
21 import javax.inject.Inject;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Map;
30
31 import org.apache.maven.artifact.Artifact;
32 import org.apache.maven.plugin.MojoExecutionException;
33 import org.apache.maven.plugin.MojoFailureException;
34 import org.apache.maven.plugins.annotations.LifecyclePhase;
35 import org.apache.maven.plugins.annotations.Mojo;
36 import org.apache.maven.plugins.annotations.Parameter;
37 import org.apache.maven.plugins.annotations.ResolutionScope;
38 import org.apache.maven.project.MavenProject;
39 import org.apache.maven.shared.utils.StringUtils;
40 import org.apache.maven.shared.utils.cli.Commandline;
41 import org.apache.maven.shared.utils.io.FileUtils;
42 import org.apache.maven.shared.utils.logging.MessageUtils;
43 import org.apache.maven.toolchain.Toolchain;
44 import org.apache.maven.toolchain.ToolchainManager;
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.ModuleNameSource;
49 import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
50 import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
51
52
53
54
55
56
57
58
59
60 @Mojo(
61 name = "create",
62 requiresDependencyResolution = ResolutionScope.RUNTIME,
63 defaultPhase = LifecyclePhase.PACKAGE,
64 requiresProject = true)
65
66 public class JModCreateMojo extends AbstractJModMojo {
67 private static final String JMODS = "jmods";
68
69 private List<String> classpathElements;
70
71 private List<String> modulepathElements;
72
73 @Parameter(defaultValue = "${project.compileClasspathElements}", readonly = true, required = true)
74 private List<String> compilePath;
75
76 private final LocationManager locationManager;
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 @Parameter
97 private List<String> cmds;
98
99 private static final String DEFAULT_CMD_DIRECTORY = "src/main/cmds";
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 @Parameter
121 private List<String> configs;
122
123 private static final String DEFAULT_CONFIG_DIRECTORY = "src/main/configs";
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138 @Parameter
139 private List<String> excludes;
140
141
142
143
144 @Parameter
145 private String mainClass;
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> libs;
167
168 private static final String DEFAULT_LIB_DIRECTORY = "src/main/libs";
169
170
171
172
173 @Parameter(defaultValue = "${project.version}")
174 private String moduleVersion;
175
176
177
178
179 @Parameter(defaultValue = "false")
180 private boolean doNotResolveByDefault;
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201 @Parameter
202 private List<String> headerFiles;
203
204 private static final String DEFAULT_HEADER_FILES_DIRECTORY = "src/main/headerfiles";
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224 @Parameter
225 private List<String> manPages;
226
227 private static final String DEFAULT_MAN_PAGES_DIRECTORY = "src/main/manpages";
228
229
230
231
232 @Parameter(defaultValue = "${project.artifactId}", required = true, readonly = true)
233 private String outputFileName;
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253 @Parameter
254 private List<String> legalNotices;
255
256 private static final String DEFAULT_LEGAL_NOTICES_DIRECTORY = "src/main/legalnotices";
257
258
259
260
261 @Parameter
262 private String targetPlatform;
263
264
265
266
267
268
269
270
271
272 @Parameter
273 private String warnIfResolved;
274
275 @Parameter(defaultValue = "${project.build.outputDirectory}", required = true, readonly = true)
276 private File targetClassesDirectory;
277
278 @Parameter(defaultValue = "${project.build.directory}", required = true, readonly = true)
279 private File outputDirectory;
280
281
282 private File javaHome;
283
284 @Inject
285 public JModCreateMojo(ToolchainManager toolchainManager, LocationManager locationManager) {
286 super(toolchainManager);
287 this.locationManager = locationManager;
288 }
289
290 public void execute() throws MojoExecutionException, MojoFailureException {
291 try {
292 String jModExecutable = getJModExecutable();
293 File jModExecuteableFile = new File(jModExecutable);
294 javaHome = jModExecuteableFile.getParentFile().getParentFile();
295 File jmodsFolderJDK = new File(javaHome, JMODS);
296 getLog().debug("Parent: " + javaHome.getAbsolutePath());
297 getLog().debug("jmodsFolder: " + jmodsFolderJDK.getAbsolutePath());
298
299 preparePaths();
300
301 failIfParametersAreNotInTheirValidValueRanges();
302
303 getLog().debug("Toolchain in maven-jmod-plugin: jmod [ " + jModExecutable + " ]");
304
305
306
307
308 File modsFolder = new File(outputDirectory, "jmods");
309 File resultingJModFile = new File(modsFolder, outputFileName + ".jmod");
310
311 deleteOutputIfAlreadyExists(resultingJModFile);
312
313
314 modsFolder.mkdirs();
315
316 Commandline cmd = createJModCreateCommandLine(resultingJModFile);
317 cmd.setExecutable(jModExecutable);
318
319 executeCommand(cmd, outputDirectory);
320
321 if (projectHasAlreadySetAnArtifact()) {
322 throw new MojoExecutionException("You have to use a classifier "
323 + "to attach supplemental artifacts to the project instead of replacing them.");
324 }
325
326 getProject().getArtifact().setFile(resultingJModFile);
327 } catch (IOException e) {
328 throw new MojoFailureException("Unable to find jmod command: " + e.getMessage(), e);
329 }
330 }
331
332 private void deleteOutputIfAlreadyExists(File resultingJModFile) throws MojoFailureException {
333 if (resultingJModFile.exists() && resultingJModFile.isFile()) {
334 try {
335 getLog().debug("Deleting the existing " + resultingJModFile.getAbsolutePath() + " file.");
336 FileUtils.forceDelete(resultingJModFile);
337 } catch (IOException e) {
338 String message = "Failure during deleting of file " + resultingJModFile.getAbsolutePath();
339 getLog().error(message);
340 throw new MojoFailureException(message);
341 }
342 }
343 }
344
345 private void failIfParametersAreNotInTheirValidValueRanges() throws MojoFailureException {
346 if (warnIfResolved != null) {
347 String x = warnIfResolved.toLowerCase().trim();
348 if (!"deprecated".equals(x) && "deprecated-for-removal".equals(x) && "incubating".equals(x)) {
349 String message = "The parameter warnIfResolved does not contain a valid value. "
350 + "Valid values are 'deprecated', 'deprecated-for-removal' or 'incubating'.";
351 getLog().error(message);
352 throw new MojoFailureException(message);
353 }
354 }
355
356 throwExceptionIfNotExistOrNotADirectory(handleConfigurationListWithDefault(cmds, DEFAULT_CMD_DIRECTORY), "cmd");
357 throwExceptionIfNotExistOrNotADirectory(
358 handleConfigurationListWithDefault(configs, DEFAULT_CONFIG_DIRECTORY), "config");
359 throwExceptionIfNotExistOrNotADirectory(handleConfigurationListWithDefault(libs, DEFAULT_LIB_DIRECTORY), "lib");
360 throwExceptionIfNotExistOrNotADirectory(
361 handleConfigurationListWithDefault(headerFiles, DEFAULT_HEADER_FILES_DIRECTORY), "headerFile");
362 throwExceptionIfNotExistOrNotADirectory(
363 handleConfigurationListWithDefault(legalNotices, DEFAULT_LEGAL_NOTICES_DIRECTORY), "legalNotice");
364 throwExceptionIfNotExistOrNotADirectory(
365 handleConfigurationListWithDefault(manPages, DEFAULT_MAN_PAGES_DIRECTORY), "manPage");
366 }
367
368 private void throwExceptionIfNotExistOrNotADirectory(List<String> configurations, String partialMessage)
369 throws MojoFailureException {
370 for (String configLocation : configurations) {
371 File dir = new File(configLocation);
372 if (!dir.exists() || !dir.isDirectory()) {
373 String message = "The directory " + configLocation + " for " + partialMessage
374 + " parameter does not exist " + "or is not a directory. ";
375 getLog().error(message);
376 throw new MojoFailureException(message);
377 }
378 }
379 }
380
381 private List<File> getCompileClasspathElements(MavenProject project) {
382 List<File> list = new ArrayList<File>(project.getArtifacts().size() + 1);
383
384 if (targetClassesDirectory.exists()) {
385 list.add(new File(project.getBuild().getOutputDirectory()));
386 }
387
388 for (Artifact a : project.getArtifacts()) {
389 list.add(a.getFile());
390 }
391 return list;
392 }
393
394 private void preparePaths() {
395 boolean hasModuleDescriptor = false;
396
397
398
399 File moduleInfo = new File(targetClassesDirectory, "module-info.class");
400
401 if (moduleInfo.exists() && moduleInfo.isFile()) {
402 getLog().debug("We have found a module-info.class file.");
403 hasModuleDescriptor = true;
404 }
405
406 Collection<File> dependencyArtifacts = getCompileClasspathElements(getProject());
407
408 if (hasModuleDescriptor) {
409
410
411
412
413 modulepathElements = new ArrayList<>();
414 classpathElements = new ArrayList<>();
415
416 ResolvePathsResult<File> resolvePathsResult;
417 try {
418
419 ResolvePathsRequest<File> request =
420 ResolvePathsRequest.ofFiles(dependencyArtifacts).setMainModuleDescriptor(moduleInfo);
421
422 Toolchain toolchain = getToolchain();
423 if (toolchain != null && toolchain instanceof DefaultJavaToolChain) {
424 request.setJdkHome(new File(((DefaultJavaToolChain) toolchain).getJavaHome()));
425 }
426
427 resolvePathsResult = locationManager.resolvePaths(request);
428
429 JavaModuleDescriptor moduleDescriptor = resolvePathsResult.getMainModuleDescriptor();
430
431 for (Map.Entry<File, ModuleNameSource> entry :
432 resolvePathsResult.getModulepathElements().entrySet()) {
433 getLog().debug("File: " + entry.getKey().getAbsolutePath() + " "
434 + entry.getValue().name());
435 if (ModuleNameSource.FILENAME.equals(entry.getValue())) {
436 final String message = "Required filename-based automodules detected. "
437 + "Please don't publish this project to a public artifact repository!";
438
439 if (moduleDescriptor.exports().isEmpty()) {
440
441 getLog().info(message);
442 } else {
443
444 writeBoxedWarning(message);
445 }
446 break;
447 }
448 }
449
450 for (File file : resolvePathsResult.getClasspathElements()) {
451 getLog().debug("classpathElements: File: " + file.getPath());
452 classpathElements.add(file.getPath());
453 }
454
455 for (File file : resolvePathsResult.getModulepathElements().keySet()) {
456 getLog().debug("modulepathElements: File: " + file.getPath());
457 if (file.isDirectory()) {
458 modulepathElements.add(file.getPath());
459 } else {
460 modulepathElements.add(file.getParent());
461 }
462 }
463 } catch (IOException e) {
464 getLog().warn(e.getMessage());
465 }
466 } else {
467 modulepathElements = Collections.emptyList();
468
469 classpathElements = new ArrayList<String>();
470 for (File file : dependencyArtifacts) {
471 classpathElements.add(file.getPath());
472 }
473 }
474 }
475
476 private Commandline createJModCreateCommandLine(File resultingJModFile) {
477 Commandline command = new Commandline();
478 command.createArg().setValue("create");
479 if (moduleVersion != null) {
480 command.createArg().setValue("--module-version=" + moduleVersion);
481 }
482
483 List<String> classPaths;
484 if (classpathElements != null) {
485 classPaths = new ArrayList<>(classpathElements);
486 } else {
487 classPaths = new ArrayList<>(1);
488 }
489 if (targetClassesDirectory.exists()) {
490 classPaths.add(targetClassesDirectory.getAbsolutePath());
491 }
492
493 command.createArg()
494 .setValue("--class-path=" + getPlatformSeparatedList(classPaths).replace("\\", "\\\\"));
495
496 if (excludes != null && !excludes.isEmpty()) {
497 String commaSeparatedList = getCommaSeparatedList(excludes);
498 command.createArg().setValue("--exclude=" + commaSeparatedList.replace("\\", "\\\\"));
499 }
500
501 List<String> configList = handleConfigurationListWithDefault(configs, DEFAULT_CONFIG_DIRECTORY);
502 if (!configList.isEmpty()) {
503 command.createArg().setValue("--config=" + getPlatformSeparatedList(configList));
504 }
505
506 if (StringUtils.isNotBlank(mainClass)) {
507 command.createArg().setValue("--main-class=" + mainClass);
508 }
509
510 List<String> cmdsList = handleConfigurationListWithDefault(cmds, DEFAULT_CMD_DIRECTORY);
511 if (!cmdsList.isEmpty()) {
512 command.createArg().setValue("--cmds=" + getPlatformSeparatedList(cmdsList));
513 }
514
515 List<String> libsList = handleConfigurationListWithDefault(libs, DEFAULT_LIB_DIRECTORY);
516 if (!libsList.isEmpty()) {
517 command.createArg().setValue("--libs=" + getPlatformSeparatedList(libsList));
518 }
519
520 List<String> headerFilesList = handleConfigurationListWithDefault(headerFiles, DEFAULT_HEADER_FILES_DIRECTORY);
521 if (!headerFilesList.isEmpty()) {
522 command.createArg().setValue("--header-files=" + getPlatformSeparatedList(headerFilesList));
523 }
524
525 List<String> legalNoticesList =
526 handleConfigurationListWithDefault(legalNotices, DEFAULT_LEGAL_NOTICES_DIRECTORY);
527 if (!legalNoticesList.isEmpty()) {
528 command.createArg().setValue("--legal-notices=" + getPlatformSeparatedList(legalNoticesList));
529 }
530
531 List<String> manPagesList = handleConfigurationListWithDefault(manPages, DEFAULT_MAN_PAGES_DIRECTORY);
532 if (!manPagesList.isEmpty()) {
533 command.createArg().setValue("--man-pages=" + getPlatformSeparatedList(manPagesList));
534 }
535
536 List<String> modulePaths = new ArrayList<>(modulepathElements);
537 modulePaths.add(new File(javaHome, JMODS).getAbsolutePath());
538 command.createArg()
539 .setValue(
540 "--module-path=" + getPlatformSeparatedList(modulePaths).replace("\\", "\\\\"));
541
542 if (targetPlatform != null) {
543 command.createArg().setValue("--target-platform=" + targetPlatform);
544 }
545
546 if (warnIfResolved != null) {
547 command.createArg().setValue("--warn-if-resolved=" + warnIfResolved);
548 }
549
550 if (doNotResolveByDefault) {
551 command.createArg().setValue("--do-not-resolve-by-default");
552 }
553
554 command.createArg().setValue(resultingJModFile.getAbsolutePath());
555
556 return command;
557 }
558
559 private boolean isConfigurationDefinedInPOM(List<String> configuration) {
560 return configuration != null && !configuration.isEmpty();
561 }
562
563 private List<String> handleConfigurationListWithDefault(List<String> configuration, String defaultLocation) {
564 List<String> commands = new ArrayList<String>();
565 if (isConfigurationDefinedInPOM(configuration)) {
566 commands.addAll(configuration);
567 } else {
568 if (doDefaultsExist(defaultLocation)) {
569 commands.add(defaultLocation);
570 }
571 }
572
573 commands = resolveAgainstProjectBaseDir(commands);
574 return commands;
575 }
576
577 private List<String> resolveAgainstProjectBaseDir(List<String> relativeDirectories) {
578 List<String> result = new ArrayList<>();
579
580 for (String configLocation : relativeDirectories) {
581 File dir = new File(getProject().getBasedir(), configLocation);
582 result.add(dir.getAbsolutePath());
583 }
584 return result;
585 }
586
587 private boolean doDefaultsExist(String defaultLocation) {
588 boolean result = false;
589 File dir = new File(getProject().getBasedir(), defaultLocation);
590 if (dir.exists() && dir.isDirectory()) {
591 result = true;
592 }
593 return result;
594 }
595
596 private String getPlatformSeparatedList(Collection<String> paths) {
597 StringBuilder sb = new StringBuilder();
598 for (String module : paths) {
599 if (sb.length() > 0) {
600 sb.append(File.pathSeparatorChar);
601 }
602 sb.append(module);
603 }
604 return sb.toString();
605 }
606
607 private void writeBoxedWarning(String message) {
608 String line = StringUtils.repeat("*", message.length() + 4);
609 getLog().warn(line);
610 getLog().warn("* " + MessageUtils.buffer().strong(message) + " *");
611 getLog().warn(line);
612 }
613 }