mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-03 04:30:06 +00:00
8170289: Re-examine entry point support in jlink
Reviewed-by: mchung
This commit is contained in:
parent
29869a6a87
commit
5f451fea3f
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user