# HG changeset patch # User sdrach # Date 1478036186 25200 # Node ID 0436ea0c6099ce8151030c841c1c9e3ffa1d74a7 # Parent 710feffff038ca334f8f8aed41048e0dad075a29 8156499: Update jlink to support creating images with modules that are packaged as multi-release JARs Reviewed-by: alanb, mchung diff -r 710feffff038 -r 0436ea0c6099 jdk/src/java.base/share/classes/module-info.java --- a/jdk/src/java.base/share/classes/module-info.java Tue Nov 01 12:37:29 2016 +0000 +++ b/jdk/src/java.base/share/classes/module-info.java Tue Nov 01 14:36:26 2016 -0700 @@ -196,7 +196,8 @@ jdk.vm.ci; exports jdk.internal.util.jar to jdk.jartool, - jdk.jdeps; + jdk.jdeps, + jdk.jlink; exports jdk.internal.vm to java.management, jdk.jvmstat; diff -r 710feffff038 -r 0436ea0c6099 jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java Tue Nov 01 12:37:29 2016 +0000 +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JarArchive.java Tue Nov 01 14:36:26 2016 -0700 @@ -30,9 +30,11 @@ import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.Objects; +import java.util.jar.JarFile; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import jdk.internal.util.jar.VersionedStream; import jdk.tools.jlink.internal.Archive.Entry.EntryType; /** @@ -72,8 +74,8 @@ private final Path file; private final String moduleName; - // currently processed ZipFile - protected ZipFile zipFile; + // currently processed JarFile + private JarFile jarFile; protected JarArchive(String mn, Path file) { Objects.requireNonNull(mn); @@ -95,13 +97,15 @@ @Override public Stream entries() { try { - if (zipFile == null) { + if (jarFile == null) { open(); } } catch (IOException ioe) { throw new UncheckedIOException(ioe); } - return zipFile.stream().map(this::toEntry).filter(n -> n != null); + return VersionedStream.stream(jarFile) + .filter(je -> !je.isDirectory()) + .map(this::toEntry); } abstract EntryType toEntryType(String entryName); @@ -112,16 +116,20 @@ @Override public void close() throws IOException { - if (zipFile != null) { - zipFile.close(); + if (jarFile != null) { + jarFile.close(); } } @Override public void open() throws IOException { - if (zipFile != null) { - zipFile.close(); + if (jarFile != null) { + jarFile.close(); } - zipFile = new ZipFile(file.toFile()); + jarFile = new JarFile(file.toFile(), true, ZipFile.OPEN_READ, JarFile.runtimeVersion()); + } + + protected JarFile getJarFile() { + return jarFile; } } diff -r 710feffff038 -r 0436ea0c6099 jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModularJarArchive.java --- a/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModularJarArchive.java Tue Nov 01 12:37:29 2016 +0000 +++ b/jdk/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ModularJarArchive.java Tue Nov 01 14:36:26 2016 -0700 @@ -54,13 +54,9 @@ @Override Entry toEntry(ZipEntry ze) { - if (ze.isDirectory()) { - return null; - } - String name = ze.getName(); EntryType type = toEntryType(name); - return new JarEntry(ze.getName(), getFileName(name), type, zipFile, ze); + return new JarEntry(ze.getName(), getFileName(name), type, getJarFile(), ze); } @Override diff -r 710feffff038 -r 0436ea0c6099 jdk/test/tools/jlink/multireleasejar/JLinkMultiReleaseJarTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/tools/jlink/multireleasejar/JLinkMultiReleaseJarTest.java Tue Nov 01 14:36:26 2016 -0700 @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2016, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8156499 + * @summary Test image creation from Multi-Release JAR + * @author Steve Drach + * @library /lib/testlibrary /test/lib + * @modules java.base/jdk.internal.jimage + * java.base/jdk.internal.module + * @build jdk.testlibrary.FileUtils jdk.test.lib.process.* + * @run testng JLinkMultiReleaseJarTest +*/ + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.module.ModuleDescriptor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.spi.ToolProvider; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import jdk.internal.jimage.BasicImageReader; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.testlibrary.FileUtils; + +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class JLinkMultiReleaseJarTest { + private static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar") + .orElseThrow(() -> new RuntimeException("jar tool not found")); + private static final ToolProvider JAVAC_TOOL = ToolProvider.findFirst("javac") + .orElseThrow(() -> new RuntimeException("javac tool not found")); + private static final ToolProvider JLINK_TOOL = ToolProvider.findFirst("jlink") + .orElseThrow(() -> new RuntimeException("jlink tool not found")); + + private final Path userdir = Paths.get(System.getProperty("user.dir", ".")); + private final Path javahome = Paths.get(System.getProperty("java.home")); + private final Path jmodsdir = javahome.resolve("jmods"); + + private final String pathsep = System.getProperty("path.separator"); + + private byte[] resource = (Runtime.version().major() + " resource file").getBytes(); + + @BeforeClass + public void initialize() throws IOException { + Path srcdir = Paths.get(System.getProperty("test.src")); + + // create class files from source + Path base = srcdir.resolve("base"); + Path basemods = userdir.resolve("basemods"); + javac(base, basemods, base.toString()); + + Path rt = srcdir.resolve("rt"); + Path rtmods = userdir.resolve("rtmods"); + javac(rt, rtmods, rt.toString()); + + // create resources in basemods and rtmods + Path dest = basemods.resolve("m1").resolve("resource.txt"); + byte[] text = "base resource file".getBytes(); + ByteArrayInputStream is = new ByteArrayInputStream(text); + Files.copy(is, dest); + + dest = rtmods.resolve("m1").resolve("resource.txt"); + is = new ByteArrayInputStream(resource); + Files.copy(is, dest); + + // build multi-release jar file with different module-infos + String[] args = { + "-cf", "m1.jar", + "-C", basemods.resolve("m1").toString(), ".", + "--release ", String.valueOf(JarFile.runtimeVersion().major()), + "-C", rtmods.resolve("m1").toString(), "." + }; + JAR_TOOL.run(System.out, System.err, args); + + // now move the module-info that requires logging to temporary place + Files.move(rtmods.resolve("m1").resolve("module-info.class"), + userdir.resolve("module-info.class")); + + // and build another jar + args[1] = "m1-no-logging.jar"; + JAR_TOOL.run(System.out, System.err, args); + + // replace the no logging module-info with the logging module-info + Files.move(userdir.resolve("module-info.class"), + basemods.resolve("m1").resolve("module-info.class"), + StandardCopyOption.REPLACE_EXISTING); + + // and build another jar + args[1] = "m1-logging.jar"; + JAR_TOOL.run(System.out, System.err, args); + } + + private void javac(Path source, Path destination, String srcpath) throws IOException { + String[] args = Stream.concat( + Stream.of("-d", destination.toString(), "--module-source-path", srcpath), + Files.walk(source) + .map(Path::toString) + .filter(s -> s.endsWith(".java")) + ).toArray(String[]::new); + int rc = JAVAC_TOOL.run(System.out, System.err, args); + Assert.assertEquals(rc, 0); + } + + @AfterClass + public void close() throws IOException { + Files.walk(userdir, 1) + .filter(p -> !p.equals(userdir)) + .forEach(p -> { + try { + if (Files.isDirectory(p)) { + FileUtils.deleteFileTreeWithRetry(p); + } else { + FileUtils.deleteFileIfExistsWithRetry(p); + } + } catch (IOException x) { + throw new UncheckedIOException(x); + } + }); + } + + @Test + public void basicTest() throws Throwable { + if (ignoreTest()) return; + + // use jlink to build image from multi-release jar + jlink("m1.jar", "myimage"); + + // validate image + Path jimage = userdir.resolve("myimage").resolve("lib").resolve("modules"); + try (BasicImageReader reader = BasicImageReader.open(jimage)) { + + // do we have the right entry names? + Set names = Arrays.stream(reader.getEntryNames()) + .filter(n -> n.startsWith("/m1")) + .collect(Collectors.toSet()); + Assert.assertEquals(names, Set.of( + "/m1/module-info.class", + "/m1/p/Main.class", + "/m1/p/Type.class", + "/m1/q/PublicClass.class", + "/m1/META-INF/MANIFEST.MF", + "/m1/resource.txt")); + + // do we have the right module-info.class? + byte[] b = reader.getResource("/m1/module-info.class"); + Set requires = ModuleDescriptor + .read(new ByteArrayInputStream(b)) + .requires() + .stream() + .map(mdr -> mdr.name()) + .filter(nm -> !nm.equals("java.base")) + .collect(Collectors.toSet()); + Assert.assertEquals(requires, Set.of("java.logging")); + + // do we have the right resource? + b = reader.getResource("/m1/resource.txt"); + Assert.assertEquals(b, resource); + + // do we have the right class? + b = reader.getResource("/m1/p/Main.class"); + Class clazz = (new ByteArrayClassLoader()).loadClass("p.Main", b); + MethodHandle getVersion = MethodHandles.lookup() + .findVirtual(clazz, "getVersion", MethodType.methodType(int.class)); + int version = (int) getVersion.invoke(clazz.getConstructor().newInstance()); + Assert.assertEquals(version, JarFile.runtimeVersion().major()); + } + } + + @Test + public void noLoggingTest() throws Throwable { + if (ignoreTest()) return; + + jlink("m1-no-logging.jar", "no-logging-image"); + runImage("no-logging-image", false); + } + + @Test + public void loggingTest() throws Throwable { + if (ignoreTest()) return; + + jlink("m1-logging.jar", "logging-image"); + runImage("logging-image", true); + + } + + // java.base.jmod must exist for this test to make sense + private boolean ignoreTest() { + if (Files.isRegularFile(jmodsdir.resolve("java.base.jmod"))) { + return false; + } + System.err.println("Test skipped. NO jmods/java.base.jmod"); + return true; + } + + + private void jlink(String jar, String image) { + String args = "--output " + image + " --add-modules m1 --module-path " + + jar + pathsep + jmodsdir.toString(); + int exitCode = JLINK_TOOL.run(System.out, System.err, args.split(" +")); + Assert.assertEquals(exitCode, 0); + } + + public void runImage(String image, boolean expected) throws Throwable { + Path java = Paths.get(image, "bin", "java"); + OutputAnalyzer oa = ProcessTools.executeProcess(java.toString(), "-m", "m1/p.Main"); + String sout = oa.getStdout(); + boolean actual = sout.contains("logging found"); + Assert.assertEquals(actual, expected); + System.out.println(sout); + System.err.println(oa.getStderr()); + Assert.assertEquals(oa.getExitValue(), 0); + } + + private static class ByteArrayClassLoader extends ClassLoader { + public Class loadClass(String name, byte[] bytes) { + return defineClass(name, bytes, 0, bytes.length); + } + } +} diff -r 710feffff038 -r 0436ea0c6099 jdk/test/tools/jlink/multireleasejar/base/m1/module-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/tools/jlink/multireleasejar/base/m1/module-info.java Tue Nov 01 14:36:26 2016 -0700 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2016, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +module m1 { + exports p; +} diff -r 710feffff038 -r 0436ea0c6099 jdk/test/tools/jlink/multireleasejar/base/m1/p/Main.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/tools/jlink/multireleasejar/base/m1/p/Main.java Tue Nov 01 14:36:26 2016 -0700 @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package p; + +import java.lang.module.ModuleFinder; + +public class Main { + private String msg = "something to give this a different size"; + + public int getVersion() { + return 8; + } + + private void testForLogging() { + ModuleFinder.ofSystem().find("java.logging").ifPresentOrElse( + mr -> System.out.println("java.logging found in image"), + () -> System.out.println("java.logging not found in image") + ); + } + + public static void main(String[] args) { + Main main = new Main(); + main.testForLogging(); + } +} diff -r 710feffff038 -r 0436ea0c6099 jdk/test/tools/jlink/multireleasejar/rt/m1/module-info.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/tools/jlink/multireleasejar/rt/m1/module-info.java Tue Nov 01 14:36:26 2016 -0700 @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +module m1 { + requires java.logging; + exports p; +} diff -r 710feffff038 -r 0436ea0c6099 jdk/test/tools/jlink/multireleasejar/rt/m1/p/Main.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/tools/jlink/multireleasejar/rt/m1/p/Main.java Tue Nov 01 14:36:26 2016 -0700 @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package p; + +import java.lang.module.ModuleFinder; +import java.util.jar.JarFile; + +public class Main { + public int getVersion() { + return JarFile.runtimeVersion().major(); + } + + private void testForLogging() { + ModuleFinder.ofSystem().find("java.logging").ifPresentOrElse( + mr -> System.out.println("java.logging found in image"), + () -> System.out.println("java.logging not found in image") + ); + } + + public static void main(String[] args) { + Main main = new Main(); + main.testForLogging(); + } +} diff -r 710feffff038 -r 0436ea0c6099 jdk/test/tools/jlink/multireleasejar/rt/m1/p/Type.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/tools/jlink/multireleasejar/rt/m1/p/Type.java Tue Nov 01 14:36:26 2016 -0700 @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2016, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package p; + +class Type{} diff -r 710feffff038 -r 0436ea0c6099 jdk/test/tools/jlink/multireleasejar/rt/m1/q/PublicClass.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/tools/jlink/multireleasejar/rt/m1/q/PublicClass.java Tue Nov 01 14:36:26 2016 -0700 @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package q; + +public class PublicClass { + public void doNothing() { + int i = 3 + 2; + } +}