< prev index next >

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * 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
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this

@@ -108,10 +108,16 @@
         }, "--add-modules"),
         new Option<JlinkTask>(true, (task, opt, arg) -> {
             Path path = Paths.get(arg);
             task.options.output = path;
         }, "--output"),
+        new Option<JlinkTask>(false, (task, opt, arg) -> {
+            task.options.bindServices = true;
+        }, "--bind-services"),
+        new Option<JlinkTask>(false, (task, opt, arg) -> {
+            task.options.suggestProviders = true;
+        }, "--suggest-providers", "", true),
         new Option<JlinkTask>(true, (task, opt, arg) -> {
             String[] values = arg.split("=");
             // check values
             if (values.length != 2 || values[0].isEmpty() || values[1].isEmpty()) {
                 throw taskHelper.newBadArgs("err.launcher.value.format", arg);

@@ -139,10 +145,13 @@
             } else {
                 throw taskHelper.newBadArgs("err.unknown.byte.order", arg);
             }
         }, "--endian"),
         new Option<JlinkTask>(false, (task, opt, arg) -> {
+            task.options.verbose = true;
+        }, "--verbose", "-v"),
+        new Option<JlinkTask>(false, (task, opt, arg) -> {
             task.options.version = true;
         }, "--version"),
         new Option<JlinkTask>(true, (task, opt, arg) -> {
             Path path = Paths.get(arg);
             if (Files.exists(path)) {

@@ -183,29 +192,36 @@
             EXIT_ABNORMAL = 4;// terminated abnormally
 
     static class OptionsValues {
         boolean help;
         String  saveoptsfile;
+        boolean verbose;
         boolean version;
         boolean fullVersion;
         final List<Path> modulePath = new ArrayList<>();
         final Set<String> limitMods = new HashSet<>();
         final Set<String> addMods = new HashSet<>();
         Path output;
         final Map<String, String> launchers = new HashMap<>();
         Path packagedModulesPath;
         ByteOrder endian = ByteOrder.nativeOrder();
         boolean ignoreSigning = false;
+        boolean bindServices = false;
+        boolean suggestProviders = false;
     }
 
     int run(String[] args) {
         if (log == null) {
             setLog(new PrintWriter(System.out, true),
                    new PrintWriter(System.err, true));
         }
         try {
-            optionsHelper.handleOptionsNoUnhandled(this, args);
+            List<String> 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;
             }
             if (optionsHelper.shouldListPlugins()) {

@@ -215,22 +231,29 @@
             if (options.version || options.fullVersion) {
                 taskHelper.showVersion(options.fullVersion);
                 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.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;
         } catch (PluginException | IllegalArgumentException |
                  UncheckedIOException |IOException | FindException | ResolutionException e) {
             log.println(taskHelper.getMessage("error.prefix") + " " + e.getMessage());

@@ -264,29 +287,17 @@
             throws Exception {
         Objects.requireNonNull(config);
         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
         ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(plugins);
 

@@ -317,68 +328,66 @@
         postProcessImage(img, config.getPlugins());
     }
 
     // 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);
         }
 
         Set<String> 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)
                       .map(ModuleDescriptor::name)
                       .forEach(mn -> roots.add(mn));
             } else {
                 roots.add(mod);
             }
         }
 
-        ModuleFinder finder = newModuleFinder(options.modulePath,
+        return new JlinkConfiguration(options.output,
+                                      options.modulePath,
+                                      roots,
                                               options.limitMods,
-                                              roots);
+                                      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
      * the modules specified in {@code limitMods} plus other modules
      * specified in the {@code roots} set.

@@ -403,26 +412,36 @@
             throw new InternalError(m + " does not have a location");
         URI uri = ouri.get();
         return Paths.get(uri);
     }
 
-    private static ImageProvider createImageProvider(ModuleFinder finder,
-                                                     Set<String> 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<ModuleReference> 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) {
             String im = cf.modules()
                           .stream()

@@ -436,18 +455,18 @@
                 log.println("WARNING: Using incubator modules: " + im);
         }
 
         Map<String, Path> 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,
+    public static ModuleFinder limitFinder(ModuleFinder finder,
                                             Set<String> roots,
                                             Set<String> otherMods) {
 
         // resolve all root modules
         Configuration cf = Configuration.empty()

@@ -482,10 +501,151 @@
                 return mrefs;
             }
         };
     }
 
+    /*
+     * Returns a map of each service type to the modules that use it
+     */
+    private static Map<String, Set<String>> uses(Set<ModuleReference> modules) {
+        // collects the services used by the modules and print uses
+        Map<String, Set<String>> 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<ModuleReference> 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<ModuleReference> modules,
+                                       Map<String, Set<String>> uses) {
+        if (modules.isEmpty())
+            return;
+
+        // Build a map of a service type to the provider modules
+        Map<String, Set<ModuleDescriptor>> 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<String> 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<ModuleReference> 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<String> names = Stream.of(args.get(0).split(","))
+                .collect(Collectors.toSet());
+            // find the modules that provide the specified service
+            Set<ModuleReference> 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<String, Set<String>> 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<String> collection) {
+        return collection.stream().sorted()
+                         .collect(Collectors.joining(","));
+    }
+
     private String getSaveOpts() {
         StringBuilder sb = new StringBuilder();
         sb.append('#').append(new Date()).append("\n");
         for (String c : optionsHelper.getInputCommand()) {
             sb.append(c).append(" ");
< prev index next >