• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Bazel Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //    http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.devtools.build.runfiles;
16 
17 import java.io.IOException;
18 import java.io.PrintWriter;
19 import java.util.ArrayDeque;
20 import java.util.Deque;
21 import java.util.Set;
22 import javax.annotation.processing.AbstractProcessor;
23 import javax.annotation.processing.RoundEnvironment;
24 import javax.annotation.processing.SupportedAnnotationTypes;
25 import javax.annotation.processing.SupportedOptions;
26 import javax.lang.model.SourceVersion;
27 import javax.lang.model.element.Element;
28 import javax.lang.model.element.TypeElement;
29 import javax.tools.Diagnostic.Kind;
30 
31 /** Processor for {@link AutoBazelRepository}. */
32 @SupportedAnnotationTypes("com.google.devtools.build.runfiles.AutoBazelRepository")
33 @SupportedOptions(AutoBazelRepositoryProcessor.BAZEL_REPOSITORY_OPTION)
34 public final class AutoBazelRepositoryProcessor extends AbstractProcessor {
35 
36   static final String BAZEL_REPOSITORY_OPTION = "bazel.repository";
37 
38   @Override
getSupportedSourceVersion()39   public SourceVersion getSupportedSourceVersion() {
40     return SourceVersion.latestSupported();
41   }
42 
43   @Override
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)44   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
45     annotations.stream()
46         .flatMap(element -> roundEnv.getElementsAnnotatedWith(element).stream())
47         .map(element -> (TypeElement) element)
48         .forEach(this::emitClass);
49     return false;
50   }
51 
emitClass(TypeElement annotatedClass)52   private void emitClass(TypeElement annotatedClass) {
53     // This option is always provided by the Java rule implementations.
54     if (!processingEnv.getOptions().containsKey(BAZEL_REPOSITORY_OPTION)) {
55       processingEnv
56           .getMessager()
57           .printMessage(
58               Kind.ERROR,
59               String.format(
60                   "The %1$s annotation processor option is not set. To use this annotation"
61                       + " processor, provide the canonical repository name of the current target as"
62                       + " the value of the -A%1$s flag.",
63                   BAZEL_REPOSITORY_OPTION),
64               annotatedClass);
65       return;
66     }
67     String repositoryName = processingEnv.getOptions().get(BAZEL_REPOSITORY_OPTION);
68     if (repositoryName == null) {
69       // javac translates '-Abazel.repository=' into a null value.
70       // https://github.com/openjdk/jdk/blob/7a49c9baa1d4ad7df90e7ca626ec48ba76881822/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java#L651
71       repositoryName = "";
72     }
73 
74     // For a nested class Outer.Middle.Inner, generate a class with simple name
75     // AutoBazelRepository_Outer_Middle_Inner.
76     // Note: There can be collisions when local classes are involved, but since the definition of a
77     // class depends only on the containing Bazel target, this does not result in ambiguity.
78     Deque<String> classNameSegments = new ArrayDeque<>();
79     Element element = annotatedClass;
80     while (element instanceof TypeElement) {
81       classNameSegments.addFirst(element.getSimpleName().toString());
82       element = element.getEnclosingElement();
83     }
84     classNameSegments.addFirst("AutoBazelRepository");
85     String generatedClassSimpleName = String.join("_", classNameSegments);
86 
87     String generatedClassPackage =
88         processingEnv.getElementUtils().getPackageOf(annotatedClass).getQualifiedName().toString();
89 
90     String generatedClassName =
91         generatedClassPackage.isEmpty()
92             ? generatedClassSimpleName
93             : generatedClassPackage + "." + generatedClassSimpleName;
94 
95     try (PrintWriter out =
96         new PrintWriter(
97             processingEnv.getFiler().createSourceFile(generatedClassName).openWriter())) {
98       if (!generatedClassPackage.isEmpty()) {
99         // This annotation may exist on a class which is at the root package
100         out.printf("package %s;\n", generatedClassPackage);
101       }
102       out.printf("\n");
103       out.printf("class %s {\n", generatedClassSimpleName);
104       out.printf("  /**\n");
105       out.printf("   * The canonical name of the repository containing the Bazel target that\n");
106       out.printf("   * compiled {@link %s}.\n", annotatedClass.getQualifiedName().toString());
107       out.printf("   */\n");
108       out.printf("  static final String NAME = \"%s\";\n", repositoryName);
109       out.printf("\n");
110       out.printf("  private %s() {}\n", generatedClassSimpleName);
111       out.printf("}\n");
112     } catch (IOException e) {
113       processingEnv
114           .getMessager()
115           .printMessage(
116               Kind.ERROR,
117               String.format("Failed to generate %s: %s", generatedClassName, e.getMessage()),
118               annotatedClass);
119     }
120   }
121 }
122