8170289: Re-examine entry point support in jlink

Reviewed-by: mchung
This commit is contained in:
Athijegannathan Sundararajan 2016-12-19 09:48:59 +05:30
parent 29869a6a87
commit 5f451fea3f
7 changed files with 114 additions and 35 deletions

View File

@ -130,6 +130,7 @@ public final class DefaultImageBuilder implements ImageBuilder {
}
private final Path root;
private final Map<String, String> launchers;
private final Path mdir;
private final Set<String> modules = new HashSet<>();
private String targetOsName;
@ -140,10 +141,9 @@ public final class DefaultImageBuilder implements ImageBuilder {
* @param root The image root directory.
* @throws IOException
*/
public DefaultImageBuilder(Path root) throws IOException {
Objects.requireNonNull(root);
this.root = root;
public DefaultImageBuilder(Path root, Map<String, String> launchers) throws IOException {
this.root = Objects.requireNonNull(root);
this.launchers = Objects.requireNonNull(launchers);
this.mdir = root.resolve("lib");
Files.createDirectories(mdir);
}
@ -235,7 +235,7 @@ public final class DefaultImageBuilder implements ImageBuilder {
// If native files are stripped completely, <root>/bin dir won't exist!
// So, don't bother generating launcher scripts.
if (Files.isDirectory(bin)) {
prepareApplicationFiles(files, modules);
prepareApplicationFiles(files);
}
} catch (IOException ex) {
throw new PluginException(ex);
@ -246,22 +246,44 @@ public final class DefaultImageBuilder implements ImageBuilder {
* Generates launcher scripts.
*
* @param imageContent The image content.
* @param modules The set of modules that the runtime image contains.
* @throws IOException
*/
protected void prepareApplicationFiles(ResourcePool imageContent, Set<String> modules) throws IOException {
protected void prepareApplicationFiles(ResourcePool imageContent) throws IOException {
// generate launch scripts for the modules with a main class
for (String module : modules) {
for (Map.Entry<String, String> entry : launchers.entrySet()) {
String launcherEntry = entry.getValue();
int slashIdx = launcherEntry.indexOf("/");
String module, mainClassName;
if (slashIdx == -1) {
module = launcherEntry;
mainClassName = null;
} else {
module = launcherEntry.substring(0, slashIdx);
assert !module.isEmpty();
mainClassName = launcherEntry.substring(slashIdx + 1);
assert !mainClassName.isEmpty();
}
String path = "/" + module + "/module-info.class";
Optional<ResourcePoolEntry> res = imageContent.findEntry(path);
if (!res.isPresent()) {
throw new IOException("module-info.class not found for " + module + " module");
}
Optional<String> mainClass;
ByteArrayInputStream stream = new ByteArrayInputStream(res.get().contentBytes());
mainClass = ModuleDescriptor.read(stream).mainClass();
if (mainClass.isPresent()) {
Path cmd = root.resolve("bin").resolve(module);
Optional<String> mainClass = ModuleDescriptor.read(stream).mainClass();
if (mainClassName == null && mainClass.isPresent()) {
mainClassName = mainClass.get();
}
if (mainClassName != null) {
// make sure main class exists!
if (!imageContent.findEntry("/" + module + "/" +
mainClassName.replace('.', '/') + ".class").isPresent()) {
throw new IllegalArgumentException(module + " does not have main class: " + mainClassName);
}
String launcherFile = entry.getKey();
Path cmd = root.resolve("bin").resolve(launcherFile);
// generate shell script for Unix platforms
StringBuilder sb = new StringBuilder();
sb.append("#!/bin/sh")
@ -272,7 +294,7 @@ public final class DefaultImageBuilder implements ImageBuilder {
.append("\n");
sb.append("$DIR/java $JLINK_VM_OPTIONS -m ")
.append(module).append('/')
.append(mainClass.get())
.append(mainClassName)
.append(" $@\n");
try (BufferedWriter writer = Files.newBufferedWriter(cmd,
@ -286,7 +308,7 @@ public final class DefaultImageBuilder implements ImageBuilder {
}
// generate .bat file for Windows
if (isWindows()) {
Path bat = root.resolve(BIN_DIRNAME).resolve(module + ".bat");
Path bat = root.resolve(BIN_DIRNAME).resolve(launcherFile + ".bat");
sb = new StringBuilder();
sb.append("@echo off")
.append("\r\n");
@ -296,7 +318,7 @@ public final class DefaultImageBuilder implements ImageBuilder {
.append("\r\n");
sb.append("\"%DIR%\\java\" %JLINK_VM_OPTIONS% -m ")
.append(module).append('/')
.append(mainClass.get())
.append(mainClassName)
.append(" %*\r\n");
try (BufferedWriter writer = Files.newBufferedWriter(bat,
@ -305,6 +327,8 @@ public final class DefaultImageBuilder implements ImageBuilder {
writer.write(sb.toString());
}
}
} else {
throw new IllegalArgumentException(module + " doesn't contain main class & main not specified in command line");
}
}
}

View File

@ -110,6 +110,27 @@ public class JlinkTask {
Path path = Paths.get(arg);
task.options.output = path;
}, "--output"),
new Option<JlinkTask>(true, (task, opt, arg) -> {
String[] values = arg.split("=");
// check values
if (values.length != 2 || values[0].isEmpty() || values[1].isEmpty()) {
throw taskHelper.newBadArgs("err.launcher.value.format", arg);
} else {
String commandName = values[0];
String moduleAndMain = values[1];
int idx = moduleAndMain.indexOf("/");
if (idx != -1) {
if (moduleAndMain.substring(0, idx).isEmpty()) {
throw taskHelper.newBadArgs("err.launcher.module.name.empty", arg);
}
if (moduleAndMain.substring(idx + 1).isEmpty()) {
throw taskHelper.newBadArgs("err.launcher.main.class.empty", arg);
}
}
task.options.launchers.put(commandName, moduleAndMain);
}
}, "--launcher"),
new Option<JlinkTask>(true, (task, opt, arg) -> {
if ("little".equals(arg)) {
task.options.endian = ByteOrder.LITTLE_ENDIAN;
@ -170,6 +191,7 @@ public class JlinkTask {
final Set<String> limitMods = new HashSet<>();
final Set<String> addMods = new HashSet<>();
Path output;
final Map<String, String> launchers = new HashMap<>();
Path packagedModulesPath;
ByteOrder endian = ByteOrder.nativeOrder();
boolean ignoreSigning = false;
@ -287,7 +309,7 @@ public class JlinkTask {
}
private void postProcessOnly(Path existingImage) throws Exception {
PluginsConfiguration config = taskHelper.getPluginsConfig(null);
PluginsConfiguration config = taskHelper.getPluginsConfig(null, null);
ExecutableImage img = DefaultImageBuilder.getExecutableImage(existingImage);
if (img == null) {
throw taskHelper.newBadArgs("err.existing.image.invalid");
@ -336,7 +358,7 @@ public class JlinkTask {
// Then create the Plugin Stack
ImagePluginStack stack = ImagePluginConfiguration.
parseConfiguration(taskHelper.getPluginsConfig(options.output));
parseConfiguration(taskHelper.getPluginsConfig(options.output, options.launchers));
//Ask the stack to proceed
stack.operate(imageProvider);

View File

@ -403,7 +403,7 @@ public final class TaskHelper {
return null;
}
private PluginsConfiguration getPluginsConfig(Path output
private PluginsConfiguration getPluginsConfig(Path output, Map<String, String> launchers
) throws IOException, BadArgs {
if (output != null) {
if (Files.exists(output)) {
@ -440,9 +440,9 @@ public final class TaskHelper {
// recreate or postprocessing don't require an output directory.
ImageBuilder builder = null;
if (output != null) {
builder = new DefaultImageBuilder(output);
builder = new DefaultImageBuilder(output, launchers);
}
return new Jlink.PluginsConfiguration(pluginsList,
builder, lastSorter);
}
@ -745,9 +745,9 @@ public final class TaskHelper {
+ bundleHelper.getMessage(key, args));
}
public PluginsConfiguration getPluginsConfig(Path output)
public PluginsConfiguration getPluginsConfig(Path output, Map<String, String> launchers)
throws IOException, BadArgs {
return pluginOptions.getPluginsConfig(output);
return pluginOptions.getPluginsConfig(output, launchers);
}
public Path getExistingImage() {

View File

@ -49,6 +49,7 @@ import java.util.Set;
*/
public final class AppRuntimeImageBuilder {
private Path outputDir = null;
private Map<String, String> launchers = Collections.emptyMap();
private List<Path> modulePath = null;
private Set<String> addModules = null;
private Set<String> limitModules = null;
@ -62,6 +63,10 @@ public final class AppRuntimeImageBuilder {
outputDir = value;
}
public void setLaunchers(Map<String, String> value) {
launchers = value;
}
public void setModulePath(List<Path> value) {
modulePath = value;
}
@ -120,7 +125,7 @@ public final class AppRuntimeImageBuilder {
// build the image
Jlink.PluginsConfiguration pluginConfig = new Jlink.PluginsConfiguration(
plugins, new DefaultImageBuilder(outputDir), null);
plugins, new DefaultImageBuilder(outputDir, launchers), null);
Jlink jlink = new Jlink();
jlink.build(jlinkConfig, pluginConfig);
}

View File

@ -53,6 +53,11 @@ main.opt.limit-modules=\
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
main.command.files=\
\ @<filename> Read options from file
@ -91,6 +96,9 @@ main.extended.help.footer=\
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}
err.launcher.value.format:launcher value should be of form <command>=<module>[/<main-class>]: {0}
err.output.must.be.specified:--output must be specified
err.modulepath.must.be.specified:--module-path must be specified
err.mods.must.be.specified:no modules specified to {0}

View File

@ -186,7 +186,7 @@ public class IntegrationTest {
lst.add(new MyPostProcessor());
}
// Image builder
DefaultImageBuilder builder = new DefaultImageBuilder(output);
DefaultImageBuilder builder = new DefaultImageBuilder(output, Collections.emptyMap());
PluginsConfiguration plugins
= new Jlink.PluginsConfiguration(lst, builder, null);

View File

@ -87,20 +87,29 @@ public class BasicTest {
JarUtils.createJarFile(jarfile, classes);
Path image = Paths.get("mysmallimage");
runJmod(jarfile.toString(), TEST_MODULE);
runJlink(image, TEST_MODULE, "--compress", "2");
execute(image, TEST_MODULE);
runJmod(jarfile.toString(), TEST_MODULE, true);
runJlink(image, TEST_MODULE, "--compress", "2", "--launcher", "foo=" + TEST_MODULE);
execute(image, "foo");
Files.delete(jmods.resolve(TEST_MODULE + ".jmod"));
image = Paths.get("myimage");
runJmod(classes.toString(), TEST_MODULE);
runJlink(image, TEST_MODULE);
execute(image, TEST_MODULE);
runJmod(classes.toString(), TEST_MODULE, true);
runJlink(image, TEST_MODULE, "--launcher", "bar=" + TEST_MODULE);
execute(image, "bar");
Files.delete(jmods.resolve(TEST_MODULE + ".jmod"));
image = Paths.get("myimage2");
runJmod(classes.toString(), TEST_MODULE, false /* no ModuleMainClass! */);
// specify main class in --launcher command line
runJlink(image, TEST_MODULE, "--launcher", "bar2=" + TEST_MODULE + "/jdk.test.Test");
execute(image, "bar2");
}
private void execute(Path image, String moduleName) throws Throwable {
String cmd = image.resolve("bin").resolve(moduleName).toString();
private void execute(Path image, String scriptName) throws Throwable {
String cmd = image.resolve("bin").resolve(scriptName).toString();
OutputAnalyzer analyzer;
if (System.getProperty("os.name").startsWith("Windows")) {
analyzer = ProcessTools.executeProcess("sh.exe", cmd, "1", "2", "3");
@ -127,14 +136,25 @@ public class BasicTest {
}
}
private void runJmod(String cp, String modName) {
int rc = JMOD_TOOL.run(System.out, System.out, new String[] {
private void runJmod(String cp, String modName, boolean main) {
int rc;
if (main) {
rc = JMOD_TOOL.run(System.out, System.out, new String[] {
"create",
"--class-path", cp,
"--module-version", "1.0",
"--main-class", "jdk.test.Test",
jmods.resolve(modName + ".jmod").toString()
});
} else {
rc = JMOD_TOOL.run(System.out, System.out, new String[] {
"create",
"--class-path", cp,
"--module-version", "1.0",
jmods.resolve(modName + ".jmod").toString(),
});
});
}
if (rc != 0) {
throw new AssertionError("Jmod failed: rc = " + rc);
}