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 6717d5517d1c5..928b9a47934af 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 @@ -277,14 +277,6 @@ int run(String[] args) { return EXIT_OK; } - if (options.modulePath.isEmpty()) { - // no --module-path specified - try to set $JAVA_HOME/jmods if that exists - Path jmods = getDefaultModulePath(); - if (jmods != null) { - options.modulePath.add(jmods); - } - } - JlinkConfiguration config = initJlinkConfig(); outputPath = config.getOutput(); if (options.suggestProviders) { @@ -377,8 +369,13 @@ public static void createImage(JlinkConfiguration config, // the token for "all modules on the module path" private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH"; private JlinkConfiguration initJlinkConfig() throws BadArgs { + // Empty module path not allowed with ALL-MODULE-PATH in --add-modules + if (options.addMods.contains(ALL_MODULE_PATH) && options.modulePath.isEmpty()) { + throw taskHelper.newBadArgs("err.no.module.path"); + } ModuleFinder appModuleFinder = newModuleFinder(options.modulePath); ModuleFinder finder = appModuleFinder; + boolean isLinkFromRuntime = false; if (!appModuleFinder.find("java.base").isPresent()) { // If the application module finder doesn't contain the @@ -393,8 +390,9 @@ private JlinkConfiguration initJlinkConfig() throws BadArgs { // include the java.base module. Path defModPath = getDefaultModulePath(); if (defModPath != null) { - options.modulePath.add(defModPath); - finder = newModuleFinder(options.modulePath); + List<Path> combinedPaths = new ArrayList<>(options.modulePath); + combinedPaths.add(defModPath); + finder = newModuleFinder(combinedPaths); } // We've just added the default module path ('jmods'). If we still // don't find java.base, we must resolve JDK modules from the @@ -419,8 +417,31 @@ private JlinkConfiguration initJlinkConfig() throws BadArgs { Set<String> roots = new HashSet<>(); for (String mod : options.addMods) { if (mod.equals(ALL_MODULE_PATH)) { - ModuleFinder mf = newLimitedFinder(finder, options.limitMods, - Set.of()); + // Using --limit-modules with ALL-MODULE-PATH is an error + if (!options.limitMods.isEmpty()) { + throw taskHelper.newBadArgs("err.limit.modules"); + } + // all observable modules in the app module path are roots + Set<String> initialRoots = appModuleFinder.findAll() + .stream() + .map(ModuleReference::descriptor) + .map(ModuleDescriptor::name) + .collect(Collectors.toSet()); + + // Error if no module is found on the app module path + if (initialRoots.isEmpty()) { + String modPath = options.modulePath.stream() + .map(a -> a.toString()) + .collect(Collectors.joining(", ")); + throw taskHelper.newBadArgs("err.empty.module.path", modPath); + } + + // Use a module finder with limited observability, as determined + // by initialRoots, to find the observable modules from the + // application module path (--module-path option) only. We must + // not include JDK modules from the default module path or the + // run-time image. + ModuleFinder mf = limitFinder(finder, initialRoots, Set.of()); mf.findAll() .stream() .map(ModuleReference::descriptor) @@ -430,7 +451,7 @@ private JlinkConfiguration initJlinkConfig() throws BadArgs { roots.add(mod); } } - finder = newLimitedFinder(finder, options.limitMods, roots); + finder = limitFinder(finder, options.limitMods, roots); // --keep-packaged-modules doesn't make sense as we are not linking // from packaged modules to begin with. @@ -497,7 +518,7 @@ public static Path getDefaultModulePath() { * specified in {@code limitMods} plus other modules specified in the * {@code roots} set. */ - public static ModuleFinder newLimitedFinder(ModuleFinder finder, + public static ModuleFinder limitFinder(ModuleFinder finder, Set<String> limitMods, Set<String> roots) { // if limitMods is specified then limit the universe diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties index b5880a3556155..a491b758ea0ee 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties @@ -127,7 +127,9 @@ err.runtime.link.packaged.mods=This JDK has no packaged modules.\ err.runtime.link.modified.file={0} has been modified err.runtime.link.patched.module=jlink does not support linking from the run-time image\ \ when running on a patched runtime with --patch-module -err.empty.module.path=empty module path +err.no.module.path=--module-path option must be specified with --add-modules ALL-MODULE-PATH +err.empty.module.path=No module found in module path ''{0}'' with --add-modules ALL-MODULE-PATH +err.limit.modules=--limit-modules not allowed with --add-modules ALL-MODULE-PATH err.jlink.version.mismatch=jlink version {0}.{1} does not match target java.base version {2}.{3} err.automatic.module:automatic module cannot be used with jlink: {0} from {1} err.unknown.byte.order:unknown byte order {0} diff --git a/test/jdk/tools/jlink/IntegrationTest.java b/test/jdk/tools/jlink/IntegrationTest.java index 7f3dc22346106..5a8d0bce15ad7 100644 --- a/test/jdk/tools/jlink/IntegrationTest.java +++ b/test/jdk/tools/jlink/IntegrationTest.java @@ -157,7 +157,7 @@ private static void test() throws Exception { boolean linkFromRuntime = false; JlinkConfiguration config = new Jlink.JlinkConfiguration(output, mods, - JlinkTask.newLimitedFinder(JlinkTask.newModuleFinder(modulePaths), limits, mods), + JlinkTask.limitFinder(JlinkTask.newModuleFinder(modulePaths), limits, mods), linkFromRuntime, false /* ignore modified runtime */, false /* generate run-time image */); diff --git a/test/jdk/tools/jlink/JLinkTest.java b/test/jdk/tools/jlink/JLinkTest.java index c0fabe06a8c57..0b7de201ac9de 100644 --- a/test/jdk/tools/jlink/JLinkTest.java +++ b/test/jdk/tools/jlink/JLinkTest.java @@ -27,17 +27,15 @@ import java.lang.module.ModuleDescriptor; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.spi.ToolProvider; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; -import jdk.tools.jlink.plugin.Plugin; import jdk.tools.jlink.internal.PluginRepository; +import jdk.tools.jlink.plugin.Plugin; import tests.Helper; import tests.JImageGenerator; @@ -135,11 +133,11 @@ public static void main(String[] args) throws Exception { { // No --module-path specified. --add-modules ALL-MODULE-PATH specified. - String imageDir = "bug8189777-all-module-path"; + String imageDir = "bug8345259-all-module-path"; JImageGenerator.getJLinkTask() .output(helper.createNewImageDir(imageDir)) .addMods("ALL-MODULE-PATH") - .call().assertSuccess(); + .call().assertFailure(); } { diff --git a/test/jdk/tools/jlink/basic/AllModulePath.java b/test/jdk/tools/jlink/basic/AllModulePath.java index ba6cc08bd4757..a05f2d06d8658 100644 --- a/test/jdk/tools/jlink/basic/AllModulePath.java +++ b/test/jdk/tools/jlink/basic/AllModulePath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, 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 @@ -21,45 +21,52 @@ * questions. */ -/* - * @test - * @summary jlink test of --add-module ALL-MODULE-PATH - * @library /test/lib - * @modules jdk.compiler - * @build jdk.test.lib.process.ProcessTools - * jdk.test.lib.process.OutputAnalyzer - * jdk.test.lib.compiler.CompilerUtils - * @run testng AllModulePath - */ +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; -import java.io.File; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import java.util.spi.ToolProvider; - -import jdk.test.lib.compiler.CompilerUtils; -import jdk.test.lib.process.ProcessTools; +import java.util.stream.Collectors; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import static org.testng.Assert.*; +import jdk.test.lib.compiler.CompilerUtils; +import jdk.test.lib.process.ProcessTools; +import jdk.tools.jlink.internal.LinkableRuntimeImage; +import tests.Helper; +import tests.Result; + +/* + * @test + * @bug 8345259 + * @summary jlink test of --add-module ALL-MODULE-PATH + * @library ../../lib /test/lib + * @modules jdk.compiler + * java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jimage + * @build jdk.test.lib.process.ProcessTools + * jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.compiler.CompilerUtils + * @run testng/othervm -Duser.language=en -Duser.country=US AllModulePath + */ public class AllModulePath { - private final Path JMODS = Paths.get(System.getProperty("test.jdk")).resolve("jmods"); - private final Path SRC = Paths.get(System.getProperty("test.src")).resolve("src"); - private final Path MODS = Paths.get("mods"); + private static final Path JMODS = Paths.get(System.getProperty("test.jdk")).resolve("jmods"); + private static final Path SRC = Paths.get(System.getProperty("test.src")).resolve("src"); + private static final Path MODS = Paths.get("mods"); + private static final boolean LINKABLE_RUNTIME = LinkableRuntimeImage.isLinkableRuntime(); + private static final boolean JMODS_EXIST = Files.exists(JMODS); private final static Set<String> MODULES = Set.of("test", "m1"); @@ -67,12 +74,22 @@ public class AllModulePath { .orElseThrow(() -> new RuntimeException("jlink tool not found") ); + private static Helper HELPER; + + private static boolean isExplodedJDKImage() { + if (!JMODS_EXIST && !LINKABLE_RUNTIME) { + System.err.println("Test skipped. Not a linkable runtime and no JMODs"); + return true; + } + return false; + } @BeforeClass public void setup() throws Throwable { - if (Files.notExists(JMODS)) { + if (isExplodedJDKImage()) { return; } + HELPER = Helper.newHelper(LINKABLE_RUNTIME); Files.createDirectories(MODS); @@ -84,60 +101,114 @@ public void setup() throws Throwable { } } + /* + * --add-modules ALL-MODULE-PATH with an existing module-path. + */ @Test public void testAllModulePath() throws Throwable { - if (Files.notExists(JMODS)) { + if (isExplodedJDKImage()) { return; } - // create custom image - Path image = Paths.get("image"); - createImage(image, "--add-modules", "ALL-MODULE-PATH"); + Path image = HELPER.createNewImageDir("image"); + List<String> opts = List.of("--module-path", MODS.toString(), + "--output", image.toString(), + "--add-modules", "ALL-MODULE-PATH"); + createImage(image, opts, true /* success */); Set<String> modules = new HashSet<>(); - Files.find(JMODS, 1, (Path p, BasicFileAttributes attr) -> - p.toString().endsWith(".jmod")) - .map(p -> JMODS.relativize(p).toString()) - .map(n -> n.substring(0, n.length()-5)) - .forEach(modules::add); + // java.base is a dependency of any external module + modules.add("java.base"); modules.add("m1"); modules.add("test"); checkModules(image, modules); } + /* + * --add-modules ALL-MODULE-PATH with --limit-modules is an error + */ @Test public void testLimitModules() throws Throwable { - if (Files.notExists(JMODS)) { + if (isExplodedJDKImage()) { return; } - - // create custom image - Path image = Paths.get("image1"); - createImage(image, - "--add-modules", "ALL-MODULE-PATH", - "--limit-modules", "m1"); - - checkModules(image, Set.of("m1", "java.base")); + Path targetPath = HELPER.createNewImageDir("all-mods-limit-mods"); + String moduleName = "com.baz.runtime"; + Result result = HELPER.generateDefaultJModule(moduleName, "jdk.jfr"); + Path customModulePath = result.getFile().getParent(); + List<String> allArgs = List.of("--add-modules", "ALL-MODULE-PATH", + "--limit-modules", "jdk.jfr", + "--module-path", customModulePath.toString(), + "--output", targetPath.toString()); + JlinkOutput allOut = createImage(targetPath, allArgs, false /* success */); + String actual = allOut.stdout.trim(); + String expected = "Error: --limit-modules not allowed with --add-modules ALL-MODULE-PATH"; + assertEquals(actual, expected); } + + /* + * --add-modules *includes* ALL-MODULE-PATH with an existing module path + */ @Test public void testAddModules() throws Throwable { - if (Files.notExists(JMODS)) { + if (isExplodedJDKImage()) { return; } // create custom image - Path image = Paths.get("image2"); - createImage(image, - "--add-modules", "m1,test", - "--add-modules", "ALL-MODULE-PATH", - "--limit-modules", "java.base"); + Path image = HELPER.createNewImageDir("image2"); + List<String> opts = List.of("--module-path", MODS.toString(), + "--output", image.toString(), + "--add-modules", "m1", + "--add-modules", "ALL-MODULE-PATH"); + createImage(image, opts, true /* success */); checkModules(image, Set.of("m1", "test", "java.base")); } /* - * check the modules linked in the image + * No --module-path with --add-modules ALL-MODULE-PATH is an error. + */ + @Test + public void noModulePath() throws IOException { + if (isExplodedJDKImage()) { + return; + } + Path targetPath = HELPER.createNewImageDir("all-mod-path-no-mod-path"); + List<String> allArgs = List.of("--add-modules", "ALL-MODULE-PATH", + "--output", targetPath.toString()); + JlinkOutput allOut = createImage(targetPath, allArgs, false /* expect failure */); + String expected = "Error: --module-path option must be specified with --add-modules ALL-MODULE-PATH"; + assertEquals(allOut.stdout.trim(), expected); + } + + /* + * --module-path not-exist and --add-modules ALL-MODULE-PATH is an error. + */ + @Test + public void modulePathEmpty() throws IOException { + if (isExplodedJDKImage()) { + return; + } + Path targetPath = HELPER.createNewImageDir("all-mod-path-not-existing"); + String strNotExists = "not-exist"; + Path notExists = Path.of(strNotExists); + if (Files.exists(notExists)) { + throw new AssertionError("Test setup error, path must not exist!"); + } + List<String> allArgs = List.of("--add-modules", "ALL-MODULE-PATH", + "--module-path", notExists.toString(), + "--output", targetPath.toString()); + + JlinkOutput allOut = createImage(targetPath, allArgs, false /* expect failure */); + String actual = allOut.stdout.trim(); + assertTrue(actual.startsWith("Error: No module found in module path")); + assertTrue(actual.contains(strNotExists)); + } + + /* + * check the modules linked in the image using m1/p.ListModules */ private void checkModules(Path image, Set<String> modules) throws Throwable { Path cmd = findTool(image, "java"); @@ -164,16 +235,19 @@ private Path findTool(Path image, String tool) { return cmd; } - private void createImage(Path image, String... options) throws IOException { - String modulepath = JMODS.toString() + File.pathSeparator + MODS.toString(); - List<String> opts = List.of("--module-path", modulepath, - "--output", image.toString()); - String[] args = Stream.concat(opts.stream(), Arrays.stream(options)) - .toArray(String[]::new); - - System.out.println("jlink " + Arrays.stream(args).collect(Collectors.joining(" "))); - PrintWriter pw = new PrintWriter(System.out); - int rc = JLINK_TOOL.run(pw, pw, args); - assertTrue(rc == 0); + private JlinkOutput createImage(Path image, List<String> args, boolean success) throws IOException { + System.out.println("jlink " + args.stream().collect(Collectors.joining(" "))); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter out = new PrintWriter(baos); + ByteArrayOutputStream berrOs = new ByteArrayOutputStream(); + PrintWriter err = new PrintWriter(berrOs); + int rc = JLINK_TOOL.run(out, err, args.toArray(String[]::new)); + String stdOut = new String(baos.toByteArray()); + String stdErr = new String(berrOs.toByteArray()); + assertEquals(rc == 0, success, String.format("Output was: %nstdout: %s%nstderr: %s%n", stdOut, stdErr)); + return new JlinkOutput(stdErr, stdOut); } + + private static record JlinkOutput(String stderr, String stdout) {}; } diff --git a/test/jdk/tools/jlink/basic/BasicTest.java b/test/jdk/tools/jlink/basic/BasicTest.java index a771d4d000250..1a14e620fa626 100644 --- a/test/jdk/tools/jlink/basic/BasicTest.java +++ b/test/jdk/tools/jlink/basic/BasicTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, 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 @@ -21,21 +21,6 @@ * questions. */ -/* - * @test - * @summary Basic test of jlink to create jmods and images - * @author Andrei Eremeev - * @library /test/lib - * @modules java.base/jdk.internal.module - * jdk.jlink - * jdk.compiler - * @build jdk.test.lib.process.ProcessTools - * jdk.test.lib.process.OutputAnalyzer - * jdk.test.lib.compiler.CompilerUtils - * jdk.test.lib.util.JarUtils - * @run main BasicTest - */ - import java.io.File; import java.io.PrintWriter; import java.nio.file.Files; @@ -50,7 +35,22 @@ import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.JarUtils; +import jdk.tools.jlink.internal.LinkableRuntimeImage; +/* + * @test + * @summary Basic test of jlink to create jmods and images + * @author Andrei Eremeev + * @library /test/lib + * @modules java.base/jdk.internal.module + * jdk.jlink/jdk.tools.jlink.internal + * jdk.compiler + * @build jdk.test.lib.process.ProcessTools + * jdk.test.lib.process.OutputAnalyzer + * jdk.test.lib.compiler.CompilerUtils + * jdk.test.lib.util.JarUtils + * @run main/othervm BasicTest + */ public class BasicTest { static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod") .orElseThrow(() -> @@ -62,21 +62,31 @@ public class BasicTest { new RuntimeException("jlink tool not found") ); - private final String TEST_MODULE = "test"; - private final Path jdkHome = Paths.get(System.getProperty("test.jdk")); - private final Path jdkMods = jdkHome.resolve("jmods"); - private final Path testSrc = Paths.get(System.getProperty("test.src")); - private final Path src = testSrc.resolve("src").resolve(TEST_MODULE); - private final Path classes = Paths.get("classes"); - private final Path jmods = Paths.get("jmods"); - private final Path jars = Paths.get("jars"); + private static final String TEST_MODULE = "test"; + private static final Path jdkHome = Paths.get(System.getProperty("test.jdk")); + private static final Path jdkMods = jdkHome.resolve("jmods"); + private static final boolean JMODS_EXIST = Files.exists(jdkMods); + private static final boolean LINKABLE_RUNTIME = LinkableRuntimeImage.isLinkableRuntime(); + private static final Path testSrc = Paths.get(System.getProperty("test.src")); + private static final Path src = testSrc.resolve("src").resolve(TEST_MODULE); + private static final Path classes = Paths.get("classes"); + private static final Path jmods = Paths.get("jmods"); + private static final Path jars = Paths.get("jars"); public static void main(String[] args) throws Throwable { new BasicTest().run(); } + private static boolean isExplodedJDKImage() { + if (!JMODS_EXIST && !LINKABLE_RUNTIME) { + System.err.println("Test skipped. Not a linkable runtime and no JMODs"); + return true; + } + return false; + } + public void run() throws Throwable { - if (Files.notExists(jdkMods)) { + if (isExplodedJDKImage()) { return; } @@ -146,8 +156,10 @@ private void execute(Path image, String scriptName) throws Throwable { private void runJlink(Path image, String modName, String... options) { List<String> args = new ArrayList<>(); + String modPathArg = (JMODS_EXIST ? jdkMods + File.pathSeparator : "") + + jmods; Collections.addAll(args, - "--module-path", jdkMods + File.pathSeparator + jmods, + "--module-path", modPathArg, "--add-modules", modName, "--output", image.toString()); Collections.addAll(args, options);