8067445: New modular image-based file manager skips boot classes
authorjlahoda
Tue, 17 Feb 2015 15:39:05 +0100
changeset 29053 5c1f1d6b40f6
parent 29052 d1bc1685d7ce
child 29054 310b8028d7df
8067445: New modular image-based file manager skips boot classes Summary: Taking "sun.boot.class.path" system property into account when constructing bootclasspath for modular images. Reviewed-by: jjg, mcimadamore
langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java
langtools/test/tools/javac/file/BootClassPathPrepend.java
langtools/test/tools/javac/file/ExplodedImage.java
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java	Mon Feb 16 19:14:18 2015 +0530
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/file/Locations.java	Tue Feb 17 15:39:05 2015 +0100
@@ -678,29 +678,43 @@
                     boolean haveJImageFiles =
                             files.anyMatch(f -> f.getFileName().toString().endsWith(".jimage"));
                     if (haveJImageFiles) {
-                        return Collections.singleton(JRT_MARKER_FILE);
+                        return addAdditionalBootEntries(Collections.singleton(JRT_MARKER_FILE));
                     }
                 }
             }
 
-            // Temporary: if no .jimage files, return individual modules
-            if (Files.exists(libModules.resolve("java.base"))) {
-                return Files.list(libModules)
-                            .map(d -> d.resolve("classes"))
-                            .collect(Collectors.toList());
-            }
-
             // Exploded module image
             Path modules = Paths.get(java_home, "modules");
             if (Files.isDirectory(modules.resolve("java.base"))) {
-                return Files.list(modules)
-                            .collect(Collectors.toList());
+                try (Stream<Path> listedModules = Files.list(modules)) {
+                    return addAdditionalBootEntries(listedModules.collect(Collectors.toList()));
+                }
             }
 
             // not a modular image that we know about
             return null;
         }
 
