aboutsummaryrefslogtreecommitdiff
path: root/java/com/google/turbine/binder/Processing.java
diff options
context:
space:
mode:
authorcushon <cushon@google.com>2019-08-06 17:31:56 -0700
committerLiam Miller-Cushon <cushon@google.com>2019-08-07 17:55:06 -0700
commit561712b6f28380afecae63722dfd40c57c21f5e4 (patch)
tree12c51929a4e5321c0627d22f6b5ead01272f835b /java/com/google/turbine/binder/Processing.java
parent84fccce2f01e0bc5b2c32653c816f8368e6ef575 (diff)
downloadturbine-561712b6f28380afecae63722dfd40c57c21f5e4.tar.gz
Add support for annotation processing to turbine
This provides an initial implementation of turbine's ProcessingEnvironment and RoundEnvironment, and the integration with the analysis passes in 'Binder'. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=262031530
Diffstat (limited to 'java/com/google/turbine/binder/Processing.java')
-rw-r--r--java/com/google/turbine/binder/Processing.java427
1 files changed, 427 insertions, 0 deletions
diff --git a/java/com/google/turbine/binder/Processing.java b/java/com/google/turbine/binder/Processing.java
new file mode 100644
index 0000000..67ee63a
--- /dev/null
+++ b/java/com/google/turbine/binder/Processing.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2019 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.turbine.binder;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.Sets;
+import com.google.turbine.binder.Binder.BindingResult;
+import com.google.turbine.binder.bound.SourceTypeBoundClass;
+import com.google.turbine.binder.bound.TypeBoundClass;
+import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo;
+import com.google.turbine.binder.env.CompoundEnv;
+import com.google.turbine.binder.env.Env;
+import com.google.turbine.binder.env.SimpleEnv;
+import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.binder.sym.Symbol;
+import com.google.turbine.diag.SourceFile;
+import com.google.turbine.diag.TurbineLog;
+import com.google.turbine.parse.Parser;
+import com.google.turbine.processing.ModelFactory;
+import com.google.turbine.processing.TurbineFiler;
+import com.google.turbine.processing.TurbineProcessingEnvironment;
+import com.google.turbine.processing.TurbineRoundEnvironment;
+import com.google.turbine.tree.Tree.CompUnit;
+import com.google.turbine.type.AnnoInfo;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.regex.Pattern;
+import javax.annotation.processing.Processor;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/** Top level annotation processing logic, see also {@link Binder}. */
+public class Processing {
+
+ static BindingResult process(
+ TurbineLog log,
+ final List<CompUnit> initialSources,
+ final ClassPath classpath,
+ ProcessorInfo processorInfo,
+ ClassPath bootclasspath,
+ BindingResult result,
+ Optional<String> moduleVersion) {
+
+ Set<String> seen = new HashSet<>();
+ for (CompUnit u : initialSources) {
+ if (u.source() != null) {
+ seen.add(u.source().path());
+ }
+ }
+
+ TurbineFiler filer =
+ new TurbineFiler(
+ seen,
+ new Function<String, Supplier<byte[]>>() {
+ @Nullable
+ @Override
+ public Supplier<byte[]> apply(@Nullable String input) {
+ // TODO(cushon): should annotation processors be allowed to generate code with
+ // dependencies between source and bytecode, or vice versa?
+ // Currently generated classes are not available on the classpath when compiling
+ // the compilation sources (including generated sources).
+ return classpath.resource(input);
+ }
+ },
+ processorInfo.loader());
+
+ Env<ClassSymbol, SourceTypeBoundClass> tenv = new SimpleEnv<>(result.units());
+ CompoundEnv<ClassSymbol, TypeBoundClass> env =
+ CompoundEnv.<ClassSymbol, TypeBoundClass>of(result.classPathEnv()).append(tenv);
+ ModelFactory factory = new ModelFactory(env, processorInfo.loader(), result.tli());
+
+ for (Processor processor : processorInfo.processors()) {
+ processor.init(
+ new TurbineProcessingEnvironment(
+ factory, filer, log, processorInfo.options(), processorInfo.sourceVersion()));
+ }
+
+ Map<Processor, Pattern> wanted = new HashMap<>();
+ for (Processor processor : processorInfo.processors()) {
+ List<String> patterns = new ArrayList<>();
+ for (String supportedAnnotationType : processor.getSupportedAnnotationTypes()) {
+ // TODO(b/139026291): this handling of getSupportedAnnotationTypes isn't correct
+ patterns.add(supportedAnnotationType.replace("*", ".*"));
+ }
+ wanted.put(processor, Pattern.compile(Joiner.on('|').join(patterns)));
+ }
+
+ Set<ClassSymbol> allSymbols = new HashSet<>();
+
+ List<CompUnit> units = new ArrayList<>(initialSources);
+
+ Set<Processor> toRun = new LinkedHashSet<>();
+
+ boolean errorRaised = false;
+
+ while (true) {
+ ImmutableSet<ClassSymbol> syms =
+ Sets.difference(result.units().keySet(), allSymbols).immutableCopy();
+ allSymbols.addAll(syms);
+ if (syms.isEmpty()) {
+ break;
+ }
+ ImmutableSetMultimap<ClassSymbol, Symbol> allAnnotations = getAllAnnotations(env, syms);
+ TurbineRoundEnvironment roundEnv = null;
+ for (Processor processor : processorInfo.processors()) {
+ Set<TypeElement> annotations = new HashSet<>();
+ Pattern pattern = wanted.get(processor);
+ boolean run = toRun.contains(processor);
+ for (ClassSymbol a : allAnnotations.keys()) {
+ if (pattern.matcher(a.toString()).matches()) {
+ annotations.add(factory.typeElement(a));
+ run = true;
+ }
+ }
+ if (run) {
+ toRun.add(processor);
+ if (roundEnv == null) {
+ roundEnv =
+ new TurbineRoundEnvironment(factory, syms, false, errorRaised, allAnnotations);
+ }
+ // discard the result of Processor#process because 'claiming' annotations is a bad idea
+ // TODO(cushon): consider disallowing this, or reporting a diagnostic
+ boolean unused = processor.process(annotations, roundEnv);
+ }
+ }
+ Collection<SourceFile> files = filer.finishRound();
+ if (files.isEmpty()) {
+ break;
+ }
+ for (SourceFile file : files) {
+ units.add(Parser.parse(file));
+ }
+ errorRaised = log.errorRaised();
+ if (errorRaised) {
+ log.maybeThrow();
+ }
+ log.clear();
+ result =
+ Binder.bind(
+ log,
+ units,
+ filer.generatedSources(),
+ filer.generatedClasses(),
+ classpath,
+ bootclasspath,
+ moduleVersion);
+ tenv = new SimpleEnv<>(result.units());
+ env = CompoundEnv.<ClassSymbol, TypeBoundClass>of(result.classPathEnv()).append(tenv);
+ factory.round(env, result.tli());
+ }
+
+ TurbineRoundEnvironment roundEnv = null;
+ for (Processor processor : toRun) {
+ if (roundEnv == null) {
+ roundEnv =
+ new TurbineRoundEnvironment(
+ factory,
+ ImmutableSet.of(),
+ /* processingOver= */ true,
+ errorRaised,
+ ImmutableSetMultimap.of());
+ }
+ processor.process(ImmutableSet.of(), roundEnv);
+ }
+
+ Collection<SourceFile> files = filer.finishRound();
+ if (!files.isEmpty()) {
+ // processors aren't supposed to generate sources on the final processing round, but javac
+ // tolerates it anyway
+ // TODO(cushon): consider disallowing this, or reporting a diagnostic
+ for (SourceFile file : files) {
+ units.add(Parser.parse(file));
+ }
+ result =
+ Binder.bind(
+ log,
+ units,
+ filer.generatedSources(),
+ filer.generatedClasses(),
+ classpath,
+ bootclasspath,
+ moduleVersion);
+ log.maybeThrow();
+ }
+
+ if (!filer.generatedClasses().isEmpty()) {
+ // add any generated class files to the output
+ // TODO(cushon): consider handling generated classes after each round
+ result =
+ new BindingResult(
+ result.units(),
+ result.modules(),
+ result.classPathEnv(),
+ result.tli(),
+ result.generatedSources(),
+ filer.generatedClasses());
+ }
+ return result;
+ }
+
+ /** Returns a map from annotations present in the compilation to the annotated elements. */
+ private static ImmutableSetMultimap<ClassSymbol, Symbol> getAllAnnotations(
+ Env<ClassSymbol, TypeBoundClass> env, Iterable<ClassSymbol> syms) {
+ ImmutableSetMultimap.Builder<ClassSymbol, Symbol> result = ImmutableSetMultimap.builder();
+ for (ClassSymbol sym : syms) {
+ TypeBoundClass info = env.get(sym);
+ for (AnnoInfo annoInfo : info.annotations()) {
+ if (sym.simpleName().equals("package-info")) {
+ addAnno(result, annoInfo, sym.owner());
+ } else {
+ addAnno(result, annoInfo, sym);
+ }
+ }
+ for (TypeBoundClass.MethodInfo method : info.methods()) {
+ for (AnnoInfo annoInfo : method.annotations()) {
+ addAnno(result, annoInfo, method.sym());
+ }
+ for (TypeBoundClass.ParamInfo param : method.parameters()) {
+ for (AnnoInfo annoInfo : param.annotations()) {
+ addAnno(result, annoInfo, param.sym());
+ }
+ }
+ }
+ for (FieldInfo field : info.fields()) {
+ for (AnnoInfo annoInfo : field.annotations()) {
+ addAnno(result, annoInfo, field.sym());
+ }
+ }
+ }
+ return result.build();
+ }
+
+ private static void addAnno(
+ ImmutableSetMultimap.Builder<ClassSymbol, Symbol> result, AnnoInfo annoInfo, Symbol owner) {
+ ClassSymbol sym = annoInfo.sym();
+ if (sym != null) {
+ result.put(sym, owner);
+ }
+ }
+
+ public static ProcessorInfo initializeProcessors(
+ ImmutableList<String> javacopts,
+ ImmutableList<String> processorPath,
+ ImmutableSet<String> processorNames)
+ throws MalformedURLException {
+ ClassLoader processorLoader = null;
+ ImmutableList.Builder<Processor> processors = ImmutableList.builder();
+ ImmutableMap<String, String> processorOptions;
+ if (!processorNames.isEmpty() && !javacopts.contains("-proc:none")) {
+ if (!processorPath.isEmpty()) {
+ processorLoader =
+ new URLClassLoader(
+ toUrls(processorPath),
+ new ClassLoader(getPlatformClassLoader()) {
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ if (name.startsWith("com.sun.source.")
+ || name.startsWith("com.sun.tools.")
+ || name.startsWith("com.google.common.collect.")
+ || name.startsWith("com.google.common.base.")) {
+ return Class.forName(name);
+ }
+ throw new ClassNotFoundException(name);
+ }
+ });
+ } else {
+ processorLoader = Processing.class.getClassLoader();
+ }
+ for (String processor : processorNames) {
+ try {
+ Class<? extends Processor> clazz =
+ Class.forName(processor, false, processorLoader).asSubclass(Processor.class);
+ processors.add(clazz.getConstructor().newInstance());
+ } catch (ReflectiveOperationException e) {
+ throw new LinkageError(e.getMessage(), e);
+ }
+ }
+ processorOptions = processorOptions(javacopts);
+ } else {
+ processorOptions = ImmutableMap.of();
+ }
+ SourceVersion sourceVersion = SourceVersion.latestSupported();
+ Iterator<String> it = javacopts.iterator();
+ while (it.hasNext()) {
+ String option = it.next();
+ switch (option) {
+ case "-target":
+ if (it.hasNext()) {
+ String value = it.next();
+ switch (value) {
+ case "5":
+ case "1.5":
+ sourceVersion = SourceVersion.RELEASE_5;
+ break;
+ case "6":
+ case "1.6":
+ sourceVersion = SourceVersion.RELEASE_6;
+ break;
+ case "7":
+ case "1.7":
+ sourceVersion = SourceVersion.RELEASE_7;
+ break;
+ case "8":
+ sourceVersion = SourceVersion.RELEASE_8;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return ProcessorInfo.create(
+ processors.build(), processorLoader, processorOptions, sourceVersion);
+ }
+
+ private static URL[] toUrls(ImmutableList<String> processorPath) throws MalformedURLException {
+ URL[] urls = new URL[processorPath.size()];
+ int i = 0;
+ for (String path : processorPath) {
+ urls[i++] = Paths.get(path).toUri().toURL();
+ }
+ return urls;
+ }
+
+ public static ClassLoader getPlatformClassLoader() {
+ try {
+ return (ClassLoader) ClassLoader.class.getMethod("getPlatformClassLoader").invoke(null);
+ } catch (ReflectiveOperationException e) {
+ // In earlier releases, set 'null' as the parent to delegate to the boot class loader.
+ return null;
+ }
+ }
+
+ private static ImmutableMap<String, String> processorOptions(ImmutableList<String> javacopts) {
+ Map<String, String> result = new LinkedHashMap<>(); // ImmutableMap.Builder rejects duplicates
+ for (String javacopt : javacopts) {
+ if (javacopt.startsWith("-A")) {
+ javacopt = javacopt.substring("-A".length());
+ int idx = javacopt.indexOf('=');
+ String key;
+ String value;
+ if (idx != -1) {
+ key = javacopt.substring(0, idx);
+ value = javacopt.substring(idx + 1);
+ } else {
+ key = javacopt;
+ value = javacopt;
+ }
+ result.put(key, value);
+ }
+ }
+ return ImmutableMap.copyOf(result);
+ }
+
+ /** Information about any annotation processing performed by this compilation. */
+ @AutoValue
+ public abstract static class ProcessorInfo {
+
+ abstract ImmutableList<Processor> processors();
+
+ /**
+ * The classloader to use for annotation processor implementations, and any annotations they
+ * access reflectively.
+ */
+ @Nullable
+ abstract ClassLoader loader();
+
+ /** Command line annotation processing options, passed to javac with {@code -Akey=value}. */
+ abstract ImmutableMap<String, String> options();
+
+ public abstract SourceVersion sourceVersion();
+
+ public static ProcessorInfo create(
+ ImmutableList<Processor> processors,
+ @Nullable ClassLoader loader,
+ ImmutableMap<String, String> options,
+ SourceVersion sourceVersion) {
+ return new AutoValue_Processing_ProcessorInfo(processors, loader, options, sourceVersion);
+ }
+
+ static ProcessorInfo empty() {
+ return create(
+ /* processors= */ ImmutableList.of(),
+ /* loader= */ null,
+ /* options= */ ImmutableMap.of(),
+ /* sourceVersion= */ SourceVersion.latest());
+ }
+ }
+}