83 import java.util.jar.JarOutputStream;
84 import java.util.stream.Collectors;
85 import java.util.regex.Pattern;
86 import java.util.regex.PatternSyntaxException;
87 import java.util.stream.Stream;
88 import java.util.zip.ZipEntry;
89 import java.util.zip.ZipException;
90 import java.util.zip.ZipFile;
91
92 import jdk.internal.jmod.JmodFile;
93 import jdk.internal.jmod.JmodFile.Section;
94 import jdk.internal.joptsimple.BuiltinHelpFormatter;
95 import jdk.internal.joptsimple.NonOptionArgumentSpec;
96 import jdk.internal.joptsimple.OptionDescriptor;
97 import jdk.internal.joptsimple.OptionException;
98 import jdk.internal.joptsimple.OptionParser;
99 import jdk.internal.joptsimple.OptionSet;
100 import jdk.internal.joptsimple.OptionSpec;
101 import jdk.internal.joptsimple.ValueConverter;
102 import jdk.internal.loader.ResourceHelper;
103 import jdk.internal.misc.JavaLangModuleAccess;
104 import jdk.internal.misc.SharedSecrets;
105 import jdk.internal.module.ModuleHashes;
106 import jdk.internal.module.ModuleInfoExtender;
107 import jdk.tools.jlink.internal.Utils;
108
109 import static java.util.stream.Collectors.joining;
110
111 /**
112 * Implementation for the jmod tool.
113 */
114 public class JmodTask {
115
116 static class CommandException extends RuntimeException {
117 private static final long serialVersionUID = 0L;
118 boolean showUsage;
119
120 CommandException(String key, Object... args) {
121 super(getMessageOrKey(key, args));
122 }
123
124 CommandException showUsage(boolean b) {
125 showUsage = b;
126 return this;
159 };
160
161 static class Options {
162 Mode mode;
163 Path jmodFile;
164 boolean help;
165 boolean version;
166 List<Path> classpath;
167 List<Path> cmds;
168 List<Path> configs;
169 List<Path> libs;
170 List<Path> headerFiles;
171 List<Path> manPages;
172 ModuleFinder moduleFinder;
173 Version moduleVersion;
174 String mainClass;
175 String osName;
176 String osArch;
177 String osVersion;
178 Pattern modulesToHash;
179 boolean dryrun;
180 List<PathMatcher> excludes;
181 }
182
183 public int run(String[] args) {
184
185 try {
186 handleOptions(args);
187 if (options == null) {
188 showUsageSummary();
189 return EXIT_CMDERR;
190 }
191 if (options.help) {
192 showHelp();
193 return EXIT_OK;
194 }
195 if (options.version) {
196 showVersion();
197 return EXIT_OK;
198 }
238 } catch (IOException x) {
239 throw new IOException("error opening jmod file", x);
240 }
241
242 // Trivially print the archive entries for now, pending a more complete implementation
243 zip.stream().forEach(e -> out.println(e.getName()));
244 return true;
245 } finally {
246 if (zip != null)
247 zip.close();
248 }
249 }
250
251 private boolean hashModules() {
252 return new Hasher(options.moduleFinder).run();
253 }
254
255 private boolean describe() throws IOException {
256 try (JmodFile jf = new JmodFile(options.jmodFile)) {
257 try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
258 ModuleDescriptor md = ModuleDescriptor.read(in);
259 printModuleDescriptor(md);
260 return true;
261 } catch (IOException e) {
262 throw new CommandException("err.module.descriptor.not.found");
263 }
264 }
265 }
266
267 static <T> String toString(Collection<T> c) {
268 if (c.isEmpty()) { return ""; }
269 return c.stream().map(e -> e.toString().toLowerCase(Locale.ROOT))
270 .collect(joining(" "));
271 }
272
273 private static final JavaLangModuleAccess JLMA = SharedSecrets.getJavaLangModuleAccess();
274
275 private void printModuleDescriptor(ModuleDescriptor md)
276 throws IOException
277 {
278 StringBuilder sb = new StringBuilder();
279 sb.append("\n").append(md.toNameAndVersion());
280
281 md.requires().stream()
282 .sorted(Comparator.comparing(Requires::name))
283 .forEach(r -> {
284 sb.append("\n requires ");
285 if (!r.modifiers().isEmpty())
286 sb.append(toString(r.modifiers())).append(" ");
287 sb.append(r.name());
288 });
289
290 md.uses().stream().sorted()
291 .forEach(s -> sb.append("\n uses ").append(s));
292
293 md.exports().stream()
294 .sorted(Comparator.comparing(Exports::source))
295 .forEach(p -> sb.append("\n exports ").append(p));
301 Set<String> concealed = new HashSet<>(md.packages());
302 md.exports().stream().map(Exports::source).forEach(concealed::remove);
303 md.opens().stream().map(Opens::source).forEach(concealed::remove);
304 concealed.stream().sorted()
305 .forEach(p -> sb.append("\n contains ").append(p));
306
307 md.provides().stream()
308 .sorted(Comparator.comparing(Provides::service))
309 .forEach(p -> sb.append("\n provides ").append(p.service())
310 .append(" with ")
311 .append(toString(p.providers())));
312
313 md.mainClass().ifPresent(v -> sb.append("\n main-class " + v));
314
315 md.osName().ifPresent(v -> sb.append("\n operating-system-name " + v));
316
317 md.osArch().ifPresent(v -> sb.append("\n operating-system-architecture " + v));
318
319 md.osVersion().ifPresent(v -> sb.append("\n operating-system-version " + v));
320
321 JLMA.hashes(md).ifPresent(
322 hashes -> hashes.names().stream().sorted().forEach(
323 mod -> sb.append("\n hashes ").append(mod).append(" ")
324 .append(hashes.algorithm()).append(" ")
325 .append(hashes.hashFor(mod))));
326
327 out.println(sb.toString());
328 }
329
330 private boolean create() throws IOException {
331 JmodFileWriter jmod = new JmodFileWriter();
332
333 // create jmod with temporary name to avoid it being examined
334 // when scanning the module path
335 Path target = options.jmodFile;
336 Path tempTarget = target.resolveSibling(target.getFileName() + ".tmp");
337 try {
338 try (JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget)) {
339 jmod.write(jos);
340 }
341 Files.move(tempTarget, target);
342 } catch (Exception e) {
343 if (Files.exists(tempTarget)) {
344 try {
345 Files.delete(tempTarget);
346 } catch (IOException ioe) {
347 e.addSuppressed(ioe);
348 }
349 }
350 throw e;
351 }
352 return true;
353 }
354
355 private class JmodFileWriter {
356 final List<Path> cmds = options.cmds;
357 final List<Path> libs = options.libs;
358 final List<Path> configs = options.configs;
359 final List<Path> classpath = options.classpath;
360 final List<Path> headerFiles = options.headerFiles;
361 final List<Path> manPages = options.manPages;
362
363 final Version moduleVersion = options.moduleVersion;
364 final String mainClass = options.mainClass;
365 final String osName = options.osName;
366 final String osArch = options.osArch;
367 final String osVersion = options.osVersion;
368 final List<PathMatcher> excludes = options.excludes;
369 final Hasher hasher = hasher();
370
371 JmodFileWriter() { }
372
373 /**
374 * Writes the jmod to the given output stream.
375 */
376 void write(JmodOutputStream out) throws IOException {
377 // module-info.class
378 writeModuleInfo(out, findPackages(classpath));
379
380 // classes
381 processClasses(out, classpath);
382
383 processSection(out, Section.NATIVE_CMDS, cmds);
384 processSection(out, Section.NATIVE_LIBS, libs);
385 processSection(out, Section.CONFIG, configs);
386 processSection(out, Section.HEADER_FILES, headerFiles);
387 processSection(out, Section.MAN_PAGES, manPages);
388
389 }
458 if (mainClass != null)
459 extender.mainClass(mainClass);
460
461 // --os-name, --os-arch, --os-version
462 if (osName != null || osArch != null || osVersion != null)
463 extender.targetPlatform(osName, osArch, osVersion);
464
465 // --module-version
466 if (moduleVersion != null)
467 extender.version(moduleVersion);
468
469 if (hasher != null) {
470 ModuleHashes moduleHashes = hasher.computeHashes(descriptor.name());
471 if (moduleHashes != null) {
472 extender.hashes(moduleHashes);
473 } else {
474 warning("warn.no.module.hashes", descriptor.name());
475 }
476 }
477
478 // write the (possibly extended or modified) module-info.class
479 out.writeEntry(extender.toByteArray(), Section.CLASSES, MODULE_INFO);
480 }
481 }
482
483 /*
484 * Hasher resolves a module graph using the --hash-modules PATTERN
485 * as the roots.
486 *
487 * The jmod file is being created and does not exist in the
488 * given modulepath.
489 */
490 private Hasher hasher() {
491 if (options.modulesToHash == null)
492 return null;
493
494 try {
495 Supplier<InputStream> miSupplier = newModuleInfoSupplier();
496 if (miSupplier == null) {
497 throw new IOException(MODULE_INFO + " not found");
498 }
499
500 ModuleDescriptor descriptor;
501 try (InputStream in = miSupplier.get()) {
502 descriptor = ModuleDescriptor.read(in);
503 }
504
505 URI uri = options.jmodFile.toUri();
506 ModuleReference mref = new ModuleReference(descriptor, uri, new Supplier<>() {
507 @Override
508 public ModuleReader get() {
509 throw new UnsupportedOperationException();
510 }
511 });
512
513 // compose a module finder with the module path and also
514 // a module finder that can find the jmod file being created
515 ModuleFinder finder = ModuleFinder.compose(options.moduleFinder,
516 new ModuleFinder() {
517 @Override
518 public Optional<ModuleReference> find(String name) {
519 if (descriptor.name().equals(name))
520 return Optional.of(mref);
521 else return Optional.empty();
522 }
523
524 @Override
525 public Set<ModuleReference> findAll() {
526 return Collections.singleton(mref);
527 }
528 });
529
530 return new Hasher(finder);
531 } catch (IOException e) {
1063 @Override public Class<Path> valueType() { return Path.class; }
1064
1065 @Override public String valuePattern() { return "path"; }
1066 }
1067
1068 static class ModuleVersionConverter implements ValueConverter<Version> {
1069 @Override
1070 public Version convert(String value) {
1071 try {
1072 return Version.parse(value);
1073 } catch (IllegalArgumentException x) {
1074 throw new CommandException("err.invalid.version", x.getMessage());
1075 }
1076 }
1077
1078 @Override public Class<Version> valueType() { return Version.class; }
1079
1080 @Override public String valuePattern() { return "module-version"; }
1081 }
1082
1083 static class PatternConverter implements ValueConverter<Pattern> {
1084 @Override
1085 public Pattern convert(String value) {
1086 try {
1087 if (value.startsWith("regex:")) {
1088 value = value.substring("regex:".length()).trim();
1089 }
1090
1091 return Pattern.compile(value);
1092 } catch (PatternSyntaxException e) {
1093 throw new CommandException("err.bad.pattern", value);
1094 }
1095 }
1096
1097 @Override public Class<Pattern> valueType() { return Pattern.class; }
1098
1099 @Override public String valuePattern() { return "regex-pattern"; }
1100 }
1101
1102 static class PathMatcherConverter implements ValueConverter<PathMatcher> {
1112 @Override public Class<PathMatcher> valueType() { return PathMatcher.class; }
1113
1114 @Override public String valuePattern() { return "pattern-list"; }
1115 }
1116
1117 /* Support for @<file> in jmod help */
1118 private static final String CMD_FILENAME = "@<filename>";
1119
1120 /**
1121 * This formatter is adding the @filename option and does the required
1122 * formatting.
1123 */
1124 private static final class JmodHelpFormatter extends BuiltinHelpFormatter {
1125
1126 private JmodHelpFormatter() { super(80, 2); }
1127
1128 @Override
1129 public String format(Map<String, ? extends OptionDescriptor> options) {
1130 Map<String, OptionDescriptor> all = new HashMap<>();
1131 all.putAll(options);
1132 all.put(CMD_FILENAME, new OptionDescriptor() {
1133 @Override
1134 public Collection<String> options() {
1135 List<String> ret = new ArrayList<>();
1136 ret.add(CMD_FILENAME);
1137 return ret;
1138 }
1139 @Override
1140 public String description() { return getMessage("main.opt.cmdfile"); }
1141 @Override
1142 public List<?> defaultValues() { return Collections.emptyList(); }
1143 @Override
1144 public boolean isRequired() { return false; }
1145 @Override
1146 public boolean acceptsArguments() { return false; }
1147 @Override
1148 public boolean requiresArgument() { return false; }
1149 @Override
1150 public String argumentDescription() { return null; }
1151 @Override
1253 OptionSpec<Version> moduleVersion
1254 = parser.accepts("module-version", getMessage("main.opt.module-version"))
1255 .withRequiredArg()
1256 .withValuesConvertedBy(new ModuleVersionConverter());
1257
1258 OptionSpec<String> osName
1259 = parser.accepts("os-name", getMessage("main.opt.os-name"))
1260 .withRequiredArg()
1261 .describedAs(getMessage("main.opt.os-name.arg"));
1262
1263 OptionSpec<String> osArch
1264 = parser.accepts("os-arch", getMessage("main.opt.os-arch"))
1265 .withRequiredArg()
1266 .describedAs(getMessage("main.opt.os-arch.arg"));
1267
1268 OptionSpec<String> osVersion
1269 = parser.accepts("os-version", getMessage("main.opt.os-version"))
1270 .withRequiredArg()
1271 .describedAs(getMessage("main.opt.os-version.arg"));
1272
1273 OptionSpec<Void> version
1274 = parser.accepts("version", getMessage("main.opt.version"));
1275
1276 NonOptionArgumentSpec<String> nonOptions
1277 = parser.nonOptions();
1278
1279 try {
1280 OptionSet opts = parser.parse(args);
1281
1282 if (opts.has(help) || opts.has(version)) {
1283 options = new Options();
1284 options.help = opts.has(help);
1285 options.version = opts.has(version);
1286 return; // informational message will be shown
1287 }
1288
1289 List<String> words = opts.valuesOf(nonOptions);
1290 if (words.isEmpty())
1291 throw new CommandException("err.missing.mode").showUsage(true);
1292 String verb = words.get(0);
1298 }
1299
1300 if (opts.has(classPath))
1301 options.classpath = opts.valuesOf(classPath);
1302 if (opts.has(cmds))
1303 options.cmds = opts.valuesOf(cmds);
1304 if (opts.has(config))
1305 options.configs = opts.valuesOf(config);
1306 if (opts.has(dryrun))
1307 options.dryrun = true;
1308 if (opts.has(excludes))
1309 options.excludes = opts.valuesOf(excludes);
1310 if (opts.has(libs))
1311 options.libs = opts.valuesOf(libs);
1312 if (opts.has(headerFiles))
1313 options.headerFiles = opts.valuesOf(headerFiles);
1314 if (opts.has(manPages))
1315 options.manPages = opts.valuesOf(manPages);
1316 if (opts.has(modulePath)) {
1317 Path[] dirs = opts.valuesOf(modulePath).toArray(new Path[0]);
1318 options.moduleFinder = JLMA.newModulePath(Runtime.version(), true, dirs);
1319 }
1320 if (opts.has(moduleVersion))
1321 options.moduleVersion = opts.valueOf(moduleVersion);
1322 if (opts.has(mainClass))
1323 options.mainClass = opts.valueOf(mainClass);
1324 if (opts.has(osName))
1325 options.osName = opts.valueOf(osName);
1326 if (opts.has(osArch))
1327 options.osArch = opts.valueOf(osArch);
1328 if (opts.has(osVersion))
1329 options.osVersion = opts.valueOf(osVersion);
1330 if (opts.has(hashModules)) {
1331 options.modulesToHash = opts.valueOf(hashModules);
1332 // if storing hashes then the module path is required
1333 if (options.moduleFinder == null)
1334 throw new CommandException("err.modulepath.must.be.specified")
1335 .showUsage(true);
1336 }
1337
1338 if (options.mode.equals(Mode.HASH)) {
1339 if (options.moduleFinder == null || options.modulesToHash == null)
1340 throw new CommandException("err.modulepath.must.be.specified")
1341 .showUsage(true);
1342 } else {
1343 if (words.size() <= 1)
1344 throw new CommandException("err.jmod.must.be.specified").showUsage(true);
1345 Path path = Paths.get(words.get(1));
1346
1347 if (options.mode.equals(Mode.CREATE) && Files.exists(path))
1348 throw new CommandException("err.file.already.exists", path);
1349 else if ((options.mode.equals(Mode.LIST) ||
|
83 import java.util.jar.JarOutputStream;
84 import java.util.stream.Collectors;
85 import java.util.regex.Pattern;
86 import java.util.regex.PatternSyntaxException;
87 import java.util.stream.Stream;
88 import java.util.zip.ZipEntry;
89 import java.util.zip.ZipException;
90 import java.util.zip.ZipFile;
91
92 import jdk.internal.jmod.JmodFile;
93 import jdk.internal.jmod.JmodFile.Section;
94 import jdk.internal.joptsimple.BuiltinHelpFormatter;
95 import jdk.internal.joptsimple.NonOptionArgumentSpec;
96 import jdk.internal.joptsimple.OptionDescriptor;
97 import jdk.internal.joptsimple.OptionException;
98 import jdk.internal.joptsimple.OptionParser;
99 import jdk.internal.joptsimple.OptionSet;
100 import jdk.internal.joptsimple.OptionSpec;
101 import jdk.internal.joptsimple.ValueConverter;
102 import jdk.internal.loader.ResourceHelper;
103 import jdk.internal.module.ModuleHashes;
104 import jdk.internal.module.ModuleInfo;
105 import jdk.internal.module.ModuleInfoExtender;
106 import jdk.internal.module.ModulePath;
107 import jdk.internal.module.ModuleResolution;
108 import jdk.tools.jlink.internal.Utils;
109
110 import static java.util.stream.Collectors.joining;
111
112 /**
113 * Implementation for the jmod tool.
114 */
115 public class JmodTask {
116
117 static class CommandException extends RuntimeException {
118 private static final long serialVersionUID = 0L;
119 boolean showUsage;
120
121 CommandException(String key, Object... args) {
122 super(getMessageOrKey(key, args));
123 }
124
125 CommandException showUsage(boolean b) {
126 showUsage = b;
127 return this;
160 };
161
162 static class Options {
163 Mode mode;
164 Path jmodFile;
165 boolean help;
166 boolean version;
167 List<Path> classpath;
168 List<Path> cmds;
169 List<Path> configs;
170 List<Path> libs;
171 List<Path> headerFiles;
172 List<Path> manPages;
173 ModuleFinder moduleFinder;
174 Version moduleVersion;
175 String mainClass;
176 String osName;
177 String osArch;
178 String osVersion;
179 Pattern modulesToHash;
180 ModuleResolution moduleResolution;
181 boolean dryrun;
182 List<PathMatcher> excludes;
183 }
184
185 public int run(String[] args) {
186
187 try {
188 handleOptions(args);
189 if (options == null) {
190 showUsageSummary();
191 return EXIT_CMDERR;
192 }
193 if (options.help) {
194 showHelp();
195 return EXIT_OK;
196 }
197 if (options.version) {
198 showVersion();
199 return EXIT_OK;
200 }
240 } catch (IOException x) {
241 throw new IOException("error opening jmod file", x);
242 }
243
244 // Trivially print the archive entries for now, pending a more complete implementation
245 zip.stream().forEach(e -> out.println(e.getName()));
246 return true;
247 } finally {
248 if (zip != null)
249 zip.close();
250 }
251 }
252
253 private boolean hashModules() {
254 return new Hasher(options.moduleFinder).run();
255 }
256
257 private boolean describe() throws IOException {
258 try (JmodFile jf = new JmodFile(options.jmodFile)) {
259 try (InputStream in = jf.getInputStream(Section.CLASSES, MODULE_INFO)) {
260 ModuleInfo.Attributes attrs = ModuleInfo.read(in, null);
261 printModuleDescriptor(attrs.descriptor(), attrs.recordedHashes());
262 return true;
263 } catch (IOException e) {
264 throw new CommandException("err.module.descriptor.not.found");
265 }
266 }
267 }
268
269 static <T> String toString(Collection<T> c) {
270 if (c.isEmpty()) { return ""; }
271 return c.stream().map(e -> e.toString().toLowerCase(Locale.ROOT))
272 .collect(joining(" "));
273 }
274
275 private void printModuleDescriptor(ModuleDescriptor md, ModuleHashes hashes)
276 throws IOException
277 {
278 StringBuilder sb = new StringBuilder();
279 sb.append("\n").append(md.toNameAndVersion());
280
281 md.requires().stream()
282 .sorted(Comparator.comparing(Requires::name))
283 .forEach(r -> {
284 sb.append("\n requires ");
285 if (!r.modifiers().isEmpty())
286 sb.append(toString(r.modifiers())).append(" ");
287 sb.append(r.name());
288 });
289
290 md.uses().stream().sorted()
291 .forEach(s -> sb.append("\n uses ").append(s));
292
293 md.exports().stream()
294 .sorted(Comparator.comparing(Exports::source))
295 .forEach(p -> sb.append("\n exports ").append(p));
301 Set<String> concealed = new HashSet<>(md.packages());
302 md.exports().stream().map(Exports::source).forEach(concealed::remove);
303 md.opens().stream().map(Opens::source).forEach(concealed::remove);
304 concealed.stream().sorted()
305 .forEach(p -> sb.append("\n contains ").append(p));
306
307 md.provides().stream()
308 .sorted(Comparator.comparing(Provides::service))
309 .forEach(p -> sb.append("\n provides ").append(p.service())
310 .append(" with ")
311 .append(toString(p.providers())));
312
313 md.mainClass().ifPresent(v -> sb.append("\n main-class " + v));
314
315 md.osName().ifPresent(v -> sb.append("\n operating-system-name " + v));
316
317 md.osArch().ifPresent(v -> sb.append("\n operating-system-architecture " + v));
318
319 md.osVersion().ifPresent(v -> sb.append("\n operating-system-version " + v));
320
321 if (hashes != null) {
322 hashes.names().stream().sorted().forEach(
323 mod -> sb.append("\n hashes ").append(mod).append(" ")
324 .append(hashes.algorithm()).append(" ")
325 .append(toHex(hashes.hashFor(mod))));
326 }
327
328 out.println(sb.toString());
329 }
330
331 private String toHex(byte[] ba) {
332 StringBuilder sb = new StringBuilder(ba.length);
333 for (byte b: ba) {
334 sb.append(String.format("%02x", b & 0xff));
335 }
336 return sb.toString();
337 }
338
339 private boolean create() throws IOException {
340 JmodFileWriter jmod = new JmodFileWriter();
341
342 // create jmod with temporary name to avoid it being examined
343 // when scanning the module path
344 Path target = options.jmodFile;
345 Path tempTarget = target.resolveSibling(target.getFileName() + ".tmp");
346 try {
347 try (JmodOutputStream jos = JmodOutputStream.newOutputStream(tempTarget)) {
348 jmod.write(jos);
349 }
350 Files.move(tempTarget, target);
351 } catch (Exception e) {
352 if (Files.exists(tempTarget)) {
353 try {
354 Files.delete(tempTarget);
355 } catch (IOException ioe) {
356 e.addSuppressed(ioe);
357 }
358 }
359 throw e;
360 }
361 return true;
362 }
363
364 private class JmodFileWriter {
365 final List<Path> cmds = options.cmds;
366 final List<Path> libs = options.libs;
367 final List<Path> configs = options.configs;
368 final List<Path> classpath = options.classpath;
369 final List<Path> headerFiles = options.headerFiles;
370 final List<Path> manPages = options.manPages;
371
372 final Version moduleVersion = options.moduleVersion;
373 final String mainClass = options.mainClass;
374 final String osName = options.osName;
375 final String osArch = options.osArch;
376 final String osVersion = options.osVersion;
377 final List<PathMatcher> excludes = options.excludes;
378 final Hasher hasher = hasher();
379 final ModuleResolution moduleResolution = options.moduleResolution;
380
381 JmodFileWriter() { }
382
383 /**
384 * Writes the jmod to the given output stream.
385 */
386 void write(JmodOutputStream out) throws IOException {
387 // module-info.class
388 writeModuleInfo(out, findPackages(classpath));
389
390 // classes
391 processClasses(out, classpath);
392
393 processSection(out, Section.NATIVE_CMDS, cmds);
394 processSection(out, Section.NATIVE_LIBS, libs);
395 processSection(out, Section.CONFIG, configs);
396 processSection(out, Section.HEADER_FILES, headerFiles);
397 processSection(out, Section.MAN_PAGES, manPages);
398
399 }
468 if (mainClass != null)
469 extender.mainClass(mainClass);
470
471 // --os-name, --os-arch, --os-version
472 if (osName != null || osArch != null || osVersion != null)
473 extender.targetPlatform(osName, osArch, osVersion);
474
475 // --module-version
476 if (moduleVersion != null)
477 extender.version(moduleVersion);
478
479 if (hasher != null) {
480 ModuleHashes moduleHashes = hasher.computeHashes(descriptor.name());
481 if (moduleHashes != null) {
482 extender.hashes(moduleHashes);
483 } else {
484 warning("warn.no.module.hashes", descriptor.name());
485 }
486 }
487
488 if (moduleResolution != null && moduleResolution.value() != 0) {
489 extender.moduleResolution(moduleResolution);
490 }
491
492 // write the (possibly extended or modified) module-info.class
493 out.writeEntry(extender.toByteArray(), Section.CLASSES, MODULE_INFO);
494 }
495 }
496
497 /*
498 * Hasher resolves a module graph using the --hash-modules PATTERN
499 * as the roots.
500 *
501 * The jmod file is being created and does not exist in the
502 * given modulepath.
503 */
504 private Hasher hasher() {
505 if (options.modulesToHash == null)
506 return null;
507
508 try {
509 Supplier<InputStream> miSupplier = newModuleInfoSupplier();
510 if (miSupplier == null) {
511 throw new IOException(MODULE_INFO + " not found");
512 }
513
514 ModuleDescriptor descriptor;
515 try (InputStream in = miSupplier.get()) {
516 descriptor = ModuleDescriptor.read(in);
517 }
518
519 URI uri = options.jmodFile.toUri();
520 ModuleReference mref = new ModuleReference(descriptor, uri) {
521 @Override
522 public ModuleReader open() {
523 throw new UnsupportedOperationException();
524 }
525 };
526
527 // compose a module finder with the module path and also
528 // a module finder that can find the jmod file being created
529 ModuleFinder finder = ModuleFinder.compose(options.moduleFinder,
530 new ModuleFinder() {
531 @Override
532 public Optional<ModuleReference> find(String name) {
533 if (descriptor.name().equals(name))
534 return Optional.of(mref);
535 else return Optional.empty();
536 }
537
538 @Override
539 public Set<ModuleReference> findAll() {
540 return Collections.singleton(mref);
541 }
542 });
543
544 return new Hasher(finder);
545 } catch (IOException e) {
1077 @Override public Class<Path> valueType() { return Path.class; }
1078
1079 @Override public String valuePattern() { return "path"; }
1080 }
1081
1082 static class ModuleVersionConverter implements ValueConverter<Version> {
1083 @Override
1084 public Version convert(String value) {
1085 try {
1086 return Version.parse(value);
1087 } catch (IllegalArgumentException x) {
1088 throw new CommandException("err.invalid.version", x.getMessage());
1089 }
1090 }
1091
1092 @Override public Class<Version> valueType() { return Version.class; }
1093
1094 @Override public String valuePattern() { return "module-version"; }
1095 }
1096
1097 static class WarnIfResolvedReasonConverter
1098 implements ValueConverter<ModuleResolution>
1099 {
1100 @Override
1101 public ModuleResolution convert(String value) {
1102 if (value.equals("deprecated"))
1103 return (new ModuleResolution(0)).withDeprecated();
1104 else if (value.equals("deprecated-for-removal"))
1105 return (new ModuleResolution(0)).withDeprecatedForRemoval();
1106 else if (value.equals("incubating"))
1107 return (new ModuleResolution(0)).withIncubating();
1108 else
1109 throw new CommandException("err.bad.WarnIfResolvedReason", value);
1110 }
1111
1112 @Override public Class<ModuleResolution> valueType() {
1113 return ModuleResolution.class;
1114 }
1115
1116 @Override public String valuePattern() { return "reason"; }
1117 }
1118
1119 static class PatternConverter implements ValueConverter<Pattern> {
1120 @Override
1121 public Pattern convert(String value) {
1122 try {
1123 if (value.startsWith("regex:")) {
1124 value = value.substring("regex:".length()).trim();
1125 }
1126
1127 return Pattern.compile(value);
1128 } catch (PatternSyntaxException e) {
1129 throw new CommandException("err.bad.pattern", value);
1130 }
1131 }
1132
1133 @Override public Class<Pattern> valueType() { return Pattern.class; }
1134
1135 @Override public String valuePattern() { return "regex-pattern"; }
1136 }
1137
1138 static class PathMatcherConverter implements ValueConverter<PathMatcher> {
1148 @Override public Class<PathMatcher> valueType() { return PathMatcher.class; }
1149
1150 @Override public String valuePattern() { return "pattern-list"; }
1151 }
1152
1153 /* Support for @<file> in jmod help */
1154 private static final String CMD_FILENAME = "@<filename>";
1155
1156 /**
1157 * This formatter is adding the @filename option and does the required
1158 * formatting.
1159 */
1160 private static final class JmodHelpFormatter extends BuiltinHelpFormatter {
1161
1162 private JmodHelpFormatter() { super(80, 2); }
1163
1164 @Override
1165 public String format(Map<String, ? extends OptionDescriptor> options) {
1166 Map<String, OptionDescriptor> all = new HashMap<>();
1167 all.putAll(options);
1168
1169 // hidden options
1170 all.remove("do-not-resolve-by-default");
1171 all.remove("warn-if-resolved");
1172
1173 all.put(CMD_FILENAME, new OptionDescriptor() {
1174 @Override
1175 public Collection<String> options() {
1176 List<String> ret = new ArrayList<>();
1177 ret.add(CMD_FILENAME);
1178 return ret;
1179 }
1180 @Override
1181 public String description() { return getMessage("main.opt.cmdfile"); }
1182 @Override
1183 public List<?> defaultValues() { return Collections.emptyList(); }
1184 @Override
1185 public boolean isRequired() { return false; }
1186 @Override
1187 public boolean acceptsArguments() { return false; }
1188 @Override
1189 public boolean requiresArgument() { return false; }
1190 @Override
1191 public String argumentDescription() { return null; }
1192 @Override
1294 OptionSpec<Version> moduleVersion
1295 = parser.accepts("module-version", getMessage("main.opt.module-version"))
1296 .withRequiredArg()
1297 .withValuesConvertedBy(new ModuleVersionConverter());
1298
1299 OptionSpec<String> osName
1300 = parser.accepts("os-name", getMessage("main.opt.os-name"))
1301 .withRequiredArg()
1302 .describedAs(getMessage("main.opt.os-name.arg"));
1303
1304 OptionSpec<String> osArch
1305 = parser.accepts("os-arch", getMessage("main.opt.os-arch"))
1306 .withRequiredArg()
1307 .describedAs(getMessage("main.opt.os-arch.arg"));
1308
1309 OptionSpec<String> osVersion
1310 = parser.accepts("os-version", getMessage("main.opt.os-version"))
1311 .withRequiredArg()
1312 .describedAs(getMessage("main.opt.os-version.arg"));
1313
1314 OptionSpec<Void> doNotResolveByDefault
1315 = parser.accepts("do-not-resolve-by-default");
1316
1317 OptionSpec<ModuleResolution> warnIfResolved
1318 = parser.accepts("warn-if-resolved")
1319 .withRequiredArg()
1320 .withValuesConvertedBy(new WarnIfResolvedReasonConverter());
1321
1322 OptionSpec<Void> version
1323 = parser.accepts("version", getMessage("main.opt.version"));
1324
1325 NonOptionArgumentSpec<String> nonOptions
1326 = parser.nonOptions();
1327
1328 try {
1329 OptionSet opts = parser.parse(args);
1330
1331 if (opts.has(help) || opts.has(version)) {
1332 options = new Options();
1333 options.help = opts.has(help);
1334 options.version = opts.has(version);
1335 return; // informational message will be shown
1336 }
1337
1338 List<String> words = opts.valuesOf(nonOptions);
1339 if (words.isEmpty())
1340 throw new CommandException("err.missing.mode").showUsage(true);
1341 String verb = words.get(0);
1347 }
1348
1349 if (opts.has(classPath))
1350 options.classpath = opts.valuesOf(classPath);
1351 if (opts.has(cmds))
1352 options.cmds = opts.valuesOf(cmds);
1353 if (opts.has(config))
1354 options.configs = opts.valuesOf(config);
1355 if (opts.has(dryrun))
1356 options.dryrun = true;
1357 if (opts.has(excludes))
1358 options.excludes = opts.valuesOf(excludes);
1359 if (opts.has(libs))
1360 options.libs = opts.valuesOf(libs);
1361 if (opts.has(headerFiles))
1362 options.headerFiles = opts.valuesOf(headerFiles);
1363 if (opts.has(manPages))
1364 options.manPages = opts.valuesOf(manPages);
1365 if (opts.has(modulePath)) {
1366 Path[] dirs = opts.valuesOf(modulePath).toArray(new Path[0]);
1367 options.moduleFinder = new ModulePath(Runtime.version(), true, dirs);
1368 }
1369 if (opts.has(moduleVersion))
1370 options.moduleVersion = opts.valueOf(moduleVersion);
1371 if (opts.has(mainClass))
1372 options.mainClass = opts.valueOf(mainClass);
1373 if (opts.has(osName))
1374 options.osName = opts.valueOf(osName);
1375 if (opts.has(osArch))
1376 options.osArch = opts.valueOf(osArch);
1377 if (opts.has(osVersion))
1378 options.osVersion = opts.valueOf(osVersion);
1379 if (opts.has(warnIfResolved))
1380 options.moduleResolution = opts.valueOf(warnIfResolved);
1381 if (opts.has(doNotResolveByDefault)) {
1382 if (options.moduleResolution == null)
1383 options.moduleResolution = new ModuleResolution(0);
1384 options.moduleResolution = options.moduleResolution.withDoNotResolveByDefault();
1385 }
1386 if (opts.has(hashModules)) {
1387 options.modulesToHash = opts.valueOf(hashModules);
1388 // if storing hashes then the module path is required
1389 if (options.moduleFinder == null)
1390 throw new CommandException("err.modulepath.must.be.specified")
1391 .showUsage(true);
1392 }
1393
1394 if (options.mode.equals(Mode.HASH)) {
1395 if (options.moduleFinder == null || options.modulesToHash == null)
1396 throw new CommandException("err.modulepath.must.be.specified")
1397 .showUsage(true);
1398 } else {
1399 if (words.size() <= 1)
1400 throw new CommandException("err.jmod.must.be.specified").showUsage(true);
1401 Path path = Paths.get(words.get(1));
1402
1403 if (options.mode.equals(Mode.CREATE) && Files.exists(path))
1404 throw new CommandException("err.file.already.exists", path);
1405 else if ((options.mode.equals(Mode.LIST) ||
|