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