| // 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.javac; |
| |
| import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin; |
| import com.google.devtools.build.buildjar.javac.statistics.BlazeJavacStatistics; |
| 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.util.Context; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Queue; |
| |
| /** |
| * An extended version of the javac compiler, providing support for composable static analyses via a |
| * plugin mechanism. BlazeJavaCompiler keeps a list of plugins and calls callback methods in those |
| * plugins after certain compiler phases. The plugins perform the actual static analyses. |
| */ |
| public class BlazeJavaCompiler extends JavaCompiler { |
| |
| private int skippedFlowEvents = 0; |
| private int flowEvents = 0; |
| |
| /** A list of plugins to run at particular points in the compile */ |
| private final List<BlazeJavaCompilerPlugin> plugins = new ArrayList<>(); |
| |
| private BlazeJavaCompiler(Context context, Iterable<BlazeJavaCompilerPlugin> plugins) { |
| super(context); |
| |
| BlazeJavacStatistics.Builder statisticsBuilder = |
| context.get(BlazeJavacStatistics.Builder.class); |
| // initialize all plugins |
| for (BlazeJavaCompilerPlugin plugin : plugins) { |
| plugin.init(context, log, this, statisticsBuilder); |
| this.plugins.add(plugin); |
| } |
| } |
| |
| /** |
| * Adds an initialization hook to the Context, such that requests for a JavaCompiler (i.e., a |
| * lookup for 'compilerKey' of our superclass, JavaCompiler) will actually construct and return |
| * BlazeJavaCompiler. |
| * |
| * <p>This is the preferred way for extending behavior within javac, per the documentation in |
| * {@link Context}. |
| * |
| * <p>Prior to JDK-8038455 additional JavaCompilers were created for annotation processing rounds, |
| * but we now expect a single compiler instance per compilation. The factory is still seems to be |
| * necessary to avoid context-ordering issues, but we assert that the factory is only called once, |
| * and save the output after its call for introspection. |
| */ |
| public static void preRegister( |
| final Context context, final Iterable<BlazeJavaCompilerPlugin> plugins) { |
| context.put( |
| compilerKey, |
| new Context.Factory<JavaCompiler>() { |
| boolean first = true; |
| |
| @Override |
| public JavaCompiler make(Context c) { |
| if (!first) { |
| throw new AssertionError("Expected a single creation of BlazeJavaCompiler."); |
| } |
| first = false; |
| return new BlazeJavaCompiler(c, plugins); |
| } |
| }); |
| } |
| |
| @Override |
| public Env<AttrContext> attribute(Env<AttrContext> env) { |
| Env<AttrContext> result = super.attribute(env); |
| // don't run plugins if there were compilation errors |
| boolean errors = errorCount() > 0; |
| // Iterate over all plugins, calling their postAttribute methods |
| for (BlazeJavaCompilerPlugin plugin : plugins) { |
| if (!errors || plugin.runOnAttributionErrors()) { |
| plugin.postAttribute(result); |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| protected void flow(Env<AttrContext> env, Queue<Env<AttrContext>> results) { |
| boolean isDone = compileStates.isDone(env, CompileState.FLOW); |
| super.flow(env, results); |
| if (isDone) { |
| return; |
| } |
| // don't run plugins if there were compilation errors |
| if (errorCount() > 0) { |
| skippedFlowEvents++; |
| return; |
| } |
| flowEvents++; |
| |
| // Iterate over all plugins, calling their postFlow methods |
| for (BlazeJavaCompilerPlugin plugin : plugins) { |
| plugin.postFlow(env); |
| } |
| } |
| |
| @Override |
| public void close() { |
| for (BlazeJavaCompilerPlugin plugin : plugins) { |
| plugin.finish(); |
| } |
| plugins.clear(); |
| super.close(); |
| } |
| |
| /** The number of flow events that were skipped. */ |
| public int skippedFlowEvents() { |
| return skippedFlowEvents; |
| } |
| |
| /** The number of error-free flow events that were passed to plugins. */ |
| public int flowEvents() { |
| return flowEvents; |
| } |
| |
| /** |
| * Testing purposes only. Returns true if the collection of plugins in this instance contains one |
| * of the provided type. |
| */ |
| boolean pluginsContain(Class<? extends BlazeJavaCompilerPlugin> klass) { |
| for (BlazeJavaCompilerPlugin plugin : plugins) { |
| if (klass.isInstance(plugin)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |