8174826: jlink support for linking in service provider modules

Reviewed-by: alanb, anazarov
This commit is contained in:
Mandy Chung 2017-03-27 15:12:01 -07:00
parent b9cff83140
commit 340ebfef08
19 changed files with 1098 additions and 167 deletions

@ -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.ArrayList;
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 @@ class JImageTask {
}
static class OptionsValues {
Task task = Task.LIST;
Task task = null;
String directory = ".";
String include = "";
boolean fullVersion;
@ -172,24 +176,31 @@ class JImageTask {
}
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 @@ class JImageTask {
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) {
TASK_HELPER.showVersion(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 @@ class JImageTask {
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;

@ -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.List;
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 @@ public final class Jlink {
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 @@ public final class Jlink {
* @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 @@ public final class Jlink {
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();

@ -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 @@ public class JlinkTask {
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
@ -140,6 +146,9 @@ public class JlinkTask {
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"),
@ -185,6 +194,7 @@ public class JlinkTask {
static class OptionsValues {
boolean help;
String saveoptsfile;
boolean verbose;
boolean version;
boolean fullVersion;
final List<Path> modulePath = new ArrayList<>();
@ -195,6 +205,8 @@ public class JlinkTask {
Path packagedModulesPath;
ByteOrder endian = ByteOrder.nativeOrder();
boolean ignoreSigning = false;
boolean bindServices = false;
boolean suggestProviders = false;
}
int run(String[] args) {
@ -203,7 +215,11 @@ public class JlinkTask {
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 @@ public class JlinkTask {
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 @@ public class JlinkTask {
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 @@ public class JlinkTask {
// 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 @@ public class JlinkTask {
}
}
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 @@ public class JlinkTask {
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 @@ public class JlinkTask {
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 @@ public class JlinkTask {
};
}
/*
* 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");

@ -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.Map.Entry;
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 @@ public final class TaskHelper {
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 @@ public final class TaskHelper {
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 @@ public final class TaskHelper {
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 @@ public final class TaskHelper {
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 @@ public final class TaskHelper {
// 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 @@ public final class TaskHelper {
if (option == null) {
pluginOption = pluginOptions.getOption(name);
if (pluginOption == null) {
throw new BadArgs("err.unknown.option", name).
showUsage(true);
}
@ -556,20 +563,23 @@ public final class TaskHelper {
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) {

@ -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 jdk.tools.jlink.plugin.Plugin;
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 final class AppRuntimeImageBuilder {
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>();

@ -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 @@ main.opt.output=\
\ --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 @@ main.opt.ignore-signing-information=\
\ 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 @@ main.extended.help.footer=\
\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.file.error=cannot access file: {0}
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 @@ err.signing=signed modular JAR {0} is currently not supported,\
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

@ -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.File;
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 @@ public class IntegrationTest {
}
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 @@ public class IntegrationTest {
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<>();

@ -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 @@ public class JLinkTest {
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 @@ public class JLinkTest {
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

@ -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;
}
}
}

@ -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;
}
}
}

@ -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;
}

@ -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();
}
}

@ -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);
}
}
}

@ -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();
}

@ -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;
}

@ -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() {
}
}

@ -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();
}

@ -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;
}

@ -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() {
}
}