aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/AbstractJavaBuilder.java
blob: 35f50a4d94386d0dc6335fb8e11cd4da23f41a7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
// Copyright 2014 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.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;

  /**
   * 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 Exception {
    prepareSourceCompilation(build);

    final Exception[] exception = {null};
    final JavacRunner javacRunner = new JavacRunnerImpl(build.getPlugins());
    runWithLargeStack(
        new Runnable() {
          @Override
          public void run() {
            try {
              internalCompileJavaLibrary(build, javacRunner, err);
            } catch (Exception e) {
              exception[0] = e;
            }
          }
        },
        4L * 1024 * 1024); // 4MB stack

    if (exception[0] != null) {
      throw exception[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 Exception {
    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);
      build.getProcessingModule().emitManifestProto();
      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;
  }
}