--- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java 2017-03-23 14:42:17.000000000 -0700 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java 2017-03-23 14:42:17.000000000 -0700 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -110,6 +110,12 @@ Path path = Paths.get(arg); task.options.output = path; }, "--output"), + new Option(false, (task, opt, arg) -> { + task.options.bindServices = true; + }, "--bind-services"), + new Option(false, (task, opt, arg) -> { + task.options.suggestProviders = true; + }, "--suggest-providers", "", true), new Option(true, (task, opt, arg) -> { String[] values = arg.split("="); // check values @@ -141,6 +147,9 @@ } }, "--endian"), new Option(false, (task, opt, arg) -> { + task.options.verbose = true; + }, "--verbose", "-v"), + new Option(false, (task, opt, arg) -> { task.options.version = true; }, "--version"), new Option(true, (task, opt, arg) -> { @@ -185,6 +194,7 @@ static class OptionsValues { boolean help; String saveoptsfile; + boolean verbose; boolean version; boolean fullVersion; final List modulePath = new ArrayList<>(); @@ -195,6 +205,8 @@ Path packagedModulesPath; ByteOrder endian = ByteOrder.nativeOrder(); boolean ignoreSigning = false; + boolean bindServices = false; + boolean suggestProviders = false; } int run(String[] args) { @@ -203,7 +215,11 @@ new PrintWriter(System.err, true)); } try { - optionsHelper.handleOptionsNoUnhandled(this, args); + List remaining = optionsHelper.handleOptions(this, args); + if (remaining.size() > 0 && !options.suggestProviders) { + throw taskHelper.newBadArgs("err.orphan.arguments", toString(remaining)) + .showUsage(true); + } if (options.help) { optionsHelper.showHelp(PROGNAME); return EXIT_OK; @@ -217,17 +233,24 @@ return EXIT_OK; } - if (taskHelper.getExistingImage() == null) { - if (options.modulePath.isEmpty()) { - throw taskHelper.newBadArgs("err.modulepath.must.be.specified").showUsage(true); - } - createImage(); - } else { + if (taskHelper.getExistingImage() != null) { postProcessOnly(taskHelper.getExistingImage()); + return EXIT_OK; } - if (options.saveoptsfile != null) { - Files.write(Paths.get(options.saveoptsfile), getSaveOpts().getBytes()); + if (options.modulePath.isEmpty()) { + throw taskHelper.newBadArgs("err.modulepath.must.be.specified") + .showUsage(true); + } + + JlinkConfiguration config = initJlinkConfig(); + if (options.suggestProviders) { + suggestProviders(config, remaining); + } else { + createImage(config); + if (options.saveoptsfile != null) { + Files.write(Paths.get(options.saveoptsfile), getSaveOpts().getBytes()); + } } return EXIT_OK; @@ -266,25 +289,13 @@ Objects.requireNonNull(config.getOutput()); plugins = plugins == null ? new PluginsConfiguration() : plugins; - if (config.getModulepaths().isEmpty()) { - throw new IllegalArgumentException("Empty module paths"); - } - - ModuleFinder finder = newModuleFinder(config.getModulepaths(), - config.getLimitmods(), - config.getModules()); - - if (config.getModules().isEmpty()) { - throw new IllegalArgumentException("No modules to add"); - } - // First create the image provider ImageProvider imageProvider = - createImageProvider(finder, - config.getModules(), - config.getByteOrder(), + createImageProvider(config, null, IGNORE_SIGNING_DEFAULT, + false, + false, null); // Then create the Plugin Stack @@ -319,20 +330,24 @@ // the token for "all modules on the module path" private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH"; - private void createImage() throws Exception { - if (options.output == null) { - throw taskHelper.newBadArgs("err.output.must.be.specified").showUsage(true); - } - + private JlinkConfiguration initJlinkConfig() throws BadArgs { if (options.addMods.isEmpty()) { throw taskHelper.newBadArgs("err.mods.must.be.specified", "--add-modules") - .showUsage(true); + .showUsage(true); } Set roots = new HashSet<>(); for (String mod : options.addMods) { if (mod.equals(ALL_MODULE_PATH)) { - ModuleFinder finder = modulePathFinder(); + Path[] entries = options.modulePath.toArray(new Path[0]); + ModuleFinder finder = ModulePath.of(Runtime.version(), true, entries); + if (!options.limitMods.isEmpty()) { + // finder for the observable modules specified in + // the --module-path and --limit-modules options + finder = limitFinder(finder, options.limitMods, Collections.emptySet()); + } + + // all observable modules are roots finder.findAll() .stream() .map(ModuleReference::descriptor) @@ -343,40 +358,34 @@ } } - ModuleFinder finder = newModuleFinder(options.modulePath, - options.limitMods, - roots); + return new JlinkConfiguration(options.output, + options.modulePath, + roots, + options.limitMods, + options.endian); + } + private void createImage(JlinkConfiguration config) throws Exception { + if (options.output == null) { + throw taskHelper.newBadArgs("err.output.must.be.specified").showUsage(true); + } // First create the image provider - ImageProvider imageProvider = createImageProvider(finder, - roots, - options.endian, + ImageProvider imageProvider = createImageProvider(config, options.packagedModulesPath, options.ignoreSigning, + options.bindServices, + options.verbose, log); // Then create the Plugin Stack - ImagePluginStack stack = ImagePluginConfiguration. - parseConfiguration(taskHelper.getPluginsConfig(options.output, options.launchers)); + ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration( + taskHelper.getPluginsConfig(options.output, options.launchers)); //Ask the stack to proceed stack.operate(imageProvider); } - /** - * Returns a module finder to find the observable modules specified in - * the --module-path and --limit-modules options - */ - private ModuleFinder modulePathFinder() { - Path[] entries = options.modulePath.toArray(new Path[0]); - ModuleFinder finder = ModulePath.of(Runtime.version(), true, entries); - if (!options.limitMods.isEmpty()) { - finder = limitFinder(finder, options.limitMods, Collections.emptySet()); - } - return finder; - } - /* * Returns a module finder of the given module path that limits * the observable modules to those in the transitive closure of @@ -405,22 +414,32 @@ return Paths.get(uri); } - private static ImageProvider createImageProvider(ModuleFinder finder, - Set roots, - ByteOrder order, + + private static ImageProvider createImageProvider(JlinkConfiguration config, Path retainModulesPath, boolean ignoreSigning, + boolean bindService, + boolean verbose, PrintWriter log) throws IOException { - if (roots.isEmpty()) { - throw new IllegalArgumentException("empty modules and limitmods"); - } + Configuration cf = bindService ? config.resolveAndBind() + : config.resolve(); - Configuration cf = Configuration.empty() - .resolve(finder, - ModuleFinder.of(), - roots); + if (verbose && log != null) { + // print modules to be linked in + cf.modules().stream() + .sorted(Comparator.comparing(ResolvedModule::name)) + .forEach(rm -> log.format("module %s (%s)%n", + rm.name(), rm.reference().location().get())); + + // print provider info + Set references = cf.modules().stream() + .map(ResolvedModule::reference).collect(Collectors.toSet()); + + String msg = String.format("%n%s:", taskHelper.getMessage("providers.header")); + printProviders(log, msg, references); + } // emit a warning for any incubating modules in the configuration if (log != null) { @@ -438,16 +457,16 @@ Map mods = cf.modules().stream() .collect(Collectors.toMap(ResolvedModule::name, JlinkTask::toPathLocation)); - return new ImageHelper(cf, mods, order, retainModulesPath, ignoreSigning); + return new ImageHelper(cf, mods, config.getByteOrder(), retainModulesPath, ignoreSigning); } /* * Returns a ModuleFinder that limits observability to the given root * modules, their transitive dependences, plus a set of other modules. */ - private static ModuleFinder limitFinder(ModuleFinder finder, - Set roots, - Set otherMods) { + public static ModuleFinder limitFinder(ModuleFinder finder, + Set roots, + Set otherMods) { // resolve all root modules Configuration cf = Configuration.empty() @@ -484,6 +503,147 @@ }; } + /* + * Returns a map of each service type to the modules that use it + */ + private static Map> uses(Set modules) { + // collects the services used by the modules and print uses + Map> uses = new HashMap<>(); + modules.stream() + .map(ModuleReference::descriptor) + .forEach(md -> md.uses().forEach(s -> + uses.computeIfAbsent(s, _k -> new HashSet<>()).add(md.name())) + ); + return uses; + } + + private static void printProviders(PrintWriter log, + String header, + Set modules) { + printProviders(log, header, modules, uses(modules)); + } + + /* + * Prints the providers that are used by the services specified in + * the given modules. + * + * The specified uses maps a service type name to the modules + * using the service type and that may or may not be present + * the given modules. + */ + private static void printProviders(PrintWriter log, + String header, + Set modules, + Map> uses) { + if (modules.isEmpty()) + return; + + // Build a map of a service type to the provider modules + Map> providers = new HashMap<>(); + modules.stream() + .map(ModuleReference::descriptor) + .forEach(md -> { + md.provides().stream() + .filter(p -> uses.containsKey(p.service())) + .forEach(p -> providers.computeIfAbsent(p.service(), _k -> new HashSet<>()) + .add(md)); + }); + + if (!providers.isEmpty()) { + log.println(header); + } + + // print the providers of the service types used by the specified modules + // sorted by the service type name and then provider's module name + providers.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .forEach(e -> { + String service = e.getKey(); + e.getValue().stream() + .sorted(Comparator.comparing(ModuleDescriptor::name)) + .forEach(md -> + md.provides().stream() + .filter(p -> p.service().equals(service)) + .forEach(p -> log.format(" module %s provides %s, used by %s%n", + md.name(), p.service(), + uses.get(p.service()).stream() + .sorted() + .collect(Collectors.joining(",")))) + ); + }); + } + + private void suggestProviders(JlinkConfiguration config, List args) + throws BadArgs + { + if (args.size() > 1) { + throw taskHelper.newBadArgs("err.orphan.argument", + toString(args.subList(1, args.size()))) + .showUsage(true); + } + + if (options.bindServices) { + log.println(taskHelper.getMessage("no.suggested.providers")); + return; + } + + ModuleFinder finder = config.finder(); + if (args.isEmpty()) { + // print providers used by the modules resolved without service binding + Configuration cf = config.resolve(); + Set mrefs = cf.modules().stream() + .map(ResolvedModule::reference) + .collect(Collectors.toSet()); + + // print uses of the modules that would be linked into the image + mrefs.stream() + .sorted(Comparator.comparing(mref -> mref.descriptor().name())) + .forEach(mref -> { + ModuleDescriptor md = mref.descriptor(); + log.format("module %s located (%s)%n", md.name(), + mref.location().get()); + md.uses().stream().sorted() + .forEach(s -> log.format(" uses %s%n", s)); + }); + + String msg = String.format("%n%s:", taskHelper.getMessage("suggested.providers.header")); + printProviders(log, msg, finder.findAll(), uses(mrefs)); + + } else { + // comma-separated service types, if specified + Set names = Stream.of(args.get(0).split(",")) + .collect(Collectors.toSet()); + // find the modules that provide the specified service + Set mrefs = finder.findAll().stream() + .filter(mref -> mref.descriptor().provides().stream() + .map(ModuleDescriptor.Provides::service) + .anyMatch(names::contains)) + .collect(Collectors.toSet()); + + // the specified services may or may not be in the modules that + // would be linked in. So find uses declared in all observable modules + Map> uses = uses(finder.findAll()); + + // check if any name given on the command line are unused service + mrefs.stream() + .flatMap(mref -> mref.descriptor().provides().stream() + .map(ModuleDescriptor.Provides::service)) + .forEach(names::remove); + if (!names.isEmpty()) { + log.println(taskHelper.getMessage("warn.unused.services", + toString(names))); + } + + String msg = String.format("%n%s:", taskHelper.getMessage("suggested.providers.header")); + printProviders(log, msg, mrefs, uses); + } + } + + private static String toString(Collection collection) { + return collection.stream().sorted() + .collect(Collectors.joining(",")); + } + private String getSaveOpts() { StringBuilder sb = new StringBuilder(); sb.append('#').append(new Date()).append("\n");