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