+        //ensure bootclasspath prepends/appends are reflected in the systemClasses
+        private Collection<Path> addAdditionalBootEntries(Collection<Path> modules) throws IOException {
+            String files = System.getProperty("sun.boot.class.path");
+
+            if (files == null)
+                return modules;
+
+            Set<Path> paths = new LinkedHashSet<>();
+
+            for (String s : files.split(Pattern.quote(File.pathSeparator))) {
+                if (s.endsWith(".jimage")) {
+                    paths.addAll(modules);
+                } else if (!s.isEmpty()) {
+                    paths.add(Paths.get(s));
+                }
+            }
+
+            return paths;
+        }
+
         private void lazy() {
             if (searchPath == null) {
                 try {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/file/BootClassPathPrepend.java	Tue Feb 17 15:39:05 2015 +0100
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2014, 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 8067445
+ * @summary Verify that file.Locations analyze sun.boot.class.path for BCP prepends/appends
+ * @library /tools/lib
+ */
+
+import java.io.IOException;
+import java.util.EnumSet;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+public class BootClassPathPrepend {
+    public static void main(String... args) throws IOException {
+        if (args.length == 0) {
+            new BootClassPathPrepend().reRun();
+        } else {
+            new BootClassPathPrepend().run();
+        }
+    }
+
+    void reRun() {
+        String testClasses = System.getProperty("test.classes");
+        ToolBox tb = new ToolBox();
+        tb.new JavaTask().vmOptions("-Xbootclasspath/p:" + testClasses)
+                         .classArgs("real-run")
+                         .className("BootClassPathPrepend")
+                         .run()
+                         .writeAll();
+    }
+
+    EnumSet<Kind> classKind = EnumSet.of(JavaFileObject.Kind.CLASS);
+
+    void run() throws IOException {
+        JavaCompiler toolProvider = ToolProvider.getSystemJavaCompiler();
+        try (JavaFileManager fm = toolProvider.getStandardFileManager(null, null, null)) {
+            Iterable<JavaFileObject> files =
+                    fm.list(StandardLocation.PLATFORM_CLASS_PATH, "", classKind, false);
+            for (JavaFileObject fo : files) {
+                if (fo.isNameCompatible("BootClassPathPrepend", JavaFileObject.Kind.CLASS)) {
+                    System.err.println("Found BootClassPathPrepend on bootclasspath");
+                    return ;//found
+                }
+            }
+
+            throw new AssertionError("Cannot find class that was prepended on BCP");
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/file/ExplodedImage.java	Tue Feb 17 15:39:05 2015 +0100
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+import com.sun.source.util.JavacTask;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticListener;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+/**
+ * @test
+ * @bug 8067138
+ * @summary Verify that compiling against the exploded JDK image works, and that Locations close
+ *          the directory streams properly when working with exploded JDK image.
+ * @library /tools/lib
+ * @build ToolBox ExplodedImage
+ * @run main ExplodedImage
+ */
+
+public class ExplodedImage {
+    public static void main(String... args) throws IOException {
+        new ExplodedImage().run();
+    }
+
+    void run() throws IOException {
+        for (String moduleLocations : new String[] {"modules/*"}) {
+            System.setProperty("java.home", originalJavaHome);
+            testDirectoryStreamClosed(moduleLocations);
+            System.setProperty("java.home", originalJavaHome);
+            testCanCompileAgainstExplodedImage(moduleLocations);
+        }
+    }
+
+    void testDirectoryStreamClosed(String loc) throws IOException {
+        System.err.println("testDirectoryStreamClosed(" + loc + ")");
+        Path javaHome = prepareJavaHome();
+        Path targetPath = javaHome.resolve(loc.replace("*", "/java.base").replace("/", sep));
+        Path testClass = targetPath.resolve(("java/lang/" + TEST_FILE).replace("/", sep));
+        Files.createDirectories(testClass.getParent());
+        Files.createFile(testClass);
+        System.setProperty("java.home", javaHome.toString());
+
+        for (int i = 0; i < REPEATS; i++) {
+            try (StandardJavaFileManager fm = javaCompiler.getStandardFileManager(null, null, null)) {
+                Iterable<JavaFileObject> javaLangContent =
+                        fm.list(StandardLocation.PLATFORM_CLASS_PATH,
+                                "java.lang",
+                                EnumSet.allOf(JavaFileObject.Kind.class),
+                                false);
+                boolean found = false;
+
+                for (JavaFileObject fo : javaLangContent) {
+                    if (!fo.getName().endsWith(TEST_FILE)) {
+                        throw new IllegalStateException("Wrong file: " + fo);
+                    }
+                    found = true;
+                }
+
+                if (!found)
+                    throw new IllegalStateException("Could not find the expected file!");
+            }
+        }
+
+        System.err.println("finished.");
+    }
+    //where:
+        static final String TEST_FILE = "ExplodedImageTestFile.class";
+        static final int REPEATS = 16 * 1024 + 1;
+
+    void testCanCompileAgainstExplodedImage(String loc) throws IOException {
+        System.err.println("testCanCompileAgainstExplodedImage(" + loc + ")");
+        Path javaHome = prepareJavaHome();
+        Path targetPath = javaHome.resolve(loc.replace("*", "/java.base").replace("/", sep));
+        try (StandardJavaFileManager fm = javaCompiler.getStandardFileManager(null, null, null)) {
+            for (String pack : REQUIRED_PACKAGES) {
+                Iterable<JavaFileObject> content = fm.list(StandardLocation.PLATFORM_CLASS_PATH,
+                                                           pack,
+                                                           EnumSet.allOf(JavaFileObject.Kind.class),
+                                                           false);
+
+                for (JavaFileObject jfo : content) {
+                    String name = jfo.getName();
+                    int lastSlash = name.lastIndexOf('/');
+                    name = lastSlash >= 0 ? name.substring(lastSlash + 1) : name;
+                    Path target = targetPath.resolve(pack.replace(".", sep) + sep + name);
+                    Files.createDirectories(target.getParent());
+                    try (InputStream in = jfo.openInputStream()) {
+                        Files.copy(in, target);
+                    }
+                }
+            }
+        }
+
+        System.setProperty("java.home", javaHome.toString());
+
+        try (StandardJavaFileManager fm = javaCompiler.getStandardFileManager(null, null, null)) {
+            DiagnosticListener<JavaFileObject> noErrors = d -> {
+                if (d.getKind() == Diagnostic.Kind.ERROR)
+                    throw new IllegalStateException("Unexpected error: " + d);
+            };
+            ToolBox.JavaSource inputFile =
+                    new ToolBox.JavaSource("import java.util.List; class Test { List l; }");
+            List<JavaFileObject> inputFiles = Arrays.asList(inputFile);
+            boolean result =
+                    javaCompiler.getTask(null, fm, noErrors, null, null, inputFiles).call();
+            if (!result) {
+                throw new IllegalStateException("Could not compile correctly!");
+            }
+            JavacTask task =
+                    (JavacTask) javaCompiler.getTask(null, fm, noErrors, null, null, inputFiles);
+            task.parse();
+            TypeElement juList = task.getElements().getTypeElement("java.util.List");
+            if (juList == null)
+                throw new IllegalStateException("Cannot resolve java.util.List!");
+            URI listSource = ((ClassSymbol) juList).classfile.toUri();
+            if (!listSource.toString().startsWith(javaHome.toUri().toString()))
+                throw new IllegalStateException(  "Did not load java.util.List from correct place, " +
+                                                  "actual location: " + listSource.toString() +
+                                                "; expected prefix: " + javaHome.toUri());
+        }
+
+        System.err.println("finished.");
+    }
+    //where:
+        static final String[] REQUIRED_PACKAGES = {"java.lang", "java.io", "java.util"};
+
+    Path prepareJavaHome() throws IOException {
+        Path javaHome = new File("javahome").getAbsoluteFile().toPath();
+        delete(javaHome);
+        Files.createDirectory(javaHome);
+        return javaHome;
+    }
+
+    String sep = FileSystems.getDefault().getSeparator();
+    JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
+    String originalJavaHome = System.getProperty("java.home");
+
+    void delete(Path p) throws IOException {
+        if (!Files.exists(p))
+            return ;
+        if (Files.isDirectory(p)) {
+            try (DirectoryStream<Path> dir = Files.newDirectoryStream(p)) {
+                for (Path child : dir) {
+                    delete(child);
+                }
+            }
+        }
+        Files.delete(p);
+    }
+}