1*abe8e1b9SSadaf Ebrahimi // Copyright 2017 The Bazel Authors. All rights reserved. 2*abe8e1b9SSadaf Ebrahimi // 3*abe8e1b9SSadaf Ebrahimi // Licensed under the Apache License, Version 2.0 (the "License"); 4*abe8e1b9SSadaf Ebrahimi // you may not use this file except in compliance with the License. 5*abe8e1b9SSadaf Ebrahimi // You may obtain a copy of the License at 6*abe8e1b9SSadaf Ebrahimi // 7*abe8e1b9SSadaf Ebrahimi // http://www.apache.org/licenses/LICENSE-2.0 8*abe8e1b9SSadaf Ebrahimi // 9*abe8e1b9SSadaf Ebrahimi // Unless required by applicable law or agreed to in writing, software 10*abe8e1b9SSadaf Ebrahimi // distributed under the License is distributed on an "AS IS" BASIS, 11*abe8e1b9SSadaf Ebrahimi // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*abe8e1b9SSadaf Ebrahimi // See the License for the specific language governing permissions and 13*abe8e1b9SSadaf Ebrahimi // limitations under the License. 14*abe8e1b9SSadaf Ebrahimi 15*abe8e1b9SSadaf Ebrahimi import java.io.BufferedOutputStream; 16*abe8e1b9SSadaf Ebrahimi import java.io.ByteArrayOutputStream; 17*abe8e1b9SSadaf Ebrahimi import java.io.IOException; 18*abe8e1b9SSadaf Ebrahimi import java.io.InputStream; 19*abe8e1b9SSadaf Ebrahimi import java.io.OutputStream; 20*abe8e1b9SSadaf Ebrahimi import java.io.UncheckedIOException; 21*abe8e1b9SSadaf Ebrahimi import java.lang.reflect.Method; 22*abe8e1b9SSadaf Ebrahimi import java.net.URI; 23*abe8e1b9SSadaf Ebrahimi import java.nio.file.DirectoryStream; 24*abe8e1b9SSadaf Ebrahimi import java.nio.file.FileSystem; 25*abe8e1b9SSadaf Ebrahimi import java.nio.file.FileSystems; 26*abe8e1b9SSadaf Ebrahimi import java.nio.file.FileVisitResult; 27*abe8e1b9SSadaf Ebrahimi import java.nio.file.Files; 28*abe8e1b9SSadaf Ebrahimi import java.nio.file.Path; 29*abe8e1b9SSadaf Ebrahimi import java.nio.file.Paths; 30*abe8e1b9SSadaf Ebrahimi import java.nio.file.SimpleFileVisitor; 31*abe8e1b9SSadaf Ebrahimi import java.nio.file.attribute.BasicFileAttributes; 32*abe8e1b9SSadaf Ebrahimi import java.util.ArrayList; 33*abe8e1b9SSadaf Ebrahimi import java.util.Arrays; 34*abe8e1b9SSadaf Ebrahimi import java.util.Collection; 35*abe8e1b9SSadaf Ebrahimi import java.util.GregorianCalendar; 36*abe8e1b9SSadaf Ebrahimi import java.util.List; 37*abe8e1b9SSadaf Ebrahimi import java.util.Map; 38*abe8e1b9SSadaf Ebrahimi import java.util.SortedMap; 39*abe8e1b9SSadaf Ebrahimi import java.util.TreeMap; 40*abe8e1b9SSadaf Ebrahimi import java.util.jar.JarEntry; 41*abe8e1b9SSadaf Ebrahimi import java.util.jar.JarFile; 42*abe8e1b9SSadaf Ebrahimi import java.util.jar.JarOutputStream; 43*abe8e1b9SSadaf Ebrahimi import java.util.zip.CRC32; 44*abe8e1b9SSadaf Ebrahimi import java.util.zip.ZipEntry; 45*abe8e1b9SSadaf Ebrahimi 46*abe8e1b9SSadaf Ebrahimi /** 47*abe8e1b9SSadaf Ebrahimi * Output a jar file containing all classes on the platform classpath of the given JDK release. 48*abe8e1b9SSadaf Ebrahimi * 49*abe8e1b9SSadaf Ebrahimi * <p>usage: {@code DumpPlatformClassPath <output jar> <path to target JDK>} 50*abe8e1b9SSadaf Ebrahimi */ 51*abe8e1b9SSadaf Ebrahimi public class DumpPlatformClassPath { 52*abe8e1b9SSadaf Ebrahimi main(String[] args)53*abe8e1b9SSadaf Ebrahimi public static void main(String[] args) throws Exception { 54*abe8e1b9SSadaf Ebrahimi if (args.length != 2) { 55*abe8e1b9SSadaf Ebrahimi System.err.println("usage: DumpPlatformClassPath <output jar> <path to target JDK>"); 56*abe8e1b9SSadaf Ebrahimi System.exit(1); 57*abe8e1b9SSadaf Ebrahimi } 58*abe8e1b9SSadaf Ebrahimi Path output = Paths.get(args[0]); 59*abe8e1b9SSadaf Ebrahimi Path targetJavabase = Paths.get(args[1]); 60*abe8e1b9SSadaf Ebrahimi 61*abe8e1b9SSadaf Ebrahimi int hostMajorVersion = hostMajorVersion(); 62*abe8e1b9SSadaf Ebrahimi boolean ok; 63*abe8e1b9SSadaf Ebrahimi if (hostMajorVersion == 8) { 64*abe8e1b9SSadaf Ebrahimi ok = dumpJDK8BootClassPath(output, targetJavabase); 65*abe8e1b9SSadaf Ebrahimi } else { 66*abe8e1b9SSadaf Ebrahimi ok = dumpJDK9AndNewerBootClassPath(hostMajorVersion, output, targetJavabase); 67*abe8e1b9SSadaf Ebrahimi } 68*abe8e1b9SSadaf Ebrahimi System.exit(ok ? 0 : 1); 69*abe8e1b9SSadaf Ebrahimi } 70*abe8e1b9SSadaf Ebrahimi 71*abe8e1b9SSadaf Ebrahimi // JDK 8 bootclasspath handling. 72*abe8e1b9SSadaf Ebrahimi // * JDK 8 represents a bootclasspath as a search path of jars (rt.jar, etc.). 73*abe8e1b9SSadaf Ebrahimi // * It does not support --release or --system. dumpJDK8BootClassPath(Path output, Path targetJavabase)74*abe8e1b9SSadaf Ebrahimi static boolean dumpJDK8BootClassPath(Path output, Path targetJavabase) throws IOException { 75*abe8e1b9SSadaf Ebrahimi List<Path> bootClassPathJars = getBootClassPathJars(targetJavabase); 76*abe8e1b9SSadaf Ebrahimi writeClassPathJars(output, bootClassPathJars); 77*abe8e1b9SSadaf Ebrahimi return true; 78*abe8e1b9SSadaf Ebrahimi } 79*abe8e1b9SSadaf Ebrahimi 80*abe8e1b9SSadaf Ebrahimi // JDK > 8 --host_javabase bootclasspath handling. 81*abe8e1b9SSadaf Ebrahimi // (The default --host_javabase is currently JDK 9.) dumpJDK9AndNewerBootClassPath( int hostMajorVersion, Path output, Path targetJavabase)82*abe8e1b9SSadaf Ebrahimi static boolean dumpJDK9AndNewerBootClassPath( 83*abe8e1b9SSadaf Ebrahimi int hostMajorVersion, Path output, Path targetJavabase) throws IOException { 84*abe8e1b9SSadaf Ebrahimi 85*abe8e1b9SSadaf Ebrahimi // JDK 9 and newer support cross-compiling to older platform versions using the --system 86*abe8e1b9SSadaf Ebrahimi // and --release flags. 87*abe8e1b9SSadaf Ebrahimi // * --system takes the path to a JDK root for JDK 9 and up, and causes the compilation 88*abe8e1b9SSadaf Ebrahimi // to target the APIs from that JDK. 89*abe8e1b9SSadaf Ebrahimi // * --release takes a language level (e.g. '9') and uses the API information baked in to 90*abe8e1b9SSadaf Ebrahimi // the host JDK (in lib/ct.sym). 91*abe8e1b9SSadaf Ebrahimi 92*abe8e1b9SSadaf Ebrahimi // Since --system only supports JDK >= 9, first check if the target JDK defines a JDK 8 93*abe8e1b9SSadaf Ebrahimi // bootclasspath. 94*abe8e1b9SSadaf Ebrahimi List<Path> bootClassPathJars = getBootClassPathJars(targetJavabase); 95*abe8e1b9SSadaf Ebrahimi if (!bootClassPathJars.isEmpty()) { 96*abe8e1b9SSadaf Ebrahimi writeClassPathJars(output, bootClassPathJars); 97*abe8e1b9SSadaf Ebrahimi return true; 98*abe8e1b9SSadaf Ebrahimi } 99*abe8e1b9SSadaf Ebrahimi 100*abe8e1b9SSadaf Ebrahimi // Read the bootclasspath data using the JRT filesystem 101*abe8e1b9SSadaf Ebrahimi Map<String, byte[]> entries = new TreeMap<>(); 102*abe8e1b9SSadaf Ebrahimi Map<String, String> env = new TreeMap<>(); 103*abe8e1b9SSadaf Ebrahimi env.put("java.home", String.valueOf(targetJavabase)); 104*abe8e1b9SSadaf Ebrahimi try (FileSystem fileSystem = FileSystems.newFileSystem(URI.create("jrt:/"), env)) { 105*abe8e1b9SSadaf Ebrahimi Path modules = fileSystem.getPath("/modules"); 106*abe8e1b9SSadaf Ebrahimi try (DirectoryStream<Path> ms = Files.newDirectoryStream(modules)) { 107*abe8e1b9SSadaf Ebrahimi for (Path m : ms) { 108*abe8e1b9SSadaf Ebrahimi Files.walkFileTree( 109*abe8e1b9SSadaf Ebrahimi m, 110*abe8e1b9SSadaf Ebrahimi new SimpleFileVisitor<Path>() { 111*abe8e1b9SSadaf Ebrahimi @Override 112*abe8e1b9SSadaf Ebrahimi public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 113*abe8e1b9SSadaf Ebrahimi throws IOException { 114*abe8e1b9SSadaf Ebrahimi if (file.getFileName().toString().endsWith(".class")) { 115*abe8e1b9SSadaf Ebrahimi entries.put(m.relativize(file).toString(), Files.readAllBytes(file)); 116*abe8e1b9SSadaf Ebrahimi } 117*abe8e1b9SSadaf Ebrahimi return super.visitFile(file, attrs); 118*abe8e1b9SSadaf Ebrahimi } 119*abe8e1b9SSadaf Ebrahimi }); 120*abe8e1b9SSadaf Ebrahimi } 121*abe8e1b9SSadaf Ebrahimi } 122*abe8e1b9SSadaf Ebrahimi writeEntries(output, entries); 123*abe8e1b9SSadaf Ebrahimi } 124*abe8e1b9SSadaf Ebrahimi return true; 125*abe8e1b9SSadaf Ebrahimi } 126*abe8e1b9SSadaf Ebrahimi 127*abe8e1b9SSadaf Ebrahimi /** Writes the given entry names and data to a jar archive at the given path. */ writeEntries(Path output, Map<String, byte[]> entries)128*abe8e1b9SSadaf Ebrahimi private static void writeEntries(Path output, Map<String, byte[]> entries) throws IOException { 129*abe8e1b9SSadaf Ebrahimi if (!entries.containsKey("java/lang/Object.class")) { 130*abe8e1b9SSadaf Ebrahimi throw new AssertionError( 131*abe8e1b9SSadaf Ebrahimi "\nCould not find java.lang.Object on bootclasspath; something has gone terribly wrong.\n" 132*abe8e1b9SSadaf Ebrahimi + "Please file a bug: https://github.com/bazelbuild/bazel/issues"); 133*abe8e1b9SSadaf Ebrahimi } 134*abe8e1b9SSadaf Ebrahimi try (OutputStream os = Files.newOutputStream(output); 135*abe8e1b9SSadaf Ebrahimi BufferedOutputStream bos = new BufferedOutputStream(os, 65536); 136*abe8e1b9SSadaf Ebrahimi JarOutputStream jos = new JarOutputStream(bos)) { 137*abe8e1b9SSadaf Ebrahimi entries.entrySet().stream() 138*abe8e1b9SSadaf Ebrahimi .forEachOrdered( 139*abe8e1b9SSadaf Ebrahimi entry -> { 140*abe8e1b9SSadaf Ebrahimi try { 141*abe8e1b9SSadaf Ebrahimi addEntry(jos, entry.getKey(), entry.getValue()); 142*abe8e1b9SSadaf Ebrahimi } catch (IOException e) { 143*abe8e1b9SSadaf Ebrahimi throw new UncheckedIOException(e); 144*abe8e1b9SSadaf Ebrahimi } 145*abe8e1b9SSadaf Ebrahimi }); 146*abe8e1b9SSadaf Ebrahimi } 147*abe8e1b9SSadaf Ebrahimi } 148*abe8e1b9SSadaf Ebrahimi 149*abe8e1b9SSadaf Ebrahimi /** Collects the entries of the given jar files into a map from jar entry names to their data. */ writeClassPathJars(Path output, Collection<Path> paths)150*abe8e1b9SSadaf Ebrahimi private static void writeClassPathJars(Path output, Collection<Path> paths) throws IOException { 151*abe8e1b9SSadaf Ebrahimi List<JarFile> jars = new ArrayList<>(); 152*abe8e1b9SSadaf Ebrahimi for (Path path : paths) { 153*abe8e1b9SSadaf Ebrahimi jars.add(new JarFile(path.toFile())); 154*abe8e1b9SSadaf Ebrahimi } 155*abe8e1b9SSadaf Ebrahimi SortedMap<String, byte[]> entries = new TreeMap<>(); 156*abe8e1b9SSadaf Ebrahimi for (JarFile jar : jars) { 157*abe8e1b9SSadaf Ebrahimi jar.stream() 158*abe8e1b9SSadaf Ebrahimi .filter(p -> p.getName().endsWith(".class")) 159*abe8e1b9SSadaf Ebrahimi .forEachOrdered( 160*abe8e1b9SSadaf Ebrahimi entry -> { 161*abe8e1b9SSadaf Ebrahimi try { 162*abe8e1b9SSadaf Ebrahimi entries.put(entry.getName(), toByteArray(jar.getInputStream(entry))); 163*abe8e1b9SSadaf Ebrahimi } catch (IOException e) { 164*abe8e1b9SSadaf Ebrahimi throw new UncheckedIOException(e); 165*abe8e1b9SSadaf Ebrahimi } 166*abe8e1b9SSadaf Ebrahimi }); 167*abe8e1b9SSadaf Ebrahimi } 168*abe8e1b9SSadaf Ebrahimi writeEntries(output, entries); 169*abe8e1b9SSadaf Ebrahimi for (JarFile jar : jars) { 170*abe8e1b9SSadaf Ebrahimi jar.close(); 171*abe8e1b9SSadaf Ebrahimi } 172*abe8e1b9SSadaf Ebrahimi } 173*abe8e1b9SSadaf Ebrahimi 174*abe8e1b9SSadaf Ebrahimi /** Returns paths to the entries of a JDK 8-style bootclasspath. */ getBootClassPathJars(Path javaHome)175*abe8e1b9SSadaf Ebrahimi private static List<Path> getBootClassPathJars(Path javaHome) throws IOException { 176*abe8e1b9SSadaf Ebrahimi List<Path> jars = new ArrayList<>(); 177*abe8e1b9SSadaf Ebrahimi Path extDir = javaHome.resolve("jre/lib/ext"); 178*abe8e1b9SSadaf Ebrahimi if (Files.exists(extDir)) { 179*abe8e1b9SSadaf Ebrahimi for (Path extJar : Files.newDirectoryStream(extDir, "*.jar")) { 180*abe8e1b9SSadaf Ebrahimi jars.add(extJar); 181*abe8e1b9SSadaf Ebrahimi } 182*abe8e1b9SSadaf Ebrahimi } 183*abe8e1b9SSadaf Ebrahimi for (String jar : 184*abe8e1b9SSadaf Ebrahimi Arrays.asList("rt.jar", "resources.jar", "jsse.jar", "jce.jar", "charsets.jar")) { 185*abe8e1b9SSadaf Ebrahimi Path path = javaHome.resolve("jre/lib").resolve(jar); 186*abe8e1b9SSadaf Ebrahimi if (Files.exists(path)) { 187*abe8e1b9SSadaf Ebrahimi jars.add(path); 188*abe8e1b9SSadaf Ebrahimi } 189*abe8e1b9SSadaf Ebrahimi } 190*abe8e1b9SSadaf Ebrahimi Path toolsJar = javaHome.resolve("lib/tools.jar"); 191*abe8e1b9SSadaf Ebrahimi if (Files.exists(toolsJar)) { 192*abe8e1b9SSadaf Ebrahimi jars.add(toolsJar); 193*abe8e1b9SSadaf Ebrahimi } 194*abe8e1b9SSadaf Ebrahimi return jars; 195*abe8e1b9SSadaf Ebrahimi } 196*abe8e1b9SSadaf Ebrahimi 197*abe8e1b9SSadaf Ebrahimi // Use a fixed timestamp for deterministic jar output. 198*abe8e1b9SSadaf Ebrahimi private static final long FIXED_TIMESTAMP = 199*abe8e1b9SSadaf Ebrahimi new GregorianCalendar(2010, 0, 1, 0, 0, 0).getTimeInMillis(); 200*abe8e1b9SSadaf Ebrahimi 201*abe8e1b9SSadaf Ebrahimi /** 202*abe8e1b9SSadaf Ebrahimi * Add a jar entry to the given {@link JarOutputStream}, normalizing the entry timestamps to 203*abe8e1b9SSadaf Ebrahimi * ensure deterministic build output. 204*abe8e1b9SSadaf Ebrahimi */ addEntry(JarOutputStream jos, String name, byte[] bytes)205*abe8e1b9SSadaf Ebrahimi private static void addEntry(JarOutputStream jos, String name, byte[] bytes) throws IOException { 206*abe8e1b9SSadaf Ebrahimi JarEntry je = new JarEntry(name); 207*abe8e1b9SSadaf Ebrahimi je.setTime(FIXED_TIMESTAMP); 208*abe8e1b9SSadaf Ebrahimi je.setMethod(ZipEntry.STORED); 209*abe8e1b9SSadaf Ebrahimi // When targeting JDK >= 10, patch the major version so it will be accepted by javac 9 210*abe8e1b9SSadaf Ebrahimi // TODO(cushon): remove this after updating javac 211*abe8e1b9SSadaf Ebrahimi if (bytes[7] > 53) { 212*abe8e1b9SSadaf Ebrahimi bytes[7] = 53; 213*abe8e1b9SSadaf Ebrahimi } 214*abe8e1b9SSadaf Ebrahimi je.setSize(bytes.length); 215*abe8e1b9SSadaf Ebrahimi CRC32 crc = new CRC32(); 216*abe8e1b9SSadaf Ebrahimi crc.update(bytes); 217*abe8e1b9SSadaf Ebrahimi je.setCrc(crc.getValue()); 218*abe8e1b9SSadaf Ebrahimi jos.putNextEntry(je); 219*abe8e1b9SSadaf Ebrahimi jos.write(bytes); 220*abe8e1b9SSadaf Ebrahimi } 221*abe8e1b9SSadaf Ebrahimi toByteArray(InputStream is)222*abe8e1b9SSadaf Ebrahimi private static byte[] toByteArray(InputStream is) throws IOException { 223*abe8e1b9SSadaf Ebrahimi byte[] buffer = new byte[8192]; 224*abe8e1b9SSadaf Ebrahimi ByteArrayOutputStream boas = new ByteArrayOutputStream(); 225*abe8e1b9SSadaf Ebrahimi while (true) { 226*abe8e1b9SSadaf Ebrahimi int r = is.read(buffer); 227*abe8e1b9SSadaf Ebrahimi if (r == -1) { 228*abe8e1b9SSadaf Ebrahimi break; 229*abe8e1b9SSadaf Ebrahimi } 230*abe8e1b9SSadaf Ebrahimi boas.write(buffer, 0, r); 231*abe8e1b9SSadaf Ebrahimi } 232*abe8e1b9SSadaf Ebrahimi return boas.toByteArray(); 233*abe8e1b9SSadaf Ebrahimi } 234*abe8e1b9SSadaf Ebrahimi 235*abe8e1b9SSadaf Ebrahimi /** 236*abe8e1b9SSadaf Ebrahimi * Returns the major version of the host Java runtime (e.g. '8' for JDK 8), using {@link 237*abe8e1b9SSadaf Ebrahimi * Runtime#version} if it is available, and otherwise falling back to the {@code 238*abe8e1b9SSadaf Ebrahimi * java.class.version} system. property. 239*abe8e1b9SSadaf Ebrahimi */ hostMajorVersion()240*abe8e1b9SSadaf Ebrahimi static int hostMajorVersion() { 241*abe8e1b9SSadaf Ebrahimi try { 242*abe8e1b9SSadaf Ebrahimi Method versionMethod = Runtime.class.getMethod("version"); 243*abe8e1b9SSadaf Ebrahimi Object version = versionMethod.invoke(null); 244*abe8e1b9SSadaf Ebrahimi return (int) version.getClass().getMethod("major").invoke(version); 245*abe8e1b9SSadaf Ebrahimi } catch (ReflectiveOperationException e) { 246*abe8e1b9SSadaf Ebrahimi // Runtime.version() isn't available on JDK 8; continue below 247*abe8e1b9SSadaf Ebrahimi } 248*abe8e1b9SSadaf Ebrahimi int version = (int) Double.parseDouble(System.getProperty("java.class.version")); 249*abe8e1b9SSadaf Ebrahimi if (49 <= version && version <= 52) { 250*abe8e1b9SSadaf Ebrahimi return version - (49 - 5); 251*abe8e1b9SSadaf Ebrahimi } 252*abe8e1b9SSadaf Ebrahimi throw new IllegalStateException( 253*abe8e1b9SSadaf Ebrahimi "Unknown Java version: " + System.getProperty("java.specification.version")); 254*abe8e1b9SSadaf Ebrahimi } 255*abe8e1b9SSadaf Ebrahimi }