From e0484eb7df6a84b069c7d6cc0b5f3392e2efcc75 Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Mon, 26 Feb 2024 17:59:49 +0100 Subject: [PATCH] Add support for custom modules via the module path --- .../jdk/tools/jlink/internal/JlinkTask.java | 54 +++++++++++++++++-- .../JmodLess/AbstractLinkableRuntimeTest.java | 32 ++++++++++- .../jlink/JmodLess/CustomModuleJlinkTest.java | 17 +++--- 3 files changed, 88 insertions(+), 15 deletions(-) diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java index 1a8dcc9310f96..3100744d2c0dc 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java @@ -49,6 +49,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; @@ -406,14 +407,54 @@ private JlinkConfiguration initJlinkConfig() throws BadArgs { } finder = newModuleFinder(options.modulePath, options.limitMods, roots); } + boolean isLinkFromRuntime = options.modulePath.isEmpty(); + // In case of custom modules outside the JDK we may + // have a non-empty module path, which doesn't include + // java.base. In that case take the JDK modules from the + // current run-time image. + if (finder.find("java.base").isEmpty()) { + isLinkFromRuntime = true; + ModuleFinder runtimeImageFinder = ModuleFinder.ofSystem(); + finder = combinedFinders(finder, runtimeImageFinder, options.limitMods, roots); + } + return new JlinkConfiguration(options.output, roots, finder, - options.modulePath.isEmpty(), + isLinkFromRuntime, options.ignoreModifiedRuntime); } + private ModuleFinder combinedFinders(ModuleFinder finder, + ModuleFinder runtimeImageFinder, Set limitMods, + Set roots) { + ModuleFinder combined = new ModuleFinder() { + + @Override + public Optional find(String name) { + Optional basic = runtimeImageFinder.find(name); + if (basic.isEmpty()) { + return finder.find(name); + } + return basic; + } + + @Override + public Set findAll() { + Set all = new HashSet<>(); + all.addAll(runtimeImageFinder.findAll()); + all.addAll(finder.findAll()); + return Collections.unmodifiableSet(all); + } + }; + // if limitmods is specified then limit the universe + if (limitMods != null && !limitMods.isEmpty()) { + return limitFinder(combined, limitMods, Objects.requireNonNull(roots)); + } + return combined; + } + private void createImage(JlinkConfiguration config) throws Exception { if (options.output == null) { throw taskHelper.newBadArgs("err.output.must.be.specified").showUsage(true); @@ -633,14 +674,13 @@ private static ImageHelper createImageProvider(JlinkConfiguration config, .orElse(Runtime.version()); Set archives = mods.entrySet().stream() - .map(e -> config.linkFromRuntimeImage() ? new JRTArchive(e.getKey(), e.getValue(), !config.ignoreModifiedRuntime()) - : newArchive(e.getKey(), e.getValue(), version, ignoreSigning)) + .map(e -> newArchive(e.getKey(), e.getValue(), version, ignoreSigning, config)) .collect(Collectors.toSet()); return new ImageHelper(archives, targetPlatform, retainModulesPath); } - private static Archive newArchive(String module, Path path, Runtime.Version version, boolean ignoreSigning) { + private static Archive newArchive(String module, Path path, Runtime.Version version, boolean ignoreSigning, JlinkConfiguration config) { if (path.toString().endsWith(".jmod")) { return new JmodArchive(module, path); } else if (path.toString().endsWith(".jar")) { @@ -667,6 +707,12 @@ private static Archive newArchive(String module, Path path, Runtime.Version vers } } return modularJarArchive; + } else if (config.linkFromRuntimeImage()) { + // This is after jmod and modular jar branches, since, in runtime link + // mode custom - JDK external modules - are only supported via the + // module path. Directory module paths are not supported since those + // clash with JRT-FS based archives of JRTArchive. + return new JRTArchive(module, path, !config.ignoreModifiedRuntime()); } else if (Files.isDirectory(path)) { Path modInfoPath = path.resolve("module-info.class"); if (Files.isRegularFile(modInfoPath)) { diff --git a/test/jdk/tools/jlink/JmodLess/AbstractLinkableRuntimeTest.java b/test/jdk/tools/jlink/JmodLess/AbstractLinkableRuntimeTest.java index 168e94f95d58b..bd57deb2a8cb7 100644 --- a/test/jdk/tools/jlink/JmodLess/AbstractLinkableRuntimeTest.java +++ b/test/jdk/tools/jlink/JmodLess/AbstractLinkableRuntimeTest.java @@ -149,6 +149,12 @@ protected Path jlinkUsingImage(JlinkSpec spec, OutputAnalyzerHandler handler, Pr if (spec.getExtraJlinkOpts() != null && !spec.getExtraJlinkOpts().isEmpty()) { jlinkCmd.addAll(spec.getExtraJlinkOpts()); } + if (spec.getModulePath() != null) { + for (String mp: spec.getModulePath()) { + jlinkCmd.add("--module-path"); + jlinkCmd.add(mp); + } + } jlinkCmd = Collections.unmodifiableList(jlinkCmd); // freeze System.out.println("DEBUG: jmod-less jlink command: " + jlinkCmd.stream().collect( Collectors.joining(" "))); @@ -360,11 +366,13 @@ static class JlinkSpec { final List unexpectedLocations; final String[] expectedFiles; final List extraJlinkOpts; + final List modulePath; JlinkSpec(Path imageToUse, Helper helper, String name, List modules, String validatingModule, List expectedLocations, List unexpectedLocations, String[] expectedFiles, - List extraJlinkOpts) { + List extraJlinkOpts, + List modulePath) { this.imageToUse = imageToUse; this.helper = helper; this.name = name; @@ -374,6 +382,7 @@ static class JlinkSpec { this.unexpectedLocations = unexpectedLocations; this.expectedFiles = expectedFiles; this.extraJlinkOpts = extraJlinkOpts; + this.modulePath = modulePath; } public Path getImageToUse() { @@ -411,6 +420,10 @@ public String[] getExpectedFiles() { public List getExtraJlinkOpts() { return extraJlinkOpts; } + + public List getModulePath() { + return modulePath; + } } static class JlinkSpecBuilder { @@ -423,6 +436,7 @@ static class JlinkSpecBuilder { List unexpectedLocations = new ArrayList<>(); List expectedFiles = new ArrayList<>(); List extraJlinkOpts = new ArrayList<>(); + List modulePath = new ArrayList<>(); JlinkSpec build() { if (imageToUse == null) { @@ -437,7 +451,16 @@ JlinkSpec build() { if (validatingModule == null) { throw new IllegalStateException("No module specified for after generation validation!"); } - return new JlinkSpec(imageToUse, helper, name, modules, validatingModule, expectedLocations, unexpectedLocations, expectedFiles.toArray(new String[0]), extraJlinkOpts); + return new JlinkSpec(imageToUse, + helper, + name, + modules, + validatingModule, + expectedLocations, + unexpectedLocations, + expectedFiles.toArray(new String[0]), + extraJlinkOpts, + modulePath); } JlinkSpecBuilder imagePath(Path image) { @@ -465,6 +488,11 @@ JlinkSpecBuilder validatingModule(String module) { return this; } + JlinkSpecBuilder addModulePath(String modulePath) { + this.modulePath.add(modulePath); + return this; + } + JlinkSpecBuilder expectedLocation(String location) { expectedLocations.add(location); return this; diff --git a/test/jdk/tools/jlink/JmodLess/CustomModuleJlinkTest.java b/test/jdk/tools/jlink/JmodLess/CustomModuleJlinkTest.java index f7991674552d6..81803d21b2122 100644 --- a/test/jdk/tools/jlink/JmodLess/CustomModuleJlinkTest.java +++ b/test/jdk/tools/jlink/JmodLess/CustomModuleJlinkTest.java @@ -28,9 +28,9 @@ /* - * FIXME: This needs thinking about + * @test * @summary Test jmod-less jlink with a custom module - * @requires (vm.compMode != "Xcomp" & os.maxMemory >= 2g) + * @requires (jlink.runtime.linkable & vm.compMode != "Xcomp" & os.maxMemory >= 2g) * @library ../../lib /test/lib * @enablePreview * @modules java.base/jdk.internal.classfile @@ -54,22 +54,21 @@ void runTest(Helper helper) throws Exception { String customModule = "leaf1"; helper.generateDefaultJModule(customModule); - // create a base image including jdk.jlink and the leaf1 module. This will - // add the leaf1 module's module path. + // create a base image for runtime linking Path jlinkImage = createRuntimeLinkImage(new BaseJlinkSpecBuilder() .helper(helper) .name("cmod-jlink") - .addModule(customModule) - .validatingModule("java.base") // not used + .addModule("java.base") + .validatingModule("java.base") .build()); - // Now that the base image already includes the 'leaf1' module, it should - // be possible to jlink it again, asking for *only* the 'leaf1' plugin even - // though we won't have any jmods directories present. + // Next jlink using the current runtime image for java.base, but take + // the custom module from the module path. Path finalImage = jlinkUsingImage(new JlinkSpecBuilder() .imagePath(jlinkImage) .helper(helper) .name(customModule) + .addModulePath(helper.defaultModulePath(false)) .expectedLocation(String.format("/%s/%s/com/foo/bar/X.class", customModule, customModule)) .addModule(customModule) .validatingModule(customModule)