1 /* 2 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.tools.jlink.internal; 26 27 import java.io.BufferedInputStream; 28 import java.io.File; 29 import java.io.IOException; 30 import java.io.PrintWriter; 31 import java.io.UncheckedIOException; 32 import java.lang.module.Configuration; 33 import java.lang.module.FindException; 34 import java.lang.module.ModuleDescriptor; 35 import java.lang.module.ModuleFinder; 36 import java.lang.module.ModuleReference; 37 import java.lang.module.ResolutionException; 38 import java.lang.module.ResolvedModule; 39 import java.net.URI; 40 import java.nio.ByteOrder; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.nio.file.Paths; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collection; 47 import java.util.Collections; 48 import java.util.Comparator; 49 import java.util.Date; 50 import java.util.HashMap; 51 import java.util.HashSet; 52 import java.util.List; 53 import java.util.Locale; 54 import java.util.Map; 55 import java.util.Objects; 56 import java.util.Optional; 57 import java.util.Set; 58 import java.util.stream.Collectors; 59 import java.util.stream.Stream; 60 61 import jdk.tools.jlink.internal.TaskHelper.BadArgs; 62 import static jdk.tools.jlink.internal.TaskHelper.JLINK_BUNDLE; 63 import jdk.tools.jlink.internal.Jlink.JlinkConfiguration; 64 import jdk.tools.jlink.internal.Jlink.PluginsConfiguration; 65 import jdk.tools.jlink.internal.TaskHelper.Option; 66 import jdk.tools.jlink.internal.TaskHelper.OptionsHelper; 67 import jdk.tools.jlink.internal.ImagePluginStack.ImageProvider; 68 import jdk.tools.jlink.plugin.PluginException; 69 import jdk.tools.jlink.builder.DefaultImageBuilder; 70 import jdk.tools.jlink.plugin.Plugin; 71 import jdk.internal.module.ModulePath; 72 import jdk.internal.module.ModuleResolution; 73 74 /** 75 * Implementation for the jlink tool. 76 * 77 * ## Should use jdk.joptsimple some day. 78 */ 79 public class JlinkTask { 80 static final boolean DEBUG = Boolean.getBoolean("jlink.debug"); 81 82 // jlink API ignores by default. Remove when signing is implemented. 83 static final boolean IGNORE_SIGNING_DEFAULT = true; 84 85 private static final TaskHelper taskHelper 86 = new TaskHelper(JLINK_BUNDLE); 87 88 private static final Option<?>[] recognizedOptions = { 89 new Option<JlinkTask>(false, (task, opt, arg) -> { 90 task.options.help = true; 91 }, "--help", "-h"), 92 new Option<JlinkTask>(true, (task, opt, arg) -> { 93 // if used multiple times, the last one wins! 94 // So, clear previous values, if any. 95 task.options.modulePath.clear(); 96 String[] dirs = arg.split(File.pathSeparator); 97 int i = 0; 98 Arrays.stream(dirs) 99 .map(Paths::get) 100 .forEach(task.options.modulePath::add); 101 }, "--module-path", "-p"), 102 new Option<JlinkTask>(true, (task, opt, arg) -> { 103 // if used multiple times, the last one wins! 104 // So, clear previous values, if any. 105 task.options.limitMods.clear(); 106 for (String mn : arg.split(",")) { 107 if (mn.isEmpty()) { 108 throw taskHelper.newBadArgs("err.mods.must.be.specified", 109 "--limit-modules"); 110 } 111 task.options.limitMods.add(mn); 112 } 113 }, "--limit-modules"), 114 new Option<JlinkTask>(true, (task, opt, arg) -> { 115 for (String mn : arg.split(",")) { 116 if (mn.isEmpty()) { 117 throw taskHelper.newBadArgs("err.mods.must.be.specified", 118 "--add-modules"); 119 } 120 task.options.addMods.add(mn); 121 } 122 }, "--add-modules"), 123 new Option<JlinkTask>(true, (task, opt, arg) -> { 124 Path path = Paths.get(arg); 125 task.options.output = path; 126 }, "--output"), 127 new Option<JlinkTask>(false, (task, opt, arg) -> { 128 task.options.bindServices = true; 129 }, "--bind-services"), 130 new Option<JlinkTask>(false, (task, opt, arg) -> { 131 task.options.suggestProviders = true; 132 }, "--suggest-providers", "", true), 133 new Option<JlinkTask>(true, (task, opt, arg) -> { 134 String[] values = arg.split("="); 135 // check values 136 if (values.length != 2 || values[0].isEmpty() || values[1].isEmpty()) { 137 throw taskHelper.newBadArgs("err.launcher.value.format", arg); 138 } else { 139 String commandName = values[0]; 140 String moduleAndMain = values[1]; 141 int idx = moduleAndMain.indexOf("/"); 142 if (idx != -1) { 143 if (moduleAndMain.substring(0, idx).isEmpty()) { 144 throw taskHelper.newBadArgs("err.launcher.module.name.empty", arg); 145 } 146 147 if (moduleAndMain.substring(idx + 1).isEmpty()) { 148 throw taskHelper.newBadArgs("err.launcher.main.class.empty", arg); 149 } 150 } 151 task.options.launchers.put(commandName, moduleAndMain); 152 } 153 }, "--launcher"), 154 new Option<JlinkTask>(true, (task, opt, arg) -> { 155 if ("little".equals(arg)) { 156 task.options.endian = ByteOrder.LITTLE_ENDIAN; 157 } else if ("big".equals(arg)) { 158 task.options.endian = ByteOrder.BIG_ENDIAN; 159 } else { 160 throw taskHelper.newBadArgs("err.unknown.byte.order", arg); 161 } 162 }, "--endian"), 163 new Option<JlinkTask>(false, (task, opt, arg) -> { 164 task.options.verbose = true; 165 }, "--verbose", "-v"), 166 new Option<JlinkTask>(false, (task, opt, arg) -> { 167 task.options.version = true; 168 }, "--version"), 169 new Option<JlinkTask>(true, (task, opt, arg) -> { 170 Path path = Paths.get(arg); 171 if (Files.exists(path)) { 172 throw taskHelper.newBadArgs("err.dir.exists", path); 173 } 174 task.options.packagedModulesPath = path; 175 }, true, "--keep-packaged-modules"), 176 new Option<JlinkTask>(true, (task, opt, arg) -> { 177 task.options.saveoptsfile = arg; 178 }, "--save-opts"), 179 new Option<JlinkTask>(false, (task, opt, arg) -> { 180 task.options.fullVersion = true; 181 }, true, "--full-version"), 182 new Option<JlinkTask>(false, (task, opt, arg) -> { 183 task.options.ignoreSigning = true; 184 }, "--ignore-signing-information"),}; 185 186 private static final String PROGNAME = "jlink"; 187 private final OptionsValues options = new OptionsValues(); 188 189 private static final OptionsHelper<JlinkTask> optionsHelper 190 = taskHelper.newOptionsHelper(JlinkTask.class, recognizedOptions); 191 private PrintWriter log; 192 193 void setLog(PrintWriter out, PrintWriter err) { 194 log = out; 195 taskHelper.setLog(log); 196 } 197 198 /** 199 * Result codes. 200 */ 201 static final int 202 EXIT_OK = 0, // Completed with no errors. 203 EXIT_ERROR = 1, // Completed but reported errors. 204 EXIT_CMDERR = 2, // Bad command-line arguments 205 EXIT_SYSERR = 3, // System error or resource exhaustion. 206 EXIT_ABNORMAL = 4;// terminated abnormally 207 208 static class OptionsValues { 209 boolean help; 210 String saveoptsfile; 211 boolean verbose; 212 boolean version; 213 boolean fullVersion; 214 final List<Path> modulePath = new ArrayList<>(); 215 final Set<String> limitMods = new HashSet<>(); 216 final Set<String> addMods = new HashSet<>(); 217 Path output; 218 final Map<String, String> launchers = new HashMap<>(); 219 Path packagedModulesPath; 220 ByteOrder endian = ByteOrder.nativeOrder(); 221 boolean ignoreSigning = false; 222 boolean bindServices = false; 223 boolean suggestProviders = false; 224 } 225 226 int run(String[] args) { 227 if (log == null) { 228 setLog(new PrintWriter(System.out, true), 229 new PrintWriter(System.err, true)); 230 } 231 try { 232 List<String> remaining = optionsHelper.handleOptions(this, args); 233 if (remaining.size() > 0 && !options.suggestProviders) { 234 throw taskHelper.newBadArgs("err.orphan.arguments", toString(remaining)) 235 .showUsage(true); 236 } 237 if (options.help) { 238 optionsHelper.showHelp(PROGNAME); 239 return EXIT_OK; 240 } 241 if (optionsHelper.shouldListPlugins()) { 242 optionsHelper.listPlugins(); 243 return EXIT_OK; 244 } 245 if (options.version || options.fullVersion) { 246 taskHelper.showVersion(options.fullVersion); 247 return EXIT_OK; 248 } 249 250 if (taskHelper.getExistingImage() != null) { 251 postProcessOnly(taskHelper.getExistingImage()); 252 return EXIT_OK; 253 } 254 255 256 if (options.modulePath.isEmpty()) { 257 // no --module-path specified - try to set $JAVA_HOME/jmods if that exists 258 Path jmods = getDefaultModulePath(); 259 if (jmods != null) { 260 options.modulePath.add(jmods); 261 } 262 263 if (options.modulePath.isEmpty()) { 264 throw taskHelper.newBadArgs("err.modulepath.must.be.specified") 265 .showUsage(true); 266 } 267 } 268 269 JlinkConfiguration config = initJlinkConfig(); 270 if (options.suggestProviders) { 271 suggestProviders(config, remaining); 272 } else { 273 createImage(config); 274 if (options.saveoptsfile != null) { 275 Files.write(Paths.get(options.saveoptsfile), getSaveOpts().getBytes()); 276 } 277 } 278 279 return EXIT_OK; 280 } catch (PluginException | IllegalArgumentException | 281 UncheckedIOException |IOException | FindException | ResolutionException e) { 282 log.println(taskHelper.getMessage("error.prefix") + " " + e.getMessage()); 283 if (DEBUG) { 284 e.printStackTrace(log); 285 } 286 return EXIT_ERROR; 287 } catch (BadArgs e) { 288 taskHelper.reportError(e.key, e.args); 289 if (e.showUsage) { 290 log.println(taskHelper.getMessage("main.usage.summary", PROGNAME)); 291 } 292 if (DEBUG) { 293 e.printStackTrace(log); 294 } 295 return EXIT_CMDERR; 296 } catch (Throwable x) { 297 log.println(taskHelper.getMessage("error.prefix") + " " + x.getMessage()); 298 x.printStackTrace(log); 299 return EXIT_ABNORMAL; 300 } finally { 301 log.flush(); 302 } 303 } 304 305 /* 306 * Jlink API entry point. 307 */ 308 public static void createImage(JlinkConfiguration config, 309 PluginsConfiguration plugins) 310 throws Exception { 311 Objects.requireNonNull(config); 312 Objects.requireNonNull(config.getOutput()); 313 plugins = plugins == null ? new PluginsConfiguration() : plugins; 314 315 // First create the image provider 316 ImageProvider imageProvider = 317 createImageProvider(config, 318 null, 319 IGNORE_SIGNING_DEFAULT, 320 false, 321 false, 322 null); 323 324 // Then create the Plugin Stack 325 ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(plugins); 326 327 //Ask the stack to proceed; 328 stack.operate(imageProvider); 329 } 330 331 /* 332 * Jlink API entry point. 333 */ 334 public static void postProcessImage(ExecutableImage image, List<Plugin> postProcessorPlugins) 335 throws Exception { 336 Objects.requireNonNull(image); 337 Objects.requireNonNull(postProcessorPlugins); 338 PluginsConfiguration config = new PluginsConfiguration(postProcessorPlugins); 339 ImagePluginStack stack = ImagePluginConfiguration. 340 parseConfiguration(config); 341 342 stack.operate((ImagePluginStack stack1) -> image); 343 } 344 345 private void postProcessOnly(Path existingImage) throws Exception { 346 PluginsConfiguration config = taskHelper.getPluginsConfig(null, null); 347 ExecutableImage img = DefaultImageBuilder.getExecutableImage(existingImage); 348 if (img == null) { 349 throw taskHelper.newBadArgs("err.existing.image.invalid"); 350 } 351 postProcessImage(img, config.getPlugins()); 352 } 353 354 // the token for "all modules on the module path" 355 private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH"; 356 private JlinkConfiguration initJlinkConfig() throws BadArgs { 357 Set<String> roots = new HashSet<>(); 358 for (String mod : options.addMods) { 359 if (mod.equals(ALL_MODULE_PATH)) { 360 ModuleFinder finder = newModuleFinder(options.modulePath, options.limitMods, Set.of()); 361 // all observable modules are roots 362 finder.findAll() 363 .stream() 364 .map(ModuleReference::descriptor) 365 .map(ModuleDescriptor::name) 366 .forEach(mn -> roots.add(mn)); 367 } else { 368 roots.add(mod); 369 } 370 } 371 372 ModuleFinder finder = newModuleFinder(options.modulePath, options.limitMods, roots); 373 if (!finder.find("java.base").isPresent()) { 374 Path defModPath = getDefaultModulePath(); 375 if (defModPath != null) { 376 options.modulePath.add(defModPath); 377 } 378 finder = newModuleFinder(options.modulePath, options.limitMods, roots); 379 } 380 381 return new JlinkConfiguration(options.output, 382 roots, 383 options.endian, 384 finder); 385 } 386 387 private void createImage(JlinkConfiguration config) throws Exception { 388 if (options.output == null) { 389 throw taskHelper.newBadArgs("err.output.must.be.specified").showUsage(true); 390 } 391 if (options.addMods.isEmpty()) { 392 throw taskHelper.newBadArgs("err.mods.must.be.specified", "--add-modules") 393 .showUsage(true); 394 } 395 396 // First create the image provider 397 ImageProvider imageProvider = createImageProvider(config, 398 options.packagedModulesPath, 399 options.ignoreSigning, 400 options.bindServices, 401 options.verbose, 402 log); 403 404 // Then create the Plugin Stack 405 ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration( 406 taskHelper.getPluginsConfig(options.output, options.launchers)); 407 408 //Ask the stack to proceed 409 stack.operate(imageProvider); 410 } 411 412 /** 413 * @return the system module path or null 414 */ 415 public static Path getDefaultModulePath() { 416 Path jmods = Paths.get(System.getProperty("java.home"), "jmods"); 417 return Files.isDirectory(jmods)? jmods : null; 418 } 419 420 /* 421 * Returns a module finder of the given module path that limits 422 * the observable modules to those in the transitive closure of 423 * the modules specified in {@code limitMods} plus other modules 424 * specified in the {@code roots} set. 425 * 426 * @throws IllegalArgumentException if java.base module is present 427 * but its descriptor has no version 428 */ 429 public static ModuleFinder newModuleFinder(List<Path> paths, 430 Set<String> limitMods, 431 Set<String> roots) 432 { 433 if (Objects.requireNonNull(paths).isEmpty()) { 434 throw new IllegalArgumentException(taskHelper.getMessage("err.empty.module.path")); 435 } 436 437 Path[] entries = paths.toArray(new Path[0]); 438 Runtime.Version version = Runtime.version(); 439 ModuleFinder finder = ModulePath.of(version, true, entries); 440 441 if (finder.find("java.base").isPresent()) { 442 // use the version of java.base module, if present, as 443 // the release version for multi-release JAR files 444 ModuleDescriptor.Version v = finder.find("java.base").get() 445 .descriptor().version().orElseThrow(() -> 446 new IllegalArgumentException("No version in java.base descriptor") 447 ); 448 449 // java.base version is different than the current runtime version 450 version = Runtime.Version.parse(v.toString()); 451 if (Runtime.version().feature() != version.feature() || 452 Runtime.version().interim() != version.interim()) 453 { 454 // jlink version and java.base version do not match. 455 // We do not (yet) support this mode. 456 throw new IllegalArgumentException(taskHelper.getMessage("err.jlink.version.mismatch", 457 Runtime.version().feature(), Runtime.version().interim(), 458 version.feature(), version.interim())); 459 } 460 } 461 462 // if limitmods is specified then limit the universe 463 if (limitMods != null && !limitMods.isEmpty()) { 464 finder = limitFinder(finder, limitMods, Objects.requireNonNull(roots)); 465 } 466 return finder; 467 } 468 469 private static Path toPathLocation(ResolvedModule m) { 470 Optional<URI> ouri = m.reference().location(); 471 if (!ouri.isPresent()) 472 throw new InternalError(m + " does not have a location"); 473 URI uri = ouri.get(); 474 return Paths.get(uri); 475 } 476 477 478 private static ImageProvider createImageProvider(JlinkConfiguration config, 479 Path retainModulesPath, 480 boolean ignoreSigning, 481 boolean bindService, 482 boolean verbose, 483 PrintWriter log) 484 throws IOException 485 { 486 Configuration cf = bindService ? config.resolveAndBind() 487 : config.resolve(); 488 489 cf.modules().stream() 490 .map(ResolvedModule::reference) 491 .filter(mref -> mref.descriptor().isAutomatic()) 492 .findAny() 493 .ifPresent(mref -> { 494 String loc = mref.location().map(URI::toString).orElse("<unknown>"); 495 throw new IllegalArgumentException( 496 taskHelper.getMessage("err.automatic.module", mref.descriptor().name(), loc)); 497 }); 498 499 if (verbose && log != null) { 500 // print modules to be linked in 501 cf.modules().stream() 502 .sorted(Comparator.comparing(ResolvedModule::name)) 503 .forEach(rm -> log.format("%s %s%n", 504 rm.name(), rm.reference().location().get())); 505 506 // print provider info 507 Set<ModuleReference> references = cf.modules().stream() 508 .map(ResolvedModule::reference).collect(Collectors.toSet()); 509 510 String msg = String.format("%n%s:", taskHelper.getMessage("providers.header")); 511 printProviders(log, msg, references); 512 } 513 514 // emit a warning for any incubating modules in the configuration 515 if (log != null) { 516 String im = cf.modules() 517 .stream() 518 .map(ResolvedModule::reference) 519 .filter(ModuleResolution::hasIncubatingWarning) 520 .map(ModuleReference::descriptor) 521 .map(ModuleDescriptor::name) 522 .collect(Collectors.joining(", ")); 523 524 if (!"".equals(im)) 525 log.println("WARNING: Using incubator modules: " + im); 526 } 527 528 Map<String, Path> mods = cf.modules().stream() 529 .collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation)); 530 return new ImageHelper(cf, mods, config.getByteOrder(), retainModulesPath, ignoreSigning); 531 } 532 533 /* 534 * Returns a ModuleFinder that limits observability to the given root 535 * modules, their transitive dependences, plus a set of other modules. 536 */ 537 public static ModuleFinder limitFinder(ModuleFinder finder, 538 Set<String> roots, 539 Set<String> otherMods) { 540 541 // resolve all root modules 542 Configuration cf = Configuration.empty() 543 .resolve(finder, 544 ModuleFinder.of(), 545 roots); 546 547 // module name -> reference 548 Map<String, ModuleReference> map = new HashMap<>(); 549 cf.modules().forEach(m -> { 550 ModuleReference mref = m.reference(); 551 map.put(mref.descriptor().name(), mref); 552 }); 553 554 // add the other modules 555 otherMods.stream() 556 .map(finder::find) 557 .flatMap(Optional::stream) 558 .forEach(mref -> map.putIfAbsent(mref.descriptor().name(), mref)); 559 560 // set of modules that are observable 561 Set<ModuleReference> mrefs = new HashSet<>(map.values()); 562 563 return new ModuleFinder() { 564 @Override 565 public Optional<ModuleReference> find(String name) { 566 return Optional.ofNullable(map.get(name)); 567 } 568 569 @Override 570 public Set<ModuleReference> findAll() { 571 return mrefs; 572 } 573 }; 574 } 575 576 /* 577 * Returns a map of each service type to the modules that use it 578 * It will include services that are provided by a module but may not used 579 * by any of the observable modules. 580 */ 581 private static Map<String, Set<String>> uses(Set<ModuleReference> modules) { 582 // collects the services used by the modules and print uses 583 Map<String, Set<String>> services = new HashMap<>(); 584 modules.stream() 585 .map(ModuleReference::descriptor) 586 .forEach(md -> { 587 // include services that may not be used by any observable modules 588 md.provides().forEach(p -> 589 services.computeIfAbsent(p.service(), _k -> new HashSet<>())); 590 md.uses().forEach(s -> services.computeIfAbsent(s, _k -> new HashSet<>()) 591 .add(md.name())); 592 }); 593 return services; 594 } 595 596 private static void printProviders(PrintWriter log, 597 String header, 598 Set<ModuleReference> modules) { 599 printProviders(log, header, modules, uses(modules)); 600 } 601 602 /* 603 * Prints the providers that are used by the specified services. 604 * 605 * The specified services maps a service type name to the modules 606 * using the service type which may be empty if no observable module uses 607 * that service. 608 */ 609 private static void printProviders(PrintWriter log, 610 String header, 611 Set<ModuleReference> modules, 612 Map<String, Set<String>> serviceToUses) { 613 if (modules.isEmpty()) 614 return; 615 616 // Build a map of a service type to the provider modules 617 Map<String, Set<ModuleDescriptor>> providers = new HashMap<>(); 618 modules.stream() 619 .map(ModuleReference::descriptor) 620 .forEach(md -> { 621 md.provides().stream() 622 .filter(p -> serviceToUses.containsKey(p.service())) 623 .forEach(p -> providers.computeIfAbsent(p.service(), _k -> new HashSet<>()) 624 .add(md)); 625 }); 626 627 if (!providers.isEmpty()) { 628 log.println(header); 629 } 630 631 // print the providers of the service types used by the specified modules 632 // sorted by the service type name and then provider's module name 633 providers.entrySet().stream() 634 .sorted(Map.Entry.comparingByKey()) 635 .forEach(e -> { 636 String service = e.getKey(); 637 e.getValue().stream() 638 .sorted(Comparator.comparing(ModuleDescriptor::name)) 639 .forEach(md -> 640 md.provides().stream() 641 .filter(p -> p.service().equals(service)) 642 .forEach(p -> { 643 String usedBy; 644 if (serviceToUses.get(p.service()).isEmpty()) { 645 usedBy = "not used by any observable module"; 646 } else { 647 usedBy = serviceToUses.get(p.service()).stream() 648 .sorted() 649 .collect(Collectors.joining(",", "used by ", "")); 650 } 651 log.format(" %s provides %s %s%n", 652 md.name(), p.service(), usedBy); 653 }) 654 ); 655 }); 656 } 657 658 private void suggestProviders(JlinkConfiguration config, List<String> args) 659 throws BadArgs 660 { 661 if (args.size() > 1) { 662 throw taskHelper.newBadArgs("err.orphan.argument", 663 toString(args.subList(1, args.size()))) 664 .showUsage(true); 665 } 666 667 if (options.bindServices) { 668 log.println(taskHelper.getMessage("no.suggested.providers")); 669 return; 670 } 671 672 ModuleFinder finder = config.finder(); 673 if (args.isEmpty()) { 674 // print providers used by the observable modules without service binding 675 Set<ModuleReference> mrefs = finder.findAll(); 676 // print uses of the modules that would be linked into the image 677 mrefs.stream() 678 .sorted(Comparator.comparing(mref -> mref.descriptor().name())) 679 .forEach(mref -> { 680 ModuleDescriptor md = mref.descriptor(); 681 log.format("%s %s%n", md.name(), 682 mref.location().get()); 683 md.uses().stream().sorted() 684 .forEach(s -> log.format(" uses %s%n", s)); 685 }); 686 687 String msg = String.format("%n%s:", taskHelper.getMessage("suggested.providers.header")); 688 printProviders(log, msg, mrefs, uses(mrefs)); 689 690 } else { 691 // comma-separated service types, if specified 692 Set<String> names = Stream.of(args.get(0).split(",")) 693 .collect(Collectors.toSet()); 694 // find the modules that provide the specified service 695 Set<ModuleReference> mrefs = finder.findAll().stream() 696 .filter(mref -> mref.descriptor().provides().stream() 697 .map(ModuleDescriptor.Provides::service) 698 .anyMatch(names::contains)) 699 .collect(Collectors.toSet()); 700 701 // find the modules that uses the specified services 702 Map<String, Set<String>> uses = new HashMap<>(); 703 names.forEach(s -> uses.computeIfAbsent(s, _k -> new HashSet<>())); 704 finder.findAll().stream() 705 .map(ModuleReference::descriptor) 706 .forEach(md -> md.uses().stream() 707 .filter(names::contains) 708 .forEach(s -> uses.get(s).add(md.name()))); 709 710 // check if any name given on the command line are not provided by any module 711 mrefs.stream() 712 .flatMap(mref -> mref.descriptor().provides().stream() 713 .map(ModuleDescriptor.Provides::service)) 714 .forEach(names::remove); 715 if (!names.isEmpty()) { 716 log.println(taskHelper.getMessage("warn.provider.notfound", 717 toString(names))); 718 } 719 720 String msg = String.format("%n%s:", taskHelper.getMessage("suggested.providers.header")); 721 printProviders(log, msg, mrefs, uses); 722 } 723 } 724 725 private static String toString(Collection<String> collection) { 726 return collection.stream().sorted() 727 .collect(Collectors.joining(",")); 728 } 729 730 private String getSaveOpts() { 731 StringBuilder sb = new StringBuilder(); 732 sb.append('#').append(new Date()).append("\n"); 733 for (String c : optionsHelper.getInputCommand()) { 734 sb.append(c).append(" "); 735 } 736 737 return sb.toString(); 738 } 739 740 private static String getBomHeader() { 741 StringBuilder sb = new StringBuilder(); 742 sb.append("#").append(new Date()).append("\n"); 743 sb.append("#Please DO NOT Modify this file").append("\n"); 744 return sb.toString(); 745 } 746 747 private String genBOMContent() throws IOException { 748 StringBuilder sb = new StringBuilder(); 749 sb.append(getBomHeader()); 750 StringBuilder command = new StringBuilder(); 751 for (String c : optionsHelper.getInputCommand()) { 752 command.append(c).append(" "); 753 } 754 sb.append("command").append(" = ").append(command); 755 sb.append("\n"); 756 757 return sb.toString(); 758 } 759 760 private static String genBOMContent(JlinkConfiguration config, 761 PluginsConfiguration plugins) 762 throws IOException { 763 StringBuilder sb = new StringBuilder(); 764 sb.append(getBomHeader()); 765 sb.append(config); 766 sb.append(plugins); 767 return sb.toString(); 768 } 769 770 private static class ImageHelper implements ImageProvider { 771 final ByteOrder order; 772 final Path packagedModulesPath; 773 final boolean ignoreSigning; 774 final Runtime.Version version; 775 final Set<Archive> archives; 776 777 ImageHelper(Configuration cf, 778 Map<String, Path> modsPaths, 779 ByteOrder order, 780 Path packagedModulesPath, 781 boolean ignoreSigning) throws IOException { 782 this.order = order; 783 this.packagedModulesPath = packagedModulesPath; 784 this.ignoreSigning = ignoreSigning; 785 786 // use the version of java.base module, if present, as 787 // the release version for multi-release JAR files 788 this.version = cf.findModule("java.base") 789 .map(ResolvedModule::reference) 790 .map(ModuleReference::descriptor) 791 .flatMap(ModuleDescriptor::version) 792 .map(ModuleDescriptor.Version::toString) 793 .map(Runtime.Version::parse) 794 .orElse(Runtime.version()); 795 796 this.archives = modsPaths.entrySet().stream() 797 .map(e -> newArchive(e.getKey(), e.getValue())) 798 .collect(Collectors.toSet()); 799 } 800 801 private Archive newArchive(String module, Path path) { 802 if (path.toString().endsWith(".jmod")) { 803 return new JmodArchive(module, path); 804 } else if (path.toString().endsWith(".jar")) { 805 ModularJarArchive modularJarArchive = new ModularJarArchive(module, path, version); 806 807 Stream<Archive.Entry> signatures = modularJarArchive.entries().filter((entry) -> { 808 String name = entry.name().toUpperCase(Locale.ENGLISH); 809 810 return name.startsWith("META-INF/") && name.indexOf('/', 9) == -1 && ( 811 name.endsWith(".SF") || 812 name.endsWith(".DSA") || 813 name.endsWith(".RSA") || 814 name.endsWith(".EC") || 815 name.startsWith("META-INF/SIG-") 816 ); 817 }); 818 819 if (signatures.count() != 0) { 820 if (ignoreSigning) { 821 System.err.println(taskHelper.getMessage("warn.signing", path)); 822 } else { 823 throw new IllegalArgumentException(taskHelper.getMessage("err.signing", path)); 824 } 825 } 826 827 return modularJarArchive; 828 } else if (Files.isDirectory(path)) { 829 Path modInfoPath = path.resolve("module-info.class"); 830 if (Files.isRegularFile(modInfoPath)) { 831 return new DirArchive(path, findModuleName(modInfoPath)); 832 } else { 833 throw new IllegalArgumentException( 834 taskHelper.getMessage("err.not.a.module.directory", path)); 835 } 836 } else { 837 throw new IllegalArgumentException( 838 taskHelper.getMessage("err.not.modular.format", module, path)); 839 } 840 } 841 842 private static String findModuleName(Path modInfoPath) { 843 try (BufferedInputStream bis = new BufferedInputStream( 844 Files.newInputStream(modInfoPath))) { 845 return ModuleDescriptor.read(bis).name(); 846 } catch (IOException exp) { 847 throw new IllegalArgumentException(taskHelper.getMessage( 848 "err.cannot.read.module.info", modInfoPath), exp); 849 } 850 } 851 852 @Override 853 public ExecutableImage retrieve(ImagePluginStack stack) throws IOException { 854 ExecutableImage image = ImageFileCreator.create(archives, order, stack); 855 if (packagedModulesPath != null) { 856 // copy the packaged modules to the given path 857 Files.createDirectories(packagedModulesPath); 858 for (Archive a : archives) { 859 Path file = a.getPath(); 860 Path dest = packagedModulesPath.resolve(file.getFileName()); 861 Files.copy(file, dest); 862 } 863 } 864 return image; 865 } 866 } 867 }