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