aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/AbstractJavaBuilder.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/java_tools/buildjar/java/com/google/devtools/build/buildjar/AbstractJavaBuilder.java')
-rw-r--r--src/java_tools/buildjar/java/com/google/devtools/build/buildjar/AbstractJavaBuilder.java268
1 files changed, 268 insertions, 0 deletions
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/AbstractJavaBuilder.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/AbstractJavaBuilder.java
new file mode 100644
index 0000000000..15349a1789
--- /dev/null
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/AbstractJavaBuilder.java
@@ -0,0 +1,268 @@
+// Copyright 2014 Google Inc. 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.buildjar;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.io.Files;
+import com.google.devtools.build.buildjar.javac.JavacRunner;
+import com.google.devtools.build.buildjar.javac.JavacRunnerImpl;
+
+import com.sun.tools.javac.main.Main.Result;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.zip.ZipEntry;
+
+/**
+ * A command line interface to compile a java_library rule using in-process
+ * javac. This allows us to spawn multiple java_library compilations on a
+ * single machine or distribute Java compilations to multiple machines.
+ */
+public abstract class AbstractJavaBuilder extends AbstractLibraryBuilder {
+
+ /** The name of the protobuf meta file. */
+ private static final String PROTOBUF_META_NAME = "protobuf.meta";
+
+ /** Enables more verbose output from the compiler. */
+ protected boolean debug = false;
+
+ @Override
+ protected boolean keepFileDuringCleanup(File file) {
+ return false;
+ }
+
+ /**
+ * Flush the buffers of this JavaBuilder
+ */
+ @SuppressWarnings("unused") // IOException
+ public synchronized void flush(OutputStream err) throws IOException {
+ }
+
+ /**
+ * Shut this JavaBuilder down
+ */
+ @SuppressWarnings("unused") // IOException
+ public synchronized void shutdown(OutputStream err) throws IOException {
+ }
+
+ /**
+ * Prepares a compilation run and sets everything up so that the source files
+ * in the build request can be compiled. Invokes compileSources to do the
+ * actual compilation.
+ *
+ * @param build A JavaLibraryBuildRequest request object describing what to
+ * compile
+ * @param err PrintWriter for logging any diagnostic output
+ */
+ public void compileJavaLibrary(final JavaLibraryBuildRequest build, final OutputStream err)
+ throws IOException {
+ prepareSourceCompilation(build);
+
+ final String[] message = { null };
+ final JavacRunner javacRunner = new JavacRunnerImpl(build.getPlugins());
+ runWithLargeStack(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ internalCompileJavaLibrary(build, javacRunner, err);
+ } catch (JavacException e) {
+ message[0] = e.getMessage();
+ } catch (Exception e) {
+ // Some exceptions have a null message, yet the stack trace is useful
+ e.printStackTrace();
+ message[0] = "java compilation threw exception: " + e.getMessage();
+ }
+ }
+ }, 4L * 1024 * 1024); // 4MB stack
+
+ if (message[0] != null) {
+ throw new IOException("Error compiling java source: " + message[0]);
+ }
+ }
+
+ /**
+ * Compiles the java files of the java library specified in the build request.<p>
+ * The compilation consists of two parts:<p>
+ * First, javac is invoked directly to compile the java files in the build request.<p>
+ * Second, additional processing is done to the .class files that came out of the compile.<p>
+ *
+ * @param build A JavaLibraryBuildRequest request object describing what to compile
+ * @param err OutputStream for logging any diagnostic output
+ */
+ private void internalCompileJavaLibrary(JavaLibraryBuildRequest build, JavacRunner javacRunner,
+ OutputStream err) throws IOException, JavacException {
+ // result may not be null, in case somebody changes the set of source files
+ // to the empty set
+ Result result = Result.OK;
+ if (!build.getSourceFiles().isEmpty()) {
+ PrintWriter javacErrorOutputWriter = new PrintWriter(err);
+ try {
+ result = compileSources(build, javacRunner, javacErrorOutputWriter);
+ } finally {
+ javacErrorOutputWriter.flush();
+ }
+ }
+
+ if (!result.isOK()) {
+ throw new JavacException(result);
+ }
+ runClassPostProcessing(build);
+ }
+
+ /**
+ * Build a jar file containing source files that were generated by an annotation processor.
+ */
+ public abstract void buildGensrcJar(JavaLibraryBuildRequest build, OutputStream err)
+ throws IOException;
+
+ @VisibleForTesting
+ protected void runClassPostProcessing(JavaLibraryBuildRequest build)
+ throws IOException {
+ for (AbstractPostProcessor postProcessor : build.getPostProcessors()) {
+ postProcessor.initialize(build);
+ postProcessor.processRequest();
+ }
+ }
+
+ /**
+ * Compiles the java files specified in 'JavaLibraryBuildRequest'.
+ * Implementations can try to avoid recompiling the java files. Whenever
+ * this function is invoked, it is guaranteed that the build request
+ * contains files to compile.
+ *
+ * @param build A JavaLibraryBuildRequest request object describing what to
+ * compile
+ * @param err PrintWriter for logging any diagnostic output
+ * @return the exit status of the java compiler.
+ */
+ abstract Result compileSources(JavaLibraryBuildRequest build, JavacRunner javacRunner,
+ PrintWriter err) throws IOException;
+
+ /**
+ * Perform the build.
+ */
+ public void run(JavaLibraryBuildRequest build, PrintStream err)
+ throws IOException {
+ boolean successful = false;
+ try {
+ compileJavaLibrary(build, err);
+ buildJar(build);
+ if (!build.getProcessors().isEmpty()) {
+ if (build.getGeneratedSourcesOutputJar() != null) {
+ buildGensrcJar(build, err);
+ }
+ }
+ successful = true;
+ } finally {
+ build.getDependencyModule().emitUsedClasspath(build.getClassPath());
+ build.getDependencyModule().emitDependencyInformation(build.getClassPath(), successful);
+ shutdown(err);
+ }
+ }
+
+ // Utility functions
+
+ /**
+ * Runs "run" in another thread (whose lifetime is contained within the
+ * activation of this function call) using a stack size of 'stackSize' bytes.
+ * Unchecked exceptions thrown by the Runnable will be re-thrown in the main
+ * thread.
+ */
+ private static void runWithLargeStack(final Runnable run, long stackSize) {
+ final Throwable[] unchecked = { null };
+ Thread t = new Thread(null, run, "runWithLargeStack", stackSize);
+ t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread t, Throwable e) {
+ unchecked[0] = e;
+ }
+ });
+ t.start();
+ boolean wasInterrupted = false;
+ for (;;) {
+ try {
+ t.join(0);
+ break;
+ } catch (InterruptedException e) {
+ wasInterrupted = true;
+ }
+ }
+ if (wasInterrupted) {
+ Thread.currentThread().interrupt();
+ }
+ if (unchecked[0] instanceof Error) {
+ throw (Error) unchecked[0];
+ } else if (unchecked[0] instanceof RuntimeException) {
+ throw (RuntimeException) unchecked[0];
+ }
+ }
+
+ /**
+ * A SourceJarEntryListener that collects protobuf meta data files from the
+ * source jar files.
+ */
+ private static class ProtoMetaFileCollector implements SourceJarEntryListener {
+
+ private final String sourceDir;
+ private final String outputDir;
+ private final ByteArrayOutputStream buffer;
+
+ public ProtoMetaFileCollector(String sourceDir, String outputDir) {
+ this.sourceDir = sourceDir;
+ this.outputDir = outputDir;
+ this.buffer = new ByteArrayOutputStream();
+ }
+
+ @Override
+ public void onEntry(ZipEntry entry) throws IOException {
+ String entryName = entry.getName();
+ if (!entryName.equals(PROTOBUF_META_NAME)) {
+ return;
+ }
+ Files.copy(new File(sourceDir, PROTOBUF_META_NAME), buffer);
+ }
+
+ /**
+ * Writes the combined the meta files into the output directory. Delete the
+ * stalling meta file if no meta file is collected.
+ */
+ @Override
+ public void finish() throws IOException {
+ File outputFile = new File(outputDir, PROTOBUF_META_NAME);
+ if (buffer.size() > 0) {
+ try (OutputStream outputStream = new FileOutputStream(outputFile)) {
+ buffer.writeTo(outputStream);
+ }
+ } else if (outputFile.exists()) {
+ // Delete stalled meta file.
+ outputFile.delete();
+ }
+ }
+ }
+
+ @Override
+ protected List<SourceJarEntryListener> getSourceJarEntryListeners(
+ JavaLibraryBuildRequest build) {
+ List<SourceJarEntryListener> result = super.getSourceJarEntryListeners(build);
+ result.add(new ProtoMetaFileCollector(
+ build.getTempDir(), build.getClassDir()));
+ return result;
+ }
+}