--- old/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java 2016-12-16 21:56:14.000000000 +0530 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/builder/DefaultImageBuilder.java 2016-12-16 21:56:13.000000000 +0530 @@ -130,6 +130,7 @@ } private final Path root; + private final Map launchers; private final Path mdir; private final Set modules = new HashSet<>(); private String targetOsName; @@ -140,10 +141,9 @@ * @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 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 @@ // If native files are stripped completely, /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,38 @@ * 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 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 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 res = imageContent.findEntry(path); if (!res.isPresent()) { throw new IOException("module-info.class not found for " + module + " module"); } - Optional mainClass; ByteArrayInputStream stream = new ByteArrayInputStream(res.get().contentBytes()); - mainClass = ModuleDescriptor.read(stream).mainClass(); + Optional mainClass = ModuleDescriptor.read(stream).mainClass(); if (mainClass.isPresent()) { - Path cmd = root.resolve("bin").resolve(module); + mainClassName = mainClass.get(); + } + + if (mainClassName != null) { + 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 +288,7 @@ .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 +302,7 @@ } // 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 +312,7 @@ .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 +321,8 @@ writer.write(sb.toString()); } } + } else { + throw new IllegalArgumentException(module + " doesn't contain main class & main not specified in command line"); } } } --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java 2016-12-16 21:56:15.000000000 +0530 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java 2016-12-16 21:56:14.000000000 +0530 @@ -109,6 +109,27 @@ task.options.output = path; }, "--output"), new Option(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(true, (task, opt, arg) -> { if ("little".equals(arg)) { task.options.endian = ByteOrder.LITTLE_ENDIAN; } else if ("big".equals(arg)) { @@ -168,6 +189,7 @@ final Set limitMods = new HashSet<>(); final Set addMods = new HashSet<>(); Path output; + final Map launchers = new HashMap<>(); Path packagedModulesPath; ByteOrder endian = ByteOrder.nativeOrder(); boolean ignoreSigning = false; @@ -284,7 +306,7 @@ } 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"); @@ -332,7 +354,7 @@ // 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); --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java 2016-12-16 21:56:16.000000000 +0530 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/TaskHelper.java 2016-12-16 21:56:16.000000000 +0530 @@ -403,7 +403,7 @@ return null; } - private PluginsConfiguration getPluginsConfig(Path output + private PluginsConfiguration getPluginsConfig(Path output, Map launchers ) throws IOException, BadArgs { if (output != null) { if (Files.exists(output)) { @@ -440,9 +440,9 @@ // 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 @@ + bundleHelper.getMessage(key, args)); } - public PluginsConfiguration getPluginsConfig(Path output) + public PluginsConfiguration getPluginsConfig(Path output, Map launchers) throws IOException, BadArgs { - return pluginOptions.getPluginsConfig(output); + return pluginOptions.getPluginsConfig(output, launchers); } public Path getExistingImage() { --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java 2016-12-16 21:56:17.000000000 +0530 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java 2016-12-16 21:56:17.000000000 +0530 @@ -49,6 +49,7 @@ */ public final class AppRuntimeImageBuilder { private Path outputDir = null; + private Map launchers = Collections.emptyMap(); private List modulePath = null; private Set addModules = null; private Set limitModules = null; @@ -62,6 +63,10 @@ outputDir = value; } + public void setLaunchers(Map value) { + launchers = value; + } + public void setModulePath(List value) { modulePath = value; } @@ -120,7 +125,7 @@ // 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); } --- old/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties 2016-12-16 21:56:18.000000000 +0530 +++ new/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties 2016-12-16 21:56:18.000000000 +0530 @@ -53,6 +53,11 @@ main.opt.output=\ \ --output Location of output path +main.opt.launcher=\ +\ --launcher = Launcher command name for the module\n\ +\ --launcher =/
\n\ +\ Launcher command name for the module and the main class + main.command.files=\ \ @ Read options from file @@ -91,6 +96,9 @@ 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 =: {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} --- old/test/tools/jlink/IntegrationTest.java 2016-12-16 21:56:19.000000000 +0530 +++ new/test/tools/jlink/IntegrationTest.java 2016-12-16 21:56:19.000000000 +0530 @@ -186,7 +186,7 @@ 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); --- old/test/tools/jlink/basic/BasicTest.java 2016-12-16 21:56:21.000000000 +0530 +++ new/test/tools/jlink/basic/BasicTest.java 2016-12-16 21:56:20.000000000 +0530 @@ -87,20 +87,29 @@ 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 @@ } } - 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); }