aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/java_tools/buildjar/java/com/google
diff options
context:
space:
mode:
authorGravatar Liam Miller-Cushon <cushon@google.com>2016-02-09 20:27:13 +0000
committerGravatar Dmitry Lomov <dslomov@google.com>2016-02-10 10:24:29 +0000
commit7896c3aaa911d429d4200951469132727b73b7e8 (patch)
treed350f2ebc08c05072f31ab92db9ae5bf6e616dd9 /src/java_tools/buildjar/java/com/google
parentef9cba3dea5bc0a37f23de11dda79261f1e137e5 (diff)
Turbine, a tool for improving Java build performance
Turbine compiles ijars from source, ignoring method bodies and relaxing error checks for performance. We can then do normal compilation against those ijars and move javac off the build's critical path. -- MOS_MIGRATED_REVID=114247125
Diffstat (limited to 'src/java_tools/buildjar/java/com/google')
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/BUILD33
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/BUILD101
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbine.java371
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompileRequest.java117
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompileResult.java64
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompiler.java93
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineJavaCompiler.java96
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/TreePruner.java231
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/ZipOutputFileManager.java161
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/ZipUtil.java53
10 files changed, 1320 insertions, 0 deletions
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/BUILD b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/BUILD
new file mode 100644
index 0000000000..173ea98a94
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/BUILD
@@ -0,0 +1,33 @@
+package_group(
+ name = "packages",
+ packages = ["//src/java_tools/buildjar/..."],
+)
+
+package(default_visibility = [":packages"])
+
+java_binary(
+ name = "turbine",
+ main_class = "com.google.devtools.build.java.turbine.javac.JavacTurbine",
+ runtime_deps = [
+ "//src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac:javac_turbine",
+ ],
+)
+
+java_library(
+ name = "turbine_options",
+ srcs = ["TurbineOptions.java"],
+ deps = [
+ "//third_party:guava",
+ "//third_party:jsr305",
+ ],
+)
+
+java_library(
+ name = "turbine_options_parser",
+ srcs = ["TurbineOptionsParser.java"],
+ deps = [
+ ":turbine_options",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ ],
+)
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/BUILD b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/BUILD
new file mode 100644
index 0000000000..4188035404
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/BUILD
@@ -0,0 +1,101 @@
+package_group(
+ name = "packages",
+ packages = ["//src/java_tools/buildjar/..."],
+)
+
+package(default_visibility = [":packages"])
+
+java_library(
+ name = "javac_turbine",
+ srcs = ["JavacTurbine.java"],
+ deps = [
+ ":javac_turbine_compile_request",
+ ":javac_turbine_compile_result",
+ ":javac_turbine_compiler",
+ ":zip_output_file_manager",
+ ":zip_util",
+ "//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins:dependency",
+ "//src/java_tools/buildjar/java/com/google/devtools/build/java/turbine:turbine_options",
+ "//src/java_tools/buildjar/java/com/google/devtools/build/java/turbine:turbine_options_parser",
+ "//third_party:asm",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ "//third_party/java/jdk/langtools:javac",
+ ],
+)
+
+java_library(
+ name = "javac_turbine_compile_request",
+ srcs = ["JavacTurbineCompileRequest.java"],
+ deps = [
+ "//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins:dependency",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ "//third_party/java/jdk/langtools:javac",
+ ],
+)
+
+java_library(
+ name = "javac_turbine_compile_result",
+ srcs = ["JavacTurbineCompileResult.java"],
+ deps = [
+ ":zip_output_file_manager",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ "//third_party/java/jdk/langtools:javac",
+ ],
+)
+
+java_library(
+ name = "javac_turbine_compiler",
+ srcs = ["JavacTurbineCompiler.java"],
+ deps = [
+ ":javac_turbine_compile_request",
+ ":javac_turbine_compile_result",
+ ":javac_turbine_java_compiler",
+ ":zip_output_file_manager",
+ "//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins:dependency",
+ "//third_party:guava",
+ "//third_party:jsr305",
+ "//third_party/java/jdk/langtools:javac",
+ ],
+)
+
+java_library(
+ name = "javac_turbine_java_compiler",
+ srcs = ["JavacTurbineJavaCompiler.java"],
+ deps = [
+ ":tree_pruner",
+ "//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins:dependency",
+ "//third_party:jsr305",
+ "//third_party/java/jdk/langtools:javac",
+ ],
+)
+
+java_library(
+ name = "zip_output_file_manager",
+ srcs = ["ZipOutputFileManager.java"],
+ deps = [
+ "//third_party:jsr305",
+ "//third_party/java/jdk/langtools:javac",
+ ],
+)
+
+java_library(
+ name = "zip_util",
+ srcs = ["ZipUtil.java"],
+ deps = [
+ "//third_party:jsr305",
+ "//third_party/java/jdk/langtools:javac",
+ ],
+)
+
+java_library(
+ name = "tree_pruner",
+ srcs = ["TreePruner.java"],
+ deps = [
+ "//third_party:guava",
+ "//third_party:jsr305",
+ "//third_party/java/jdk/langtools:javac",
+ ],
+)
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbine.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbine.java
new file mode 100644
index 0000000000..e30b672c5b
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbine.java
@@ -0,0 +1,371 @@
+// Copyright 2016 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.
+
+package com.google.devtools.build.java.turbine.javac;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.buildjar.javac.plugins.dependency.DependencyModule;
+import com.google.devtools.build.buildjar.javac.plugins.dependency.DependencyModule.StrictJavaDeps;
+import com.google.devtools.build.buildjar.javac.plugins.dependency.StrictJavaDepsPlugin;
+import com.google.devtools.build.java.turbine.TurbineOptions;
+import com.google.devtools.build.java.turbine.TurbineOptionsParser;
+import com.google.devtools.build.java.turbine.javac.ZipOutputFileManager.OutputFileObject;
+
+import com.sun.tools.javac.util.Context;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+import javax.tools.StandardLocation;
+
+/**
+ * An header compiler implementation based on javac.
+ *
+ * <p>This is a reference implementation used to develop the blaze integration, and to validate
+ * the real header compilation implementation.
+ */
+public class JavacTurbine implements AutoCloseable {
+
+ public static void main(String[] args) throws IOException {
+ System.exit(compile(TurbineOptionsParser.parse(Arrays.asList(args))).exitCode());
+ }
+
+ public static Result compile(TurbineOptions turbineOptions) throws IOException {
+ try (JavacTurbine turbine = new JavacTurbine(turbineOptions)) {
+ return turbine.compile();
+ }
+ }
+
+ /** A header compilation result. */
+ enum Result {
+ /** The compilation succeeded with the reduced classpath optimization. */
+ OK_WITH_REDUCED_CLASSPATH(true),
+
+ /** The compilation succeeded, but had to fall back to a transitive classpath. */
+ OK_WITH_FULL_CLASSPATH(true),
+
+ /** The compilation did not succeed. */
+ ERROR(false);
+
+ private final boolean ok;
+
+ private Result(boolean ok) {
+ this.ok = ok;
+ }
+
+ boolean ok() {
+ return ok;
+ }
+
+ int exitCode() {
+ return ok ? 0 : 1;
+ }
+ }
+
+ private static final int ZIPFILE_BUFFER_SIZE = 1024 * 16;
+
+ private static final Joiner CLASSPATH_JOINER = Joiner.on(':');
+
+ private final PrintWriter out;
+ private final TurbineOptions turbineOptions;
+ @VisibleForTesting Context context;
+
+ public JavacTurbine(PrintWriter out, TurbineOptions turbineOptions) {
+ this.out = out;
+ this.turbineOptions = turbineOptions;
+ }
+
+ public JavacTurbine(TurbineOptions turbineOptions) {
+ this(new PrintWriter(System.err, true), turbineOptions);
+ }
+
+ Result compile() throws IOException {
+ Path tmpdir = Paths.get(turbineOptions.tempDir());
+ Files.createDirectories(tmpdir);
+
+ ImmutableList.Builder<String> argbuilder = ImmutableList.builder();
+
+ addLanguageLevel(argbuilder, turbineOptions.javacOpts());
+
+ // Disable compilation of implicit source files.
+ // This is insurance: the sourcepath is empty, so we don't expect implicit sources.
+ argbuilder.add("-implicit:none");
+
+ ImmutableList<Path> processorpath;
+ if (!turbineOptions.processors().isEmpty()) {
+ argbuilder.add("-processor");
+ argbuilder.add(Joiner.on(',').join(turbineOptions.processors()));
+ processorpath = asPaths(turbineOptions.processorPath());
+ } else {
+ processorpath = ImmutableList.of();
+ }
+
+ List<String> sources = new ArrayList<>();
+ sources.addAll(turbineOptions.sources());
+ sources.addAll(extractSourceJars(turbineOptions, tmpdir));
+
+ argbuilder.addAll(sources);
+
+ JavacTurbineCompileRequest.Builder requestBuilder =
+ JavacTurbineCompileRequest.builder()
+ .setJavacOptions(argbuilder.build())
+ .setBootClassPath(asPaths(turbineOptions.bootClassPath()))
+ .setProcessorClassPath(processorpath);
+
+ StrictJavaDeps strictDepsMode = StrictJavaDeps.valueOf(turbineOptions.strictDepsMode());
+
+ DependencyModule dependencyModule = buildDependencyModule(turbineOptions, strictDepsMode);
+
+ Result result = Result.ERROR;
+ JavacTurbineCompileResult compileResult;
+ List<String> actualClasspath;
+
+ if (strictDepsMode != StrictJavaDeps.OFF) {
+
+ List<String> originalClasspath = turbineOptions.classPath();
+ List<String> compressedClasspath =
+ dependencyModule.computeStrictClasspath(turbineOptions.classPath());
+
+ requestBuilder.setStrictDepsPlugin(new StrictJavaDepsPlugin(dependencyModule));
+
+ {
+ // compile with reduced classpath
+ actualClasspath = compressedClasspath;
+ requestBuilder.setClassPath(asPaths(actualClasspath));
+ compileResult = JavacTurbineCompiler.compile(requestBuilder.build());
+ if (compileResult.success()) {
+ result = Result.OK_WITH_REDUCED_CLASSPATH;
+ context = compileResult.context();
+ }
+ }
+
+ if (!compileResult.success() && hasRecognizedError(compileResult.output())) {
+ // fall back to transitive classpath
+ deleteRecursively(tmpdir);
+ extractSourceJars(turbineOptions, tmpdir);
+
+ actualClasspath = originalClasspath;
+ requestBuilder.setClassPath(asPaths(actualClasspath));
+ compileResult = JavacTurbineCompiler.compile(requestBuilder.build());
+ if (compileResult.success()) {
+ result = Result.OK_WITH_FULL_CLASSPATH;
+ context = compileResult.context();
+ }
+ }
+
+ } else {
+ actualClasspath = turbineOptions.classPath();
+ requestBuilder.setClassPath(asPaths(actualClasspath));
+ compileResult = JavacTurbineCompiler.compile(requestBuilder.build());
+ if (compileResult.success()) {
+ result = Result.OK_WITH_FULL_CLASSPATH;
+ context = compileResult.context();
+ }
+ }
+
+ if (!result.ok()) {
+ out.println(compileResult.output());
+ return result;
+ }
+
+ emitClassJar(Paths.get(turbineOptions.outputFile()), compileResult);
+ dependencyModule.emitUsedClasspath(CLASSPATH_JOINER.join(actualClasspath));
+ dependencyModule.emitDependencyInformation(
+ CLASSPATH_JOINER.join(actualClasspath), compileResult.success());
+
+ return result;
+ }
+
+ private static DependencyModule buildDependencyModule(
+ TurbineOptions turbineOptions, StrictJavaDeps strictDepsMode) {
+ DependencyModule.Builder dependencyModuleBuilder =
+ new DependencyModule.Builder()
+ .setReduceClasspath()
+ .setTargetLabel(turbineOptions.targetLabel())
+ .addDepsArtifacts(turbineOptions.depsArtifacts())
+ .setStrictJavaDeps(strictDepsMode.toString())
+ .addDirectMappings(turbineOptions.directJarsToTargets())
+ .addIndirectMappings(turbineOptions.indirectJarsToTargets());
+
+ if (turbineOptions.outputDeps().isPresent()) {
+ dependencyModuleBuilder.setOutputDepsProtoFile(turbineOptions.outputDeps().get());
+ }
+
+ return dependencyModuleBuilder.build();
+ }
+
+ /** Write the class output from a successful compilation to the output jar. */
+ private static void emitClassJar(Path outputJar, JavacTurbineCompileResult result)
+ throws IOException {
+ try (OutputStream fos = Files.newOutputStream(outputJar);
+ ZipOutputStream zipOut =
+ new ZipOutputStream(new BufferedOutputStream(fos, ZIPFILE_BUFFER_SIZE))) {
+ boolean hasEntries = false;
+ for (Map.Entry<String, OutputFileObject> entry : result.files().entrySet()) {
+ if (entry.getValue().location != StandardLocation.CLASS_OUTPUT) {
+ continue;
+ }
+ String name = entry.getKey();
+ byte[] bytes = entry.getValue().asBytes();
+ if (name.endsWith(".class")) {
+ bytes = removeCode(bytes);
+ }
+ ZipUtil.storeEntry(name, bytes, zipOut);
+ hasEntries = true;
+ }
+ if (!hasEntries) {
+ // ZipOutputStream refuses to create a completely empty zip file.
+ ZipUtil.storeEntry("dummy", new byte[0], zipOut);
+ }
+ }
+ }
+
+ /**
+ * Strip any remaining code attributes.
+ *
+ * <p>Most code will already have been removed after parsing, but the bytecode will still
+ * contain e.g. lowered class and instance initializers.
+ */
+ // TODO(cushon): add additional stripping to produce ijar-compatible output,
+ // e.g. removing private members.
+ private static byte[] removeCode(byte[] bytes) {
+ ClassWriter cw = new ClassWriter(0);
+ new ClassReader(bytes)
+ .accept(cw, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
+ return cw.toByteArray();
+ }
+
+ /** Convert string elements of a classpath to {@link Path}s. */
+ private static ImmutableList<Path> asPaths(Iterable<String> classpath) {
+ ImmutableList.Builder<Path> result = ImmutableList.builder();
+ for (String element : classpath) {
+ result.add(Paths.get(element));
+ }
+ return result.build();
+ }
+
+ /** Extract the language level from the javacopts. */
+ @VisibleForTesting
+ static void addLanguageLevel(
+ ImmutableList.Builder<String> javacArgs, Iterable<String> defaultJavacopts) {
+ Iterator<String> it = defaultJavacopts.iterator();
+ while (it.hasNext()) {
+ String curr = it.next();
+ switch (curr) {
+ case "-source":
+ case "-target":
+ if (it.hasNext()) {
+ javacArgs.add(curr);
+ javacArgs.add(it.next());
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /** Extra sources in srcjars to disk. */
+ private static List<String> extractSourceJars(TurbineOptions turbineOptions, Path tmpdir)
+ throws IOException {
+ if (turbineOptions.sourceJars().isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ ArrayList<String> extractedSources = new ArrayList<>();
+ for (String sourceJar : turbineOptions.sourceJars()) {
+ try (ZipFile zf = new ZipFile(sourceJar)) {
+ Enumeration<? extends ZipEntry> entries = zf.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry ze = entries.nextElement();
+ if (!ze.getName().endsWith(".java")) {
+ continue;
+ }
+ Path dest = tmpdir.resolve(ze.getName());
+ Files.createDirectories(dest.getParent());
+ Files.copy(zf.getInputStream(ze), dest);
+ extractedSources.add(dest.toAbsolutePath().toString());
+ }
+ }
+ }
+ return extractedSources;
+ }
+
+ private static final Pattern MISSING_PACKAGE =
+ Pattern.compile("error: package ([\\p{javaJavaIdentifierPart}\\.]+) does not exist");
+
+ /**
+ * The compilation failed with an error that may indicate that the reduced class path was too
+ * aggressive.
+ *
+ * <p>WARNING: keep in sync with ReducedClasspathJavaLibraryBuilder.
+ */
+ // TODO(cushon): use a diagnostic listener and match known codes instead
+ private static boolean hasRecognizedError(String javacOutput) {
+ return javacOutput.contains("error: cannot access")
+ || javacOutput.contains("error: cannot find symbol")
+ || javacOutput.contains("com.sun.tools.javac.code.Symbol$CompletionFailure")
+ || MISSING_PACKAGE.matcher(javacOutput).find();
+ }
+
+ @Override
+ public void close() throws IOException {
+ deleteRecursively(Paths.get(turbineOptions.tempDir()));
+ }
+
+ private static void deleteRecursively(final Path dir) throws IOException {
+ Files.walkFileTree(
+ dir,
+ new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes attrs)
+ throws IOException {
+ Files.delete(path);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path path, IOException exc) throws IOException {
+ if (!path.equals(dir)) {
+ Files.delete(path);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompileRequest.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompileRequest.java
new file mode 100644
index 0000000000..68f99fe9d6
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompileRequest.java
@@ -0,0 +1,117 @@
+// Copyright 2016 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.
+
+package com.google.devtools.build.java.turbine.javac;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.buildjar.javac.plugins.dependency.StrictJavaDepsPlugin;
+
+import java.nio.file.Path;
+
+import javax.annotation.Nullable;
+
+/** The input to a {@link JavacTurbineCompiler} compilation. */
+class JavacTurbineCompileRequest {
+
+ private final ImmutableList<Path> classPath;
+ private final ImmutableList<Path> bootClassPath;
+ private final ImmutableList<Path> processorClassPath;
+ private final ImmutableList<String> javacOptions;
+ @Nullable private final StrictJavaDepsPlugin strictJavaDepsPlugin;
+
+ JavacTurbineCompileRequest(
+ ImmutableList<Path> classPath,
+ ImmutableList<Path> bootClassPath,
+ ImmutableList<Path> processorClassPath,
+ ImmutableList<String> javacOptions,
+ @Nullable StrictJavaDepsPlugin strictJavaDepsPlugin) {
+ this.classPath = classPath;
+ this.bootClassPath = bootClassPath;
+ this.processorClassPath = processorClassPath;
+ this.javacOptions = javacOptions;
+ this.strictJavaDepsPlugin = strictJavaDepsPlugin;
+ }
+
+ /** The class path; correspond's to javac -classpath. */
+ ImmutableList<Path> classPath() {
+ return classPath;
+ }
+
+ /** The boot class path; corresponds to javac -bootclasspath. */
+ ImmutableList<Path> bootClassPath() {
+ return bootClassPath;
+ }
+
+ /** The class path to search for processors; corresponds to javac -processorpath. */
+ ImmutableList<Path> processorClassPath() {
+ return processorClassPath;
+ }
+
+ /** Miscellaneous javac options. */
+ ImmutableList<String> javacOptions() {
+ return javacOptions;
+ }
+
+ /**
+ * The build's {@link StrictJavaDepsPlugin}, or {@code null} if Strict Java Deps is not enabled.
+ */
+ @Nullable
+ StrictJavaDepsPlugin strictJavaDepsPlugin() {
+ return strictJavaDepsPlugin;
+ }
+
+ static JavacTurbineCompileRequest.Builder builder() {
+ return new Builder();
+ }
+
+ static class Builder {
+ private ImmutableList<Path> classPath;
+ private ImmutableList<Path> bootClassPath;
+ private ImmutableList<Path> processorClassPath;
+ private ImmutableList<String> javacOptions;
+ @Nullable private StrictJavaDepsPlugin strictDepsPlugin;
+
+ private Builder() {}
+
+ JavacTurbineCompileRequest build() {
+ return new JavacTurbineCompileRequest(
+ classPath, bootClassPath, processorClassPath, javacOptions, strictDepsPlugin);
+ }
+
+ Builder setClassPath(ImmutableList<Path> classPath) {
+ this.classPath = classPath;
+ return this;
+ }
+
+ Builder setBootClassPath(ImmutableList<Path> bootClassPath) {
+ this.bootClassPath = bootClassPath;
+ return this;
+ }
+
+ Builder setProcessorClassPath(ImmutableList<Path> processorClassPath) {
+ this.processorClassPath = processorClassPath;
+ return this;
+ }
+
+ Builder setJavacOptions(ImmutableList<String> javacOptions) {
+ this.javacOptions = javacOptions;
+ return this;
+ }
+
+ Builder setStrictDepsPlugin(@Nullable StrictJavaDepsPlugin strictDepsPlugin) {
+ this.strictDepsPlugin = strictDepsPlugin;
+ return this;
+ }
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompileResult.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompileResult.java
new file mode 100644
index 0000000000..0d034316c7
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompileResult.java
@@ -0,0 +1,64 @@
+// Copyright 2016 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.
+
+package com.google.devtools.build.java.turbine.javac;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.java.turbine.javac.ZipOutputFileManager.OutputFileObject;
+
+import com.sun.tools.javac.util.Context;
+
+import java.io.StringWriter;
+
+/** The output from a {@link JavacTurbineCompiler} compilation. */
+class JavacTurbineCompileResult {
+
+ private final ImmutableMap<String, OutputFileObject> files;
+ private final boolean success;
+ private final StringWriter sb;
+ private final Context context;
+
+ JavacTurbineCompileResult(
+ ImmutableMap<String, OutputFileObject> files,
+ boolean success,
+ StringWriter sb,
+ Context context) {
+ this.files = files;
+ this.success = success;
+ this.sb = sb;
+ this.context = context;
+ }
+
+ /** True iff the compilation succeeded. */
+ boolean success() {
+ return success;
+ }
+
+ /** The stderr from the compilation. */
+ String output() {
+ return sb.toString();
+ }
+
+ /** The files produced by the compilation's {@link ZipOutputFileManager}. */
+ ImmutableMap<String, OutputFileObject> files() {
+ return files;
+ }
+
+ /** The compilation context, may by inspected by integration tests. */
+ @VisibleForTesting
+ Context context() {
+ return context;
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompiler.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompiler.java
new file mode 100644
index 0000000000..af697a833d
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineCompiler.java
@@ -0,0 +1,93 @@
+// Copyright 2016 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.
+
+package com.google.devtools.build.java.turbine.javac;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.buildjar.javac.plugins.dependency.StrictJavaDepsPlugin;
+import com.google.devtools.build.java.turbine.javac.ZipOutputFileManager.OutputFileObject;
+
+import com.sun.tools.javac.file.CacheFSInfo;
+import com.sun.tools.javac.main.Arguments;
+import com.sun.tools.javac.main.CommandLine;
+import com.sun.tools.javac.main.JavaCompiler;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Log;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+import javax.tools.JavaFileManager;
+import javax.tools.StandardLocation;
+
+/** Performs a javac-based turbine compilation given a {@link JavacTurbineCompileRequest}. */
+public class JavacTurbineCompiler {
+
+ static JavacTurbineCompileResult compile(JavacTurbineCompileRequest request) throws IOException {
+
+ Map<String, OutputFileObject> files = new LinkedHashMap<>();
+ boolean success;
+ StringWriter sw = new StringWriter();
+ Context context = new Context();
+
+ try (PrintWriter pw = new PrintWriter(sw)) {
+ ZipOutputFileManager.preRegister(context, files);
+ setupContext(context, request.strictJavaDepsPlugin());
+ CacheFSInfo.preRegister(context);
+
+ context.put(Log.outKey, pw);
+
+ ZipOutputFileManager fm = (ZipOutputFileManager) context.get(JavaFileManager.class);
+ fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, Collections.<Path>emptyList());
+ fm.setLocationFromPaths(StandardLocation.CLASS_PATH, request.classPath());
+ fm.setLocationFromPaths(StandardLocation.PLATFORM_CLASS_PATH, request.bootClassPath());
+ fm.setLocationFromPaths(
+ StandardLocation.ANNOTATION_PROCESSOR_PATH, request.processorClassPath());
+
+ String[] javacArgArray = request.javacOptions().toArray(new String[0]);
+ javacArgArray = CommandLine.parse(javacArgArray);
+
+ Arguments args = Arguments.instance(context);
+ args.init("turbine", javacArgArray);
+
+ fm.setContext(context);
+ fm.handleOptions(args.getDeferredFileManagerOptions());
+
+ JavaCompiler comp = JavaCompiler.instance(context);
+ if (request.strictJavaDepsPlugin() != null) {
+ request.strictJavaDepsPlugin().init(context, Log.instance(context), comp);
+ }
+
+ try {
+ comp.compile(args.getFileObjects(), args.getClassNames(), null);
+ success = comp.errorCount() == 0;
+ } catch (Throwable t) {
+ t.printStackTrace(pw);
+ success = false;
+ }
+ }
+
+ return new JavacTurbineCompileResult(ImmutableMap.copyOf(files), success, sw, context);
+ }
+
+ static void setupContext(Context context, @Nullable StrictJavaDepsPlugin sjd) {
+ JavacTurbineJavaCompiler.preRegister(context, sjd);
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineJavaCompiler.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineJavaCompiler.java
new file mode 100644
index 0000000000..874b1811a4
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/JavacTurbineJavaCompiler.java
@@ -0,0 +1,96 @@
+// Copyright 2016 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.
+
+package com.google.devtools.build.java.turbine.javac;
+
+import com.google.devtools.build.buildjar.javac.plugins.dependency.StrictJavaDepsPlugin;
+
+import com.sun.tools.javac.comp.AttrContext;
+import com.sun.tools.javac.comp.CompileStates.CompileState;
+import com.sun.tools.javac.comp.Env;
+import com.sun.tools.javac.main.JavaCompiler;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.util.Context;
+
+import java.util.Queue;
+
+import javax.annotation.Nullable;
+import javax.tools.JavaFileObject;
+
+/**
+ * A {@link JavaCompiler} that drops method bodies and top-level blocks after parsing, and runs
+ * Strict Java Deps.
+ *
+ * <p>The information dropped from the AST improves compilation performance and has no effect
+ * on the output of header compilation.
+ */
+class JavacTurbineJavaCompiler extends JavaCompiler implements AutoCloseable {
+
+ @Nullable private final StrictJavaDepsPlugin strictJavaDeps;
+
+ public JavacTurbineJavaCompiler(Context context, @Nullable StrictJavaDepsPlugin strictJavaDeps) {
+ super(context);
+ this.strictJavaDeps = strictJavaDeps;
+ }
+
+ @Override
+ protected JCCompilationUnit parse(JavaFileObject javaFileObject, CharSequence charSequence) {
+ JCCompilationUnit result = super.parse(javaFileObject, charSequence);
+ TreePruner.prune(result);
+ return result;
+ }
+
+ @Override
+ public Env<AttrContext> attribute(Env<AttrContext> env) {
+ if (compileStates.isDone(env, CompileState.ATTR)) {
+ return env;
+ }
+ Env<AttrContext> result = super.attribute(env);
+ if (strictJavaDeps != null) {
+ strictJavaDeps.postAttribute(result);
+ }
+ return result;
+ }
+
+ @Override
+ protected void flow(Env<AttrContext> env, Queue<Env<AttrContext>> results) {
+ // skip FLOW (as if -relax was enabled, except -relax is broken for JDK >= 8)
+ if (!compileStates.isDone(env, CompileState.FLOW)) {
+ compileStates.put(env, CompileState.FLOW);
+ }
+ results.add(env);
+ }
+
+ @Override
+ public void close() {
+ if (strictJavaDeps != null) {
+ strictJavaDeps.finish();
+ }
+ }
+
+ /**
+ * Override the default {@link JavaCompiler} implementation with {@link JavacTurbineJavaCompiler}
+ * for the given compilation context.
+ */
+ public static void preRegister(Context context, @Nullable final StrictJavaDepsPlugin sjd) {
+ context.put(
+ compilerKey,
+ new Context.Factory<JavaCompiler>() {
+ @Override
+ public JavaCompiler make(Context c) {
+ return new JavacTurbineJavaCompiler(c, sjd);
+ }
+ });
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/TreePruner.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/TreePruner.java
new file mode 100644
index 0000000000..da748442d5
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/TreePruner.java
@@ -0,0 +1,231 @@
+// Copyright 2016 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.
+
+package com.google.devtools.build.java.turbine.javac;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+
+import com.sun.source.tree.BinaryTree;
+import com.sun.source.tree.ConditionalExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.LiteralTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.ParenthesizedTree;
+import com.sun.source.tree.TypeCastTree;
+import com.sun.source.tree.UnaryTree;
+import com.sun.source.util.SimpleTreeVisitor;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.TreeScanner;
+import com.sun.tools.javac.util.List;
+
+/**
+ * Prunes AST nodes that are not required for header compilation.
+ *
+ * <p>Used by Turbine after parsing and before all subsequent phases to avoid
+ * doing unnecessary work.
+ */
+public class TreePruner {
+
+ /**
+ * Prunes AST nodes that are not required for header compilation.
+ *
+ * <p>Specifically:
+ *
+ * <ul>
+ * <li>method bodies
+ * <li>class and instance initializer blocks
+ * <li>initializers of definitely non-constant fields
+ * </ul>
+ */
+ static void prune(JCTree tree) {
+ tree.accept(PRUNING_VISITOR);
+ }
+
+ /** A {@link TreeScanner} that deletes method bodies and blocks from the AST. */
+ private static final TreeScanner PRUNING_VISITOR =
+ new TreeScanner() {
+
+ @Override
+ public void visitMethodDef(JCMethodDecl tree) {
+ if (tree.body == null) {
+ return;
+ }
+ tree.body.stats = com.sun.tools.javac.util.List.nil();
+ }
+
+ @Override
+ public void visitBlock(JCBlock tree) {
+ tree.stats = List.nil();
+ }
+
+ @Override
+ public void visitVarDef(JCVariableDecl tree) {
+ if ((tree.mods.flags & Flags.ENUM) == Flags.ENUM) {
+ // javac desugars enum constants into fields during parsing
+ return;
+ }
+ // drop field initializers unless the field looks like a JLS §4.12.4 constant variable
+ if (isConstantVariable(tree)) {
+ return;
+ }
+ tree.init = null;
+ }
+ };
+
+ private static boolean isConstantVariable(JCVariableDecl tree) {
+ if ((tree.mods.flags & Flags.FINAL) != Flags.FINAL) {
+ return false;
+ }
+ if (!constantType(tree.getType())) {
+ return false;
+ }
+ if (tree.getInitializer() != null) {
+ Boolean result = tree.getInitializer().accept(CONSTANT_VISITOR, null);
+ if (result == null || !result) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns true iff the given tree could be the type name of a constant type.
+ *
+ * <p>This is a conservative over-approximation: an identifier named {@code String}
+ * isn't necessarily a type name, but this is used at parse-time before types have
+ * been attributed.
+ */
+ private static boolean constantType(JCTree tree) {
+ switch (tree.getKind()) {
+ case PRIMITIVE_TYPE:
+ return true;
+ case IDENTIFIER:
+ return tree.toString().contentEquals("String");
+ case MEMBER_SELECT:
+ return tree.toString().contentEquals("java.lang.String");
+ default:
+ return false;
+ }
+ }
+
+ /** A visitor that identifies JLS §15.28 constant expressions. */
+ private static final SimpleTreeVisitor<Boolean, Void> CONSTANT_VISITOR =
+ new SimpleTreeVisitor<Boolean, Void>(false) {
+
+ @Override
+ public Boolean visitConditionalExpression(ConditionalExpressionTree node, Void p) {
+ return reduce(
+ node.getCondition().accept(this, null),
+ node.getTrueExpression().accept(this, null),
+ node.getFalseExpression().accept(this, null));
+ }
+
+ @Override
+ public Boolean visitParenthesized(ParenthesizedTree node, Void p) {
+ return node.getExpression().accept(this, null);
+ }
+
+ @Override
+ public Boolean visitUnary(UnaryTree node, Void p) {
+ switch (node.getKind()) {
+ case UNARY_PLUS:
+ case UNARY_MINUS:
+ case BITWISE_COMPLEMENT:
+ case LOGICAL_COMPLEMENT:
+ break;
+ default:
+ // non-constant unary expression
+ return false;
+ }
+ return node.getExpression().accept(this, null);
+ }
+
+ @Override
+ public Boolean visitBinary(BinaryTree node, Void p) {
+ switch (node.getKind()) {
+ case MULTIPLY:
+ case DIVIDE:
+ case REMAINDER:
+ case PLUS:
+ case MINUS:
+ case LEFT_SHIFT:
+ case RIGHT_SHIFT:
+ case UNSIGNED_RIGHT_SHIFT:
+ case LESS_THAN:
+ case LESS_THAN_EQUAL:
+ case GREATER_THAN:
+ case GREATER_THAN_EQUAL:
+ case AND:
+ case XOR:
+ case OR:
+ case CONDITIONAL_AND:
+ case CONDITIONAL_OR:
+ case EQUAL_TO:
+ case NOT_EQUAL_TO:
+ break;
+ default:
+ // non-constant binary expression
+ return false;
+ }
+ return reduce(
+ node.getLeftOperand().accept(this, null), node.getRightOperand().accept(this, null));
+ }
+
+ @Override
+ public Boolean visitTypeCast(TypeCastTree node, Void p) {
+ return reduce(
+ constantType((JCTree) node.getType()), node.getExpression().accept(this, null));
+ }
+
+ @Override
+ public Boolean visitMemberSelect(MemberSelectTree node, Void p) {
+ return node.getExpression().accept(this, null);
+ }
+
+ @Override
+ public Boolean visitIdentifier(IdentifierTree node, Void p) {
+ // Assume all variables are constant variables. This is a conservative assumption, but
+ // it's the best we can do with only syntactic information.
+ return true;
+ }
+
+ @Override
+ public Boolean visitLiteral(LiteralTree node, Void unused) {
+ switch (node.getKind()) {
+ case STRING_LITERAL:
+ case INT_LITERAL:
+ case LONG_LITERAL:
+ case FLOAT_LITERAL:
+ case DOUBLE_LITERAL:
+ case BOOLEAN_LITERAL:
+ case CHAR_LITERAL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public boolean reduce(Boolean... bx) {
+ boolean r = true;
+ for (Boolean b : bx) {
+ r &= firstNonNull(b, false);
+ }
+ return r;
+ }
+ };
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/ZipOutputFileManager.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/ZipOutputFileManager.java
new file mode 100644
index 0000000000..f2410afdba
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/ZipOutputFileManager.java
@@ -0,0 +1,161 @@
+// Copyright 2016 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.
+
+package com.google.devtools.build.java.turbine.javac;
+
+import com.sun.tools.javac.api.ClientCodeWrapper.Trusted;
+import com.sun.tools.javac.file.JavacFileManager;
+import com.sun.tools.javac.util.Context;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import javax.tools.FileObject;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+
+/** A {@link JavacFileManager} that collects output into a zipfile. */
+@Trusted
+public class ZipOutputFileManager extends JavacFileManager {
+
+ private final Map<String, OutputFileObject> files;
+
+ public ZipOutputFileManager(Context context, Map<String, OutputFileObject> files) {
+ super(context, true, StandardCharsets.UTF_8);
+ this.files = files;
+ }
+
+ /**
+ * Returns true if the file manager owns this location; otherwise it delegates to the underlying
+ * implementation.
+ */
+ private boolean ownedLocation(Location location) {
+ return location.isOutputLocation();
+ }
+
+ @Override
+ public boolean hasLocation(Location location) {
+ return ownedLocation(location) || super.hasLocation(location);
+ }
+
+ private OutputFileObject getOutput(String name, JavaFileObject.Kind kind, Location location) {
+ if (files.containsKey(name)) {
+ return files.get(name);
+ }
+ OutputFileObject result = new OutputFileObject(name, kind, location);
+ files.put(name, result);
+ return result;
+ }
+
+ @Override
+ public JavaFileObject getJavaFileForOutput(
+ Location location, String className, JavaFileObject.Kind kind, FileObject sibling)
+ throws IOException {
+ if (!ownedLocation(location)) {
+ return super.getJavaFileForOutput(location, className, kind, sibling);
+ }
+ // The classname parameter will be something like
+ // "com.google.common.base.Flag$String"; nested classes are delimited with
+ // dollar signs, so the following transformation works as intended.
+ return getOutput(className.replace('.', '/') + kind.extension, kind, location);
+ }
+
+ @Override
+ public FileObject getFileForOutput(
+ Location location, String packageName, String relativeName, FileObject sibling)
+ throws IOException {
+ if (!ownedLocation(location)) {
+ return super.getFileForOutput(location, packageName, relativeName, sibling);
+ }
+ String path = "";
+ if (packageName != null && !packageName.isEmpty()) {
+ path = packageName.replace('.', '/') + '/';
+ }
+ path += relativeName;
+ return getOutput(path, JavaFileObject.Kind.OTHER, location);
+ }
+
+ @Override
+ public boolean isSameFile(FileObject a, FileObject b) {
+ boolean at = a instanceof OutputFileObject;
+ boolean bt = b instanceof OutputFileObject;
+ if (at || bt) {
+ if (at ^ bt) {
+ return false;
+ }
+ return ((OutputFileObject) a).toUri().equals(((OutputFileObject) b).toUri());
+ }
+ return super.isSameFile(a, b);
+ }
+
+ /** A {@link JavaFileObject} that accumulates output in memory. */
+ public static class OutputFileObject extends SimpleJavaFileObject {
+
+ public final Location location;
+
+ private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ public OutputFileObject(String name, Kind kind, Location location) {
+ super(URI.create("outputbuffer://" + name), kind);
+ this.location = location;
+ }
+
+ @Override
+ public OutputStream openOutputStream() {
+ return buffer;
+ }
+
+ @Override
+ public InputStream openInputStream() throws IOException {
+ return new ByteArrayInputStream(asBytes());
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ CodingErrorAction errorAction =
+ ignoreEncodingErrors ? CodingErrorAction.IGNORE : CodingErrorAction.REPORT;
+ CharsetDecoder decoder =
+ StandardCharsets.UTF_8
+ .newDecoder()
+ .onUnmappableCharacter(errorAction)
+ .onMalformedInput(errorAction);
+ return decoder.decode(ByteBuffer.wrap(asBytes()));
+ }
+
+ public byte[] asBytes() {
+ return buffer.toByteArray();
+ }
+ }
+
+ public static void preRegister(Context context, final Map<String, OutputFileObject> files) {
+ context.put(
+ JavaFileManager.class,
+ new Context.Factory<JavaFileManager>() {
+ @Override
+ public JavaFileManager make(Context c) {
+ return new ZipOutputFileManager(c, files);
+ }
+ });
+ }
+}
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/ZipUtil.java b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/ZipUtil.java
new file mode 100644
index 0000000000..a4e7d7e033
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/java/turbine/javac/ZipUtil.java
@@ -0,0 +1,53 @@
+// Copyright 2016 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.
+
+package com.google.devtools.build.java.turbine.javac;
+
+import java.io.IOException;
+import java.util.GregorianCalendar;
+import java.util.zip.CRC32;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Static utility methods for working with ZipOutputStream.
+ */
+public abstract class ZipUtil {
+
+ /** The earliest date representable in a zip file, the DOS epoch. */
+ private static final long DOS_EPOCH =
+ new GregorianCalendar(1980, 0, 1, 0, 0, 0).getTimeInMillis();
+
+ /**
+ * This is a helper method for adding an uncompressed entry to a
+ * ZipOutputStream. The entry timestamp is also set to a fixed value.
+ *
+ * @param name filename to use within the zip file
+ * @param content file contents
+ * @param zip the ZipOutputStream to which this entry will be appended
+ */
+ public static void storeEntry(String name, byte[] content, ZipOutputStream zip)
+ throws IOException {
+ ZipEntry entry = new ZipEntry(name);
+ entry.setMethod(ZipEntry.STORED);
+ entry.setTime(DOS_EPOCH);
+ entry.setSize(content.length);
+ CRC32 crc32 = new CRC32();
+ crc32.update(content);
+ entry.setCrc(crc32.getValue());
+ zip.putNextEntry(entry);
+ zip.write(content);
+ zip.closeEntry();
+ }
+}