blob: e23ca53193f8f20e6d43132c8421b3e682827d81 [file] [log] [blame]
cushon29a96822017-12-20 16:19:56 -08001// Copyright 2017 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070015import com.sun.tools.javac.api.JavacTool;
16import com.sun.tools.javac.util.Context;
cushon29a96822017-12-20 16:19:56 -080017import java.io.BufferedOutputStream;
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070018import java.io.ByteArrayOutputStream;
cushon29a96822017-12-20 16:19:56 -080019import java.io.IOException;
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070020import java.io.InputStream;
cushon29a96822017-12-20 16:19:56 -080021import java.io.OutputStream;
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070022import java.io.UncheckedIOException;
Liam Miller-Cushon4566a422018-09-07 00:32:41 -070023import java.lang.reflect.Method;
cushon29a96822017-12-20 16:19:56 -080024import java.nio.file.Files;
25import java.nio.file.Path;
26import java.nio.file.Paths;
lberki28346132018-09-11 11:55:39 -070027import java.util.ArrayList;
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070028import java.util.Arrays;
cushon999354b2018-09-21 00:31:42 -070029import java.util.Collection;
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070030import java.util.EnumSet;
cushon29a96822017-12-20 16:19:56 -080031import java.util.GregorianCalendar;
lberki28346132018-09-11 11:55:39 -070032import java.util.List;
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070033import java.util.Map;
cushon999354b2018-09-21 00:31:42 -070034import java.util.SortedMap;
35import java.util.TreeMap;
cushon29a96822017-12-20 16:19:56 -080036import java.util.jar.JarEntry;
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070037import java.util.jar.JarFile;
cushon29a96822017-12-20 16:19:56 -080038import java.util.jar.JarOutputStream;
39import java.util.zip.CRC32;
40import java.util.zip.ZipEntry;
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070041import javax.tools.JavaFileManager;
42import javax.tools.JavaFileObject;
43import javax.tools.JavaFileObject.Kind;
Liam Miller-Cushon4566a422018-09-07 00:32:41 -070044import javax.tools.StandardJavaFileManager;
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070045import javax.tools.StandardLocation;
cushon29a96822017-12-20 16:19:56 -080046
47/**
Liam Miller-Cushon39873002018-08-28 18:47:25 -070048 * Output a jar file containing all classes on the platform classpath of the given JDK release.
cushon29a96822017-12-20 16:19:56 -080049 *
cushon999354b2018-09-21 00:31:42 -070050 * <p>usage: DumpPlatformClassPath <release version> <output jar> <path to target JDK>?
cushon29a96822017-12-20 16:19:56 -080051 */
52public class DumpPlatformClassPath {
53
Liam Miller-Cushon282b8832018-08-16 08:02:08 -070054 public static void main(String[] args) throws Exception {
cushon917d0cd2018-10-24 00:27:35 -070055 if (args.length != 2) {
56 System.err.println("usage: DumpPlatformClassPath <output jar> <path to target JDK>");
cushon29a96822017-12-20 16:19:56 -080057 System.exit(1);
58 }
cushon917d0cd2018-10-24 00:27:35 -070059 Path output = Paths.get(args[0]);
60 Path targetJavabase = Paths.get(args[1]);
lberki28346132018-09-11 11:55:39 -070061
cushon999354b2018-09-21 00:31:42 -070062 int hostMajorVersion = hostMajorVersion();
63 boolean ok;
64 if (hostMajorVersion == 8) {
cushon917d0cd2018-10-24 00:27:35 -070065 ok = dumpJDK8BootClassPath(output, targetJavabase);
cushon999354b2018-09-21 00:31:42 -070066 } else {
cushon917d0cd2018-10-24 00:27:35 -070067 ok = dumpJDK9AndNewerBootClassPath(hostMajorVersion, output, targetJavabase);
cushon999354b2018-09-21 00:31:42 -070068 }
69 System.exit(ok ? 0 : 1);
70 }
lberki28346132018-09-11 11:55:39 -070071
cushon999354b2018-09-21 00:31:42 -070072 // JDK 8 bootclasspath handling.
73 // * JDK 8 represents a bootclasspath as a search path of jars (rt.jar, etc.).
74 // * It does not support --release or --system.
cushon917d0cd2018-10-24 00:27:35 -070075 static boolean dumpJDK8BootClassPath(Path output, Path targetJavabase) throws IOException {
76 List<Path> bootClassPathJars = getBootClassPathJars(targetJavabase);
cushon999354b2018-09-21 00:31:42 -070077 writeClassPathJars(output, bootClassPathJars);
78 return true;
79 }
lberki28346132018-09-11 11:55:39 -070080
cushon999354b2018-09-21 00:31:42 -070081 // JDK > 8 --host_javabase bootclasspath handling.
82 // (The default --host_javabase is currently JDK 9.)
83 static boolean dumpJDK9AndNewerBootClassPath(
cushon917d0cd2018-10-24 00:27:35 -070084 int hostMajorVersion, Path output, Path targetJavabase) throws IOException {
cushon999354b2018-09-21 00:31:42 -070085
86 // JDK 9 and newer support cross-compiling to older platform versions using the --system
87 // and --release flags.
88 // * --system takes the path to a JDK root for JDK 9 and up, and causes the compilation
89 // to target the APIs from that JDK.
90 // * --release takes a language level (e.g. '9') and uses the API information baked in to
91 // the host JDK (in lib/ct.sym).
92
93 // Since --system only supports JDK >= 9, first check of the target JDK defines a JDK 8
94 // bootclasspath.
cushon917d0cd2018-10-24 00:27:35 -070095 List<Path> bootClassPathJars = getBootClassPathJars(targetJavabase);
96 if (!bootClassPathJars.isEmpty()) {
97 writeClassPathJars(output, bootClassPathJars);
98 return true;
cushon999354b2018-09-21 00:31:42 -070099 }
100
cushon917d0cd2018-10-24 00:27:35 -0700101 // Initialize a FileManager to process the --system argument, and then read the
cushon999354b2018-09-21 00:31:42 -0700102 // initialized bootclasspath data back out.
103
cushon999354b2018-09-21 00:31:42 -0700104 Context context = new Context();
105 JavacTool.create()
106 .getTask(
107 /* out = */ null,
108 /* fileManager = */ null,
109 /* diagnosticListener = */ null,
cushon917d0cd2018-10-24 00:27:35 -0700110 /* options = */ Arrays.asList("--system", String.valueOf(targetJavabase)),
cushon999354b2018-09-21 00:31:42 -0700111 /* classes = */ null,
112 /* compilationUnits = */ null,
113 context);
114 StandardJavaFileManager fileManager =
115 (StandardJavaFileManager) context.get(JavaFileManager.class);
116
117 SortedMap<String, InputStream> entries = new TreeMap<>();
cushon917d0cd2018-10-24 00:27:35 -0700118 for (JavaFileObject fileObject :
119 fileManager.list(
120 StandardLocation.PLATFORM_CLASS_PATH,
121 "",
122 EnumSet.of(Kind.CLASS),
123 /* recurse= */ true)) {
124 String binaryName =
125 fileManager.inferBinaryName(StandardLocation.PLATFORM_CLASS_PATH, fileObject);
126 entries.put(binaryName.replace('.', '/') + ".class", fileObject.openInputStream());
Liam Miller-Cushon282b8832018-08-16 08:02:08 -0700127 }
cushon999354b2018-09-21 00:31:42 -0700128 writeEntries(output, entries);
129 return true;
130 }
Liam Miller-Cushon282b8832018-08-16 08:02:08 -0700131
cushon999354b2018-09-21 00:31:42 -0700132 /** Writes the given entry names and data to a jar archive at the given path. */
133 private static void writeEntries(Path output, Map<String, InputStream> entries)
134 throws IOException {
cushon2579b792018-09-12 01:50:01 -0700135 if (!entries.containsKey("java/lang/Object.class")) {
136 throw new AssertionError(
137 "\nCould not find java.lang.Object on bootclasspath; something has gone terribly wrong.\n"
138 + "Please file a bug: https://github.com/bazelbuild/bazel/issues");
139 }
cushonc56699d2018-08-08 08:42:10 -0700140 try (OutputStream os = Files.newOutputStream(output);
cushon29a96822017-12-20 16:19:56 -0800141 BufferedOutputStream bos = new BufferedOutputStream(os, 65536);
142 JarOutputStream jos = new JarOutputStream(bos)) {
Liam Miller-Cushon282b8832018-08-16 08:02:08 -0700143 entries.entrySet().stream()
Liam Miller-Cushon282b8832018-08-16 08:02:08 -0700144 .forEachOrdered(
145 entry -> {
146 try {
147 addEntry(jos, entry.getKey(), entry.getValue());
148 } catch (IOException e) {
149 throw new UncheckedIOException(e);
cushonc56699d2018-08-08 08:42:10 -0700150 }
151 });
cushon444fc9f2018-01-24 02:52:19 -0800152 }
153 }
154
cushon999354b2018-09-21 00:31:42 -0700155 /** Collects the entries of the given jar files into a map from jar entry names to their data. */
156 private static void writeClassPathJars(Path output, Collection<Path> paths) throws IOException {
157 List<JarFile> jars = new ArrayList<>();
158 for (Path path : paths) {
159 jars.add(new JarFile(path.toFile()));
160 }
161 SortedMap<String, InputStream> entries = new TreeMap<>();
162 for (JarFile jar : jars) {
163 jar.stream()
164 .filter(p -> p.getName().endsWith(".class"))
165 .forEachOrdered(
166 entry -> {
167 try {
168 entries.put(entry.getName(), jar.getInputStream(entry));
169 } catch (IOException e) {
170 throw new UncheckedIOException(e);
171 }
172 });
173 }
174 writeEntries(output, entries);
175 for (JarFile jar : jars) {
176 jar.close();
177 }
178 }
179
180 /** Returns paths to the entries of a JDK 8-style bootclasspath. */
181 private static List<Path> getBootClassPathJars(Path javaHome) throws IOException {
182 List<Path> jars = new ArrayList<>();
183 Path extDir = javaHome.resolve("jre/lib/ext");
184 if (Files.exists(extDir)) {
185 for (Path extJar : Files.newDirectoryStream(extDir, "*.jar")) {
186 jars.add(extJar);
187 }
188 }
189 for (String jar :
190 Arrays.asList("rt.jar", "resources.jar", "jsse.jar", "jce.jar", "charsets.jar")) {
191 Path path = javaHome.resolve("jre/lib").resolve(jar);
192 if (Files.exists(path)) {
193 jars.add(path);
194 }
195 }
196 return jars;
197 }
198
cushon29a96822017-12-20 16:19:56 -0800199 // Use a fixed timestamp for deterministic jar output.
200 private static final long FIXED_TIMESTAMP =
201 new GregorianCalendar(2010, 0, 1, 0, 0, 0).getTimeInMillis();
202
cushon999354b2018-09-21 00:31:42 -0700203 /**
204 * Add a jar entry to the given {@link JarOutputStream}, normalizing the entry timestamps to
205 * ensure deterministic build output.
206 */
207 private static void addEntry(JarOutputStream jos, String name, InputStream input)
208 throws IOException {
cushonc56699d2018-08-08 08:42:10 -0700209 JarEntry je = new JarEntry(name);
210 je.setTime(FIXED_TIMESTAMP);
211 je.setMethod(ZipEntry.STORED);
cushon999354b2018-09-21 00:31:42 -0700212 byte[] bytes = toByteArray(input);
cushon148f7142019-01-24 13:32:13 -0800213 // When targeting JDK >= 10, patch the major version so it will be accepted by javac 9
214 // TODO(cushon): remove this after updating javac
215 if (bytes[7] > 53) {
216 bytes[7] = 53;
217 }
cushonc56699d2018-08-08 08:42:10 -0700218 je.setSize(bytes.length);
219 CRC32 crc = new CRC32();
220 crc.update(bytes);
221 je.setCrc(crc.getValue());
222 jos.putNextEntry(je);
223 jos.write(bytes);
cushon29a96822017-12-20 16:19:56 -0800224 }
Liam Miller-Cushon282b8832018-08-16 08:02:08 -0700225
226 private static byte[] toByteArray(InputStream is) throws IOException {
227 byte[] buffer = new byte[8192];
228 ByteArrayOutputStream boas = new ByteArrayOutputStream();
229 while (true) {
230 int r = is.read(buffer);
231 if (r == -1) {
232 break;
233 }
234 boas.write(buffer, 0, r);
235 }
236 return boas.toByteArray();
237 }
Liam Miller-Cushon4566a422018-09-07 00:32:41 -0700238
cushon999354b2018-09-21 00:31:42 -0700239 /**
cushon999354b2018-09-21 00:31:42 -0700240 * Returns the major version of the host Java runtime (e.g. '8' for JDK 8), using {@link
241 * Runtime#version} if it is available, and otherwise falling back to the {@code
242 * java.class.version} system. property.
243 */
244 static int hostMajorVersion() {
Liam Miller-Cushon4566a422018-09-07 00:32:41 -0700245 try {
246 Method versionMethod = Runtime.class.getMethod("version");
247 Object version = versionMethod.invoke(null);
cushon2579b792018-09-12 01:50:01 -0700248 return (int) version.getClass().getMethod("major").invoke(version);
Liam Miller-Cushon4566a422018-09-07 00:32:41 -0700249 } catch (ReflectiveOperationException e) {
cushon2579b792018-09-12 01:50:01 -0700250 // Runtime.version() isn't available on JDK 8; continue below
Liam Miller-Cushon4566a422018-09-07 00:32:41 -0700251 }
cushon2579b792018-09-12 01:50:01 -0700252 int version = (int) Double.parseDouble(System.getProperty("java.class.version"));
253 if (49 <= version && version <= 52) {
254 return version - (49 - 5);
255 }
256 throw new IllegalStateException(
257 "Unknown Java version: " + System.getProperty("java.specification.version"));
Liam Miller-Cushon4566a422018-09-07 00:32:41 -0700258 }
cushon29a96822017-12-20 16:19:56 -0800259}