| // Copyright 2017 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import static java.util.Comparator.comparing; |
| |
| import com.sun.tools.javac.api.JavacTool; |
| import com.sun.tools.javac.util.Context; |
| import java.io.BufferedOutputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.UncheckedIOException; |
| import java.net.URI; |
| import java.nio.file.FileSystems; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.SimpleFileVisitor; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.util.Arrays; |
| import java.util.EnumSet; |
| import java.util.GregorianCalendar; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarFile; |
| import java.util.jar.JarOutputStream; |
| import java.util.zip.CRC32; |
| import java.util.zip.ZipEntry; |
| import javax.tools.JavaFileManager; |
| import javax.tools.JavaFileObject; |
| import javax.tools.JavaFileObject.Kind; |
| import javax.tools.StandardLocation; |
| |
| /** |
| * Output a jar file containing all classes on the platform classpath of the given JDK release. |
| * |
| * <p>usage: DumpPlatformClassPath <release version> <output jar> |
| */ |
| public class DumpPlatformClassPath { |
| |
| public static void main(String[] args) throws Exception { |
| if (args.length != 2) { |
| System.err.println("usage: DumpPlatformClassPath <release version> <output jar>"); |
| System.exit(1); |
| } |
| int release = Integer.parseInt(args[0]); |
| Path output = Paths.get(args[1]); |
| |
| Map<String, byte[]> entries = new HashMap<>(); |
| |
| // Legacy JDK 8 bootclasspath handling. |
| // TODO(cushon): make sure this has test coverage. |
| Path javaHome = Paths.get(System.getProperty("java.home")); |
| if (javaHome.endsWith("jre")) { |
| javaHome = javaHome.getParent(); |
| } |
| for (String jar : |
| Arrays.asList("rt.jar", "resources.jar", "jsse.jar", "jce.jar", "charsets.jar")) { |
| Path path = javaHome.resolve("jre/lib").resolve(jar); |
| if (!Files.exists(path)) { |
| continue; |
| } |
| try (JarFile jf = new JarFile(path.toFile())) { |
| jf.stream() |
| .forEachOrdered( |
| entry -> { |
| try { |
| entries.put(entry.getName(), toByteArray(jf.getInputStream(entry))); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| }); |
| } |
| } |
| |
| if (!entries.isEmpty()) { |
| // If we found a JDK 8 bootclasspath (rt.jar, etc.) then we're done. |
| // |
| // However JDK 8 only contains bootclasspath API information for the current release, |
| // so we're always going to get a JDK 8 API level regardless of what the user requested. |
| // Emit a warning if they wanted to target a different version. |
| if (release != 8) { |
| System.err.printf( |
| "warning: ignoring release %s on --host_javabase=%s\n", |
| release, System.getProperty("java.version")); |
| } |
| } else { |
| // JDK > 8 --host_javabase bootclasspath handling. |
| // The default --host_javabase is currently JDK 10. |
| |
| // Set up a compilation with --release to initialize a filemanager |
| Context context = new Context(); |
| JavacTool.create() |
| .getTask( |
| /* out = */ null, |
| /* fileManager = */ null, |
| /* diagnosticListener = */ null, |
| /* options = */ Arrays.asList("--release", String.valueOf(release)), |
| /* classes = */ null, |
| /* compilationUnits = */ null, |
| context); |
| JavaFileManager fileManager = context.get(JavaFileManager.class); |
| |
| for (JavaFileObject fileObject : |
| fileManager.list( |
| StandardLocation.PLATFORM_CLASS_PATH, |
| "", |
| EnumSet.of(Kind.CLASS), |
| /* recurse= */ true)) { |
| String binaryName = |
| fileManager.inferBinaryName(StandardLocation.PLATFORM_CLASS_PATH, fileObject); |
| entries.put( |
| binaryName.replace('.', '/') + ".class", toByteArray(fileObject.openInputStream())); |
| } |
| |
| // Include the jdk.unsupported module for compatibility with JDK 8. |
| // (see: https://bugs.openjdk.java.net/browse/JDK-8206937) |
| // `--release 8` only provides access to supported APIs, which excludes e.g. sun.misc.Unsafe. |
| Path module = |
| FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/jdk.unsupported"); |
| Files.walkFileTree( |
| module, |
| new SimpleFileVisitor<Path>() { |
| @Override |
| public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) |
| throws IOException { |
| String name = path.getFileName().toString(); |
| if (name.endsWith(".class") && !name.equals("module-info.class")) { |
| entries.put(module.relativize(path).toString(), Files.readAllBytes(path)); |
| } |
| return super.visitFile(path, attrs); |
| } |
| }); |
| } |
| |
| try (OutputStream os = Files.newOutputStream(output); |
| BufferedOutputStream bos = new BufferedOutputStream(os, 65536); |
| JarOutputStream jos = new JarOutputStream(bos)) { |
| entries.entrySet().stream() |
| .sorted(comparing(Map.Entry::getKey)) |
| .forEachOrdered( |
| entry -> { |
| try { |
| addEntry(jos, entry.getKey(), entry.getValue()); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| }); |
| } |
| } |
| |
| // Use a fixed timestamp for deterministic jar output. |
| private static final long FIXED_TIMESTAMP = |
| new GregorianCalendar(2010, 0, 1, 0, 0, 0).getTimeInMillis(); |
| |
| private static void addEntry(JarOutputStream jos, String name, byte[] bytes) throws IOException { |
| JarEntry je = new JarEntry(name); |
| je.setTime(FIXED_TIMESTAMP); |
| je.setMethod(ZipEntry.STORED); |
| je.setSize(bytes.length); |
| CRC32 crc = new CRC32(); |
| crc.update(bytes); |
| je.setCrc(crc.getValue()); |
| jos.putNextEntry(je); |
| jos.write(bytes); |
| } |
| |
| private static byte[] toByteArray(InputStream is) throws IOException { |
| byte[] buffer = new byte[8192]; |
| ByteArrayOutputStream boas = new ByteArrayOutputStream(); |
| while (true) { |
| int r = is.read(buffer); |
| if (r == -1) { |
| break; |
| } |
| boas.write(buffer, 0, r); |
| } |
| return boas.toByteArray(); |
| } |
| } |