8174826: jlink support for linking in service provider modules
Reviewed-by: alanb, anazarov
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.java Fri Mar 24 15:02:19 2017 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.java Mon Mar 27 15:12:01 2017 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
@@ -35,8 +35,12 @@
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.MissingResourceException;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
import jdk.internal.jimage.BasicImageReader;
import jdk.internal.jimage.ImageHeader;
import jdk.internal.jimage.ImageLocation;
@@ -99,7 +103,7 @@
}
static class OptionsValues {
- Task task = Task.LIST;
+ Task task = null;
String directory = ".";
String include = "";
boolean fullVersion;
@@ -172,24 +176,31 @@
}
try {
- List<String> unhandled = OPTION_HELPER.handleOptions(this, args);
+ String command;
+ String[] remaining = args;
+ try {
+ command = args[0];
+ options.task = Enum.valueOf(Task.class, args[0].toUpperCase(Locale.ENGLISH));
+ remaining = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length)
+ : new String[0];
+ } catch (IllegalArgumentException ex) {
+ command = null;
+ options.task = null;
+ }
- if(!unhandled.isEmpty()) {
- try {
- options.task = Enum.valueOf(Task.class, unhandled.get(0).toUpperCase());
- } catch (IllegalArgumentException ex) {
- throw TASK_HELPER.newBadArgs("err.not.a.task", unhandled.get(0));
- }
+ // process arguments
+ List<String> unhandled = OPTION_HELPER.handleOptions(this, remaining);
+ for (String f : unhandled) {
+ options.jimages.add(new File(f));
+ }
- for(int i = 1; i < unhandled.size(); i++) {
- options.jimages.add(new File(unhandled.get(i)));
- }
- } else if (!options.help && !options.version && !options.fullVersion) {
- throw TASK_HELPER.newBadArgs("err.invalid.task", "<unspecified>");
+ if (options.task == null && !options.help && !options.version && !options.fullVersion) {
+ throw TASK_HELPER.newBadArgs("err.not.a.task",
+ command != null ? command : "<unspecified>");
}
if (options.help) {
- if (unhandled.isEmpty()) {
+ if (options.task == null) {
log.println(TASK_HELPER.getMessage("main.usage", PROGNAME));
Arrays.asList(RECOGNIZED_OPTIONS).stream()
.filter(option -> !option.isHidden())
@@ -203,15 +214,19 @@
log.println(TASK_HELPER.getMessage("main.usage." +
options.task.toString().toLowerCase()));
} catch (MissingResourceException ex) {
- throw TASK_HELPER.newBadArgs("err.not.a.task", unhandled.get(0));
+ throw TASK_HELPER.newBadArgs("err.not.a.task", command);
}
}
return EXIT_OK;
}
if (options.version || options.fullVersion) {
+ if (options.task == null && !unhandled.isEmpty()) {
+ throw TASK_HELPER.newBadArgs("err.not.a.task",
+ Stream.of(args).collect(Collectors.joining(" ")));
+ }
+
TASK_HELPER.showVersion(options.fullVersion);
-
if (unhandled.isEmpty()) {
return EXIT_OK;
}
@@ -435,7 +450,7 @@
iterate(this::listTitle, null, this::verify);
break;
default:
- throw TASK_HELPER.newBadArgs("err.invalid.task",
+ throw TASK_HELPER.newBadArgs("err.not.a.task",
options.task.name()).showUsage(true);
}
return true;
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java Fri Mar 24 15:02:19 2017 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Jlink.java Mon Mar 27 15:12:01 2017 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 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
@@ -24,6 +24,8 @@
*/
package jdk.tools.jlink.internal;
+import java.lang.module.Configuration;
+import java.lang.module.ModuleFinder;
import java.lang.reflect.Layer;
import java.nio.ByteOrder;
import java.nio.file.Path;
@@ -33,6 +35,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+
+import jdk.internal.module.ModulePath;
import jdk.tools.jlink.plugin.Plugin;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.builder.ImageBuilder;
@@ -147,8 +151,8 @@
private final Path output;
private final Set<String> modules;
private final Set<String> limitmods;
-
private final ByteOrder endian;
+ private final ModuleFinder finder;
/**
* jlink configuration,
@@ -160,31 +164,23 @@
* @param endian Jimage byte order. Native order by default
*/
public JlinkConfiguration(Path output,
- List<Path> modulepaths,
- Set<String> modules,
- Set<String> limitmods,
- ByteOrder endian) {
- this.output = output;
- this.modulepaths = modulepaths == null ? Collections.emptyList() : modulepaths;
- this.modules = modules == null ? Collections.emptySet() : modules;
- this.limitmods = limitmods == null ? Collections.emptySet() : limitmods;
- this.endian = endian == null ? ByteOrder.nativeOrder() : endian;
- }
+ List<Path> modulepaths,
+ Set<String> modules,
+ Set<String> limitmods,
+ ByteOrder endian) {
+ if (Objects.requireNonNull(modulepaths).isEmpty()) {
+ throw new IllegalArgumentException("Empty module path");
+ }
+ if (Objects.requireNonNull(modules).isEmpty()) {
+ throw new IllegalArgumentException("Empty modules");
+ }
- /**
- * jlink configuration,
- *
- * @param output Output directory, must not exist.
- * @param modulepaths Modules paths
- * @param modules Root modules to resolve
- * @param limitmods Limit the universe of observable modules
- */
- public JlinkConfiguration(Path output,
- List<Path> modulepaths,
- Set<String> modules,
- Set<String> limitmods) {
- this(output, modulepaths, modules, limitmods,
- ByteOrder.nativeOrder());
+ this.output = output;
+ this.modulepaths = modulepaths;
+ this.modules = modules;
+ this.limitmods = Objects.requireNonNull(limitmods);
+ this.endian = Objects.requireNonNull(endian);
+ this.finder = moduleFinder();
}
/**
@@ -222,6 +218,45 @@
return limitmods;
}
+ /**
+ * Returns {@link ModuleFinder} that finds all observable modules
+ * for this jlink configuration.
+ */
+ public ModuleFinder finder() {
+ return finder;
+ }
+
+ /**
+ * Returns a {@link Configuration} of the given module path,
+ * root modules with full service binding.
+ */
+ public Configuration resolveAndBind()
+ {
+ return Configuration.empty().resolveAndBind(finder,
+ ModuleFinder.of(),
+ modules);
+ }
+
+ /**
+ * Returns a {@link Configuration} of the given module path,
+ * root modules with no service binding.
+ */
+ public Configuration resolve()
+ {
+ return Configuration.empty().resolve(finder,
+ ModuleFinder.of(),
+ modules);
+ }
+
+ private ModuleFinder moduleFinder() {
+ Path[] entries = modulepaths.toArray(new Path[0]);
+ ModuleFinder finder = ModulePath.of(Runtime.version(), true, entries);
+ if (!limitmods.isEmpty()) {
+ finder = JlinkTask.limitFinder(finder, limitmods, modules);
+ }
+ return finder;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java Fri Mar 24 15:02:19 2017 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java Mon Mar 27 15:12:01 2017 -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<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
@@ -141,6 +147,9 @@
}
}, "--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) -> {
@@ -185,6 +194,7 @@
static class OptionsValues {
boolean help;
String saveoptsfile;
+ boolean verbose;
boolean version;
boolean fullVersion;
final List<Path> 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<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;
@@ -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<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)
@@ -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<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) {
@@ -438,16 +457,16 @@
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,
- Set<String> roots,
- Set<String> otherMods) {
+ public static ModuleFinder limitFinder(ModuleFinder finder,
+ Set<String> roots,
+ Set<String> 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<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");
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java Fri Mar 24 15:02:19 2017 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java Mon Mar 27 15:12:01 2017 -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
@@ -46,6 +46,7 @@
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.tools.jlink.internal.plugins.ExcludeJmodSectionPlugin;
@@ -101,8 +102,15 @@
final boolean hidden;
final String name;
final String shortname;
+ final boolean terminalOption;
- public Option(boolean hasArg, Processing<T> processing, boolean hidden, String name, String shortname) {
+ public Option(boolean hasArg,
+ Processing<T> processing,
+ boolean hidden,
+ String name,
+ String shortname,
+ boolean isTerminal)
+ {
if (!name.startsWith("--")) {
throw new RuntimeException("option name missing --, " + name);
}
@@ -115,24 +123,33 @@
this.hidden = hidden;
this.name = name;
this.shortname = shortname;
+ this.terminalOption = isTerminal;
+ }
+
+ public Option(boolean hasArg, Processing<T> processing, String name, String shortname, boolean isTerminal) {
+ this(hasArg, processing, false, name, shortname, isTerminal);
}
public Option(boolean hasArg, Processing<T> processing, String name, String shortname) {
- this(hasArg, processing, false, name, shortname);
+ this(hasArg, processing, false, name, shortname, false);
}
public Option(boolean hasArg, Processing<T> processing, boolean hidden, String name) {
- this(hasArg, processing, hidden, name, "");
+ this(hasArg, processing, hidden, name, "", false);
}
public Option(boolean hasArg, Processing<T> processing, String name) {
- this(hasArg, processing, false, name, "");
+ this(hasArg, processing, false, name, "", false);
}
public boolean isHidden() {
return hidden;
}
+ public boolean isTerminal() {
+ return terminalOption;
+ }
+
public boolean matches(String opt) {
return opt.equals(name) ||
opt.equals(shortname) ||
@@ -179,12 +196,12 @@
private static class PluginOption extends Option<PluginsHelper> {
public PluginOption(boolean hasArg,
Processing<PluginsHelper> processing, boolean hidden, String name, String shortname) {
- super(hasArg, processing, hidden, name, shortname);
+ super(hasArg, processing, hidden, name, shortname, false);
}
public PluginOption(boolean hasArg,
Processing<PluginsHelper> processing, boolean hidden, String name) {
- super(hasArg, processing, hidden, name, "");
+ super(hasArg, processing, hidden, name, "", false);
}
public String resourcePrefix() {
@@ -498,21 +515,13 @@
return null;
}
- // used by jimage. Return unhandled arguments like "create", "describe".
+ /**
+ * Handles all options. This method stops processing the argument
+ * at the first non-option argument i.e. not starts with `-`, or
+ * at the first terminal option and returns the remaining arguments,
+ * if any.
+ */
public List<String> handleOptions(T task, String[] args) throws BadArgs {
- return handleOptions(task, args, true);
- }
-
- // used by jlink. No unhandled arguments like "create", "describe".
- void handleOptionsNoUnhandled(T task, String[] args) throws BadArgs {
- handleOptions(task, args, false);
- }
-
- // shared code that handles options for both jlink and jimage. jimage uses arguments like
- // "create", "describe" etc. as "task names". Those arguments are unhandled here and returned
- // as "unhandled arguments list". jlink does not want such arguments. "collectUnhandled" flag
- // tells whether to allow for unhandled arguments or not.
- private List<String> handleOptions(T task, String[] args, boolean collectUnhandled) throws BadArgs {
// findbugs warning, copy instead of keeping a reference.
command = Arrays.copyOf(args, args.length);
@@ -521,7 +530,6 @@
// Unit tests can call Task multiple time in same JVM.
pluginOptions = new PluginsHelper(null);
- List<String> rest = collectUnhandled? new ArrayList<>() : null;
// process options
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("-")) {
@@ -531,7 +539,6 @@
if (option == null) {
pluginOption = pluginOptions.getOption(name);
if (pluginOption == null) {
-
throw new BadArgs("err.unknown.option", name).
showUsage(true);
}
@@ -556,20 +563,23 @@
pluginOption.process(pluginOptions, name, param);
} else {
option.process(task, name, param);
+ if (option.isTerminal()) {
+ return ++i < args.length
+ ? Stream.of(Arrays.copyOfRange(args, i, args.length))
+ .collect(Collectors.toList())
+ : Collections.emptyList();
+
+ }
}
if (opt.ignoreRest()) {
i = args.length;
}
} else {
- if (collectUnhandled) {
- rest.add(args[i]);
- } else {
- throw new BadArgs("err.orphan.argument", args[i]).
- showUsage(true);
- }
+ return Stream.of(Arrays.copyOfRange(args, i, args.length))
+ .collect(Collectors.toList());
}
}
- return rest;
+ return Collections.emptyList();
}
private Option<T> getOption(String name) {
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java Fri Mar 24 15:02:19 2017 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java Mon Mar 27 15:12:01 2017 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -34,6 +34,7 @@
import java.io.File;
import java.io.IOException;
import java.lang.module.ModuleFinder;
+import java.nio.ByteOrder;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
@@ -93,9 +94,12 @@
public void build() throws IOException {
// jlink main arguments
- Jlink.JlinkConfiguration jlinkConfig = new Jlink.JlinkConfiguration(
- new File("").toPath(), // Unused
- modulePath, addModules, limitModules);
+ Jlink.JlinkConfiguration jlinkConfig =
+ new Jlink.JlinkConfiguration(new File("").toPath(), // Unused
+ modulePath,
+ addModules,
+ limitModules,
+ ByteOrder.nativeOrder());
// plugin configuration
List<Plugin> plugins = new ArrayList<Plugin>();
--- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties Fri Mar 24 15:02:19 2017 -0700
+++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties Mon Mar 27 15:12:01 2017 -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
@@ -24,15 +24,12 @@
#
main.usage.summary=\
-Usage: {0} <options> --module-path <modulepath> --add-modules <mods> --output\n\
-\<path> use --help for a list of possible options
+Usage: {0} <options> --module-path <modulepath> --add-modules <module>[,<module>...]\n\
+\Use --help for a list of possible options
main.usage=\
-Usage: {0} <options> --module-path <modulepath> --add-modules <mods> --output\n\
-\<path> Possible options include:
-
-error.prefix=Error:
-warn.prefix=Warning:
+Usage: {0} <options> --module-path <modulepath> --add-modules <module>[,<module>...]\n\
+\Possible options include:
main.opt.help=\
\ -h, --help Print this help message
@@ -54,9 +51,18 @@
\ --output <path> Location of output path
main.opt.launcher=\
-\ --launcher <command>=<module> Launcher command name for the module\n\
-\ --launcher <command>=<module>/<main>\n\
-\ Launcher command name for the module and the main class
+\ --launcher <name>=<module>[/<mainclass>]\n\
+\ Add a launcher command of the given\n\
+\ name for the module and the main class\n\
+\ if specified
+
+main.opt.bind-services=\
+\ --bind-services Do full service binding
+
+main.opt.suggest-providers=\
+\ --suggest-providers [<name>,...] Suggest providers of services used by\n\
+\ the modules that would be linked, or\n\
+\ of the given service types
main.command.files=\
\ @<filename> Read options from file
@@ -75,6 +81,9 @@
\ signed modular JARs are not copied to\n\
\ the runtime image.
+main.opt.verbose=\
+\ -v, --verbose Enable verbose tracing
+
main.msg.bug=\
An exception has occurred in jlink. \
Please file a bug at the Java Bug Database (http://bugreport.java.com/bugreport/) \
@@ -95,6 +104,9 @@
\n\
+error.prefix=Error:
+warn.prefix=Warning:
+
err.unknown.byte.order:unknown byte order {0}
err.launcher.main.class.empty:launcher main class name cannot be empty: {0}
err.launcher.module.name.empty:launcher module name cannot be empty: {0}
@@ -111,12 +123,12 @@
err.dir.exists={0} already exists
err.badpattern=bad pattern {0}
err.unknown.option=unknown option: {0}
-err.orphan.argument=orphan argument: {0}
err.missing.arg=no value given for {0}
err.internal.error=internal error: {0} {1} {2}
err.invalid.arg.for.option=invalid argument for option: {0}
err.option.after.class=option must be specified before classes: {0}
err.option.unsupported={0} not supported: {1}
+err.orphan.arguments=invalid argument: {0}
err.config.defaults=property {0} is missing from configuration
err.config.defaults.value=wrong value in defaults property: {0}
err.bom.generation=bom file generation failed: {0}
@@ -126,3 +138,7 @@
warn.signing=WARNING: signed modular JAR {0} is currently not supported
warn.invalid.arg=invalid classname or pathname not exist: {0}
warn.split.package=package {0} defined in {1} {2}
+warn.unused.services=Services specified in --suggest-providers not used: {0}
+no.suggested.providers=--bind-services option is specified. No additional providers suggested.
+suggested.providers.header=Suggested providers
+providers.header=Providers
--- a/jdk/test/tools/jlink/IntegrationTest.java Fri Mar 24 15:02:19 2017 -0700
+++ b/jdk/test/tools/jlink/IntegrationTest.java Mon Mar 27 15:12:01 2017 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 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
@@ -25,6 +25,7 @@
import java.io.FileReader;
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -135,11 +136,6 @@
}
System.out.println(jl);
- JlinkConfiguration config
- = new JlinkConfiguration(null, null, null, null);
-
- System.out.println(config);
-
Plugin p = Jlink.newPlugin("toto", Collections.emptyMap(), null);
if (p != null) {
throw new Exception("Plugin should be null");
@@ -163,7 +159,7 @@
Set<String> limits = new HashSet<>();
limits.add("java.management");
JlinkConfiguration config = new Jlink.JlinkConfiguration(output,
- modulePaths, mods, limits, null);
+ modulePaths, mods, limits, ByteOrder.nativeOrder());
List<Plugin> lst = new ArrayList<>();
--- a/jdk/test/tools/jlink/JLinkTest.java Fri Mar 24 15:02:19 2017 -0700
+++ b/jdk/test/tools/jlink/JLinkTest.java Mon Mar 27 15:12:01 2017 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2015, 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
@@ -274,7 +274,7 @@
String[] userOptions = {"--compress", "2", "foo" };
String moduleName = "orphanarg1";
helper.generateDefaultJModule(moduleName, "composite2");
- helper.generateDefaultImage(userOptions, moduleName).assertFailure("Error: orphan argument: foo");
+ helper.generateDefaultImage(userOptions, moduleName).assertFailure("Error: invalid argument: foo");
}
// orphan argument - JDK-8166810
@@ -282,7 +282,7 @@
String[] userOptions = {"--output", "foo", "bar" };
String moduleName = "orphanarg2";
helper.generateDefaultJModule(moduleName, "composite2");
- helper.generateDefaultImage(userOptions, moduleName).assertFailure("Error: orphan argument: bar");
+ helper.generateDefaultImage(userOptions, moduleName).assertFailure("Error: invalid argument: bar");
}
// basic check for --help - JDK-8173717
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/BindServices.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.spi.ToolProvider;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static jdk.testlibrary.Asserts.assertTrue;
+import static jdk.testlibrary.ProcessTools.*;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+/**
+ * @test
+ * @bug 8174826
+ * @library /lib/testlibrary
+ * @modules jdk.compiler jdk.jlink
+ * @build BindServices CompilerUtils jdk.testlibrary.ProcessTools
+ * @run testng BindServices
+ */
+
+public class BindServices {
+ private static final String JAVA_HOME = System.getProperty("java.home");
+ private static final String TEST_SRC = System.getProperty("test.src");
+
+ private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
+ private static final Path MODS_DIR = Paths.get("mods");
+
+ private static final String MODULE_PATH =
+ Paths.get(JAVA_HOME, "jmods").toString() +
+ File.pathSeparator + MODS_DIR.toString();
+
+ // the names of the modules in this test
+ private static String[] modules = new String[] {"m1", "m2", "m3"};
+
+
+ private static boolean hasJmods() {
+ if (!Files.exists(Paths.get(JAVA_HOME, "jmods"))) {
+ System.err.println("Test skipped. NO jmods directory");
+ return false;
+ }
+ return true;
+ }
+
+ /*
+ * Compiles all modules used by the test
+ */
+ @BeforeTest
+ public void compileAll() throws Throwable {
+ if (!hasJmods()) return;
+
+ for (String mn : modules) {
+ Path msrc = SRC_DIR.resolve(mn);
+ assertTrue(CompilerUtils.compile(msrc, MODS_DIR,
+ "--module-source-path", SRC_DIR.toString()));
+ }
+ }
+
+ @Test
+ public void noServiceBinding() throws Throwable {
+ if (!hasJmods()) return;
+
+ Path dir = Paths.get("noServiceBinding");
+
+ // no service binding and does not link m2,m3 providers.
+ JLink.run("--output", dir.toString(),
+ "--module-path", MODULE_PATH,
+ "--add-modules", "m1").output();
+
+ testImage(dir, "m1");
+ }
+
+ @Test
+ public void fullServiceBinding() throws Throwable {
+ if (!hasJmods()) return;
+
+ Path dir = Paths.get("fullServiceBinding");
+
+ // full service binding
+ // m2 is a provider used by m1. During service binding, when m2 is
+ // resolved, m2 uses p2.T that causes m3 to be linked as it is a
+ // provider to p2.T
+ JLink.run("--output", dir.toString(),
+ "--module-path", MODULE_PATH,
+ "--add-modules", "m1",
+ "--bind-services",
+ "--limit-modules", "m1,m2,m3,java.base");
+
+ testImage(dir, "m1", "m2", "m3");
+ }
+
+ @Test
+ public void testVerbose() throws Throwable {
+ if (!hasJmods()) return;
+
+ Path dir = Paths.get("verbose");
+
+ List<String> output =
+ JLink.run("--output", dir.toString(),
+ "--module-path", MODULE_PATH,
+ "--add-modules", "m1",
+ "--bind-services",
+ "--verbose",
+ "--limit-modules", "m1,m2,m3,java.base").output();
+
+ List<String> expected = List.of(
+ "module m1 (" + MODS_DIR.resolve("m1").toUri().toString() + ")",
+ "module m2 (" + MODS_DIR.resolve("m2").toUri().toString() + ")",
+ "module m3 (" + MODS_DIR.resolve("m3").toUri().toString() + ")",
+ "module m1 provides p1.S, used by m1",
+ "module m2 provides p1.S, used by m1",
+ "module m2 provides p2.T, used by m2",
+ "module m3 provides p2.T, used by m2"
+ );
+
+ assertTrue(output.containsAll(expected));
+
+ testImage(dir, "m1", "m2", "m3");
+ }
+
+ /*
+ * Tests the given ${java.home} to only contain the specified modules
+ */
+ private void testImage(Path javaHome, String... modules) throws Throwable {
+ Path java = javaHome.resolve("bin").resolve("java");
+ String[] cmd = Stream.concat(
+ Stream.of(java.toString(), "-m", "m1/p1.Main"),
+ Stream.of(modules)).toArray(String[]::new);
+
+ assertTrue(executeProcess(cmd).outputTo(System.out)
+ .errorTo(System.out)
+ .getExitValue() == 0);
+ }
+
+ static class JLink {
+ static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")
+ .orElseThrow(() ->
+ new RuntimeException("jlink tool not found")
+ );
+
+ static JLink run(String... options) {
+ JLink jlink = new JLink();
+ assertTrue(jlink.execute(options) == 0);
+ return jlink;
+ }
+
+ final List<String> output = new ArrayList<>();
+ private int execute(String... options) {
+ System.out.println("jlink " +
+ Stream.of(options).collect(Collectors.joining(" ")));
+
+ StringWriter writer = new StringWriter();
+ PrintWriter pw = new PrintWriter(writer);
+ int rc = JLINK_TOOL.run(pw, pw, options);
+ System.out.println(writer.toString());
+ Stream.of(writer.toString().split("\\v"))
+ .map(String::trim)
+ .forEach(output::add);
+ return rc;
+ }
+
+ boolean contains(String s) {
+ return output.contains(s);
+ }
+
+ List<String> output() {
+ return output;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/SuggestProviders.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.spi.ToolProvider;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static jdk.testlibrary.Asserts.assertTrue;
+
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+/**
+ * @test
+ * @bug 8174826
+ * @library /lib/testlibrary
+ * @modules jdk.charsets jdk.compiler jdk.jlink
+ * @build SuggestProviders CompilerUtils
+ * @run testng SuggestProviders
+ */
+
+public class SuggestProviders {
+ private static final String JAVA_HOME = System.getProperty("java.home");
+ private static final String TEST_SRC = System.getProperty("test.src");
+
+ private static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
+ private static final Path MODS_DIR = Paths.get("mods");
+
+ private static final String MODULE_PATH =
+ Paths.get(JAVA_HOME, "jmods").toString() +
+ File.pathSeparator + MODS_DIR.toString();
+
+ // the names of the modules in this test
+ private static String[] modules = new String[] {"m1", "m2", "m3"};
+
+
+ private static boolean hasJmods() {
+ if (!Files.exists(Paths.get(JAVA_HOME, "jmods"))) {
+ System.err.println("Test skipped. NO jmods directory");
+ return false;
+ }
+ return true;
+ }
+
+ /*
+ * Compiles all modules used by the test
+ */
+ @BeforeTest
+ public void compileAll() throws Throwable {
+ if (!hasJmods()) return;
+
+ for (String mn : modules) {
+ Path msrc = SRC_DIR.resolve(mn);
+ assertTrue(CompilerUtils.compile(msrc, MODS_DIR,
+ "--module-source-path", SRC_DIR.toString()));
+ }
+ }
+
+ @Test
+ public void suggestProviders() throws Throwable {
+ if (!hasJmods()) return;
+
+ List<String> output = JLink.run("--module-path", MODULE_PATH,
+ "--add-modules", "m1",
+ "--suggest-providers").output();
+ // check a subset of services used by java.base
+ List<String> expected = List.of(
+ "uses java.lang.System$LoggerFinder",
+ "uses java.net.ContentHandlerFactory",
+ "uses java.net.spi.URLStreamHandlerProvider",
+ "uses java.nio.channels.spi.AsynchronousChannelProvider",
+ "uses java.nio.channels.spi.SelectorProvider",
+ "uses java.nio.charset.spi.CharsetProvider",
+ "uses java.nio.file.spi.FileSystemProvider",
+ "uses java.nio.file.spi.FileTypeDetector",
+ "uses java.security.Provider",
+ "uses java.util.spi.ToolProvider",
+ "uses p1.S",
+ "module jdk.charsets provides java.nio.charset.spi.CharsetProvider, used by java.base",
+ "module jdk.compiler provides java.util.spi.ToolProvider, used by java.base",
+ "module jdk.jlink provides java.util.spi.ToolProvider, used by java.base",
+ "module m1 provides p1.S, used by m1",
+ "module m2 provides p1.S, used by m1"
+ );
+
+ assertTrue(output.containsAll(expected));
+ }
+
+ @Test
+ public void providersForServices() throws Throwable {
+ if (!hasJmods()) return;
+
+ List<String> output =
+ JLink.run("--module-path", MODULE_PATH,
+ "--add-modules", "m1",
+ "--suggest-providers",
+ "java.nio.charset.spi.CharsetProvider,p1.S,p2.T").output();
+
+ System.out.println(output);
+ List<String> expected = List.of(
+ "module jdk.charsets provides java.nio.charset.spi.CharsetProvider, used by java.base",
+ "module m1 provides p1.S, used by m1",
+ "module m2 provides p1.S, used by m1",
+ "module m2 provides p2.T, used by m2",
+ "module m3 provides p2.T, used by m2"
+ );
+
+ assertTrue(output.containsAll(expected));
+ }
+
+ @Test
+ public void unusedService() throws Throwable {
+ if (!hasJmods()) return;
+
+ List<String> output =
+ JLink.run("--module-path", MODULE_PATH,
+ "--add-modules", "m1",
+ "--suggest-providers",
+ "nonExistentType").output();
+
+ System.out.println(output);
+ List<String> expected = List.of(
+ "Services specified in --suggest-providers not used: nonExistentType"
+ );
+
+ assertTrue(output.containsAll(expected));
+ }
+
+ @Test
+ public void noSuggestProviders() throws Throwable {
+ if (!hasJmods()) return;
+
+ List<String> output =
+ JLink.run("--module-path", MODULE_PATH,
+ "--add-modules", "m1",
+ "--bind-services",
+ "--limit-modules", "m1,m2,m3,java.base",
+ "--suggest-providers").output();
+
+ String expected = "--bind-services option is specified. No additional providers suggested.";
+ assertTrue(output.contains(expected));
+
+ }
+
+ static class JLink {
+ static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink")
+ .orElseThrow(() ->
+ new RuntimeException("jlink tool not found")
+ );
+
+ static JLink run(String... options) {
+ JLink jlink = new JLink();
+ assertTrue(jlink.execute(options) == 0);
+ return jlink;
+ }
+
+ final List<String> output = new ArrayList<>();
+ private int execute(String... options) {
+ System.out.println("jlink " +
+ Stream.of(options).collect(Collectors.joining(" ")));
+
+ StringWriter writer = new StringWriter();
+ PrintWriter pw = new PrintWriter(writer);
+ int rc = JLINK_TOOL.run(pw, pw, options);
+ System.out.println(writer.toString());
+ Stream.of(writer.toString().split("\\v"))
+ .map(String::trim)
+ .forEach(output::add);
+ return rc;
+ }
+
+ boolean contains(String s) {
+ return output.contains(s);
+ }
+
+ List<String> output() {
+ return output;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m1/module-info.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+module m1 {
+ exports p1;
+ uses p1.S;
+ provides p1.S with p1.Impl;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m1/p1/Impl.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package p1;
+
+public class Impl implements S {
+ public String name() {
+ return this.getClass().getName();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m1/p1/Main.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package p1;
+
+import java.lang.module.ModuleFinder;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * This tests if JAVA_HOME is linked only with the specified modules.
+ */
+public class Main {
+ public static void main(String... args) {
+ Set<String> modules = ModuleFinder.ofSystem().findAll().stream()
+ .map(mref -> mref.descriptor().name())
+ .filter(mn -> !mn.equals("java.base"))
+ .collect(Collectors.toSet());
+
+ Set<String> notLinked = Stream.of(args).filter(mn -> !modules.contains(mn))
+ .collect(Collectors.toSet());
+ if (!notLinked.isEmpty()) {
+ throw new RuntimeException("Expected modules not linked in the image: "
+ + notLinked);
+ }
+ Stream.of(args).forEach(modules::remove);
+
+ if (!modules.isEmpty()) {
+ throw new RuntimeException("Unexpected modules linked in the image: "
+ + modules);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m1/p1/S.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package p1;
+
+public interface S {
+ String name();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m2/module-info.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+module m2 {
+ requires m1;
+ exports p2;
+ uses p2.T;
+ provides p1.S with p2.Impl;
+ provides p2.T with p2.Impl;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m2/p2/Impl.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package p2;
+
+public class Impl implements p1.S, T {
+ public String name() {
+ return this.getClass().getName();
+ }
+
+ public void run() {
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m2/p2/T.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package p2;
+
+public interface T {
+ void run();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m3/module-info.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+module m3 {
+ requires m2;
+ provides p2.T with p3.Impl;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/tools/jlink/bindservices/src/m3/p3/Impl.java Mon Mar 27 15:12:01 2017 -0700
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package p3;
+
+public class Impl implements p2.T {
+ public void run() {
+ }
+}