blob: ae356d800f6d03fad41d58457fcb1994d33a7b65 [file] [log] [blame]
// Copyright 2022 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.runfiles;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
/** Processor for {@link AutoBazelRepository}. */
@SupportedAnnotationTypes("com.google.devtools.build.runfiles.AutoBazelRepository")
@SupportedOptions(AutoBazelRepositoryProcessor.BAZEL_REPOSITORY_OPTION)
public final class AutoBazelRepositoryProcessor extends AbstractProcessor {
static final String BAZEL_REPOSITORY_OPTION = "bazel.repository";
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
annotations.stream()
.flatMap(element -> roundEnv.getElementsAnnotatedWith(element).stream())
.map(element -> (TypeElement) element)
.forEach(this::emitClass);
return true;
}
private void emitClass(TypeElement annotatedClass) {
// This option is always provided by the Java rule implementations.
if (!processingEnv.getOptions().containsKey(BAZEL_REPOSITORY_OPTION)) {
processingEnv
.getMessager()
.printMessage(
Kind.ERROR,
String.format(
"The %1$s annotation processor option is not set. To use this annotation"
+ " processor, provide the canonical repository name of the current target as"
+ " the value of the -A%1$s flag.",
BAZEL_REPOSITORY_OPTION),
annotatedClass);
return;
}
String repositoryName = processingEnv.getOptions().get(BAZEL_REPOSITORY_OPTION);
if (repositoryName == null) {
// javac translates '-Abazel.repository=' into a null value.
// https://github.com/openjdk/jdk/blob/7a49c9baa1d4ad7df90e7ca626ec48ba76881822/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java#L651
repositoryName = "";
}
// For a nested class Outer.Middle.Inner, generate a class with simple name
// AutoBazelRepository_Outer_Middle_Inner.
// Note: There can be collisions when local classes are involved, but since the definition of a
// class depends only on the containing Bazel target, this does not result in ambiguity.
List<String> nestedClassNames =
Stream.iterate(
annotatedClass,
element -> element instanceof TypeElement,
Element::getEnclosingElement)
.map(Element::getSimpleName)
.map(Name::toString)
.collect(toList());
Collections.reverse(nestedClassNames);
String generatedClassSimpleName =
Stream.concat(Stream.of("AutoBazelRepository"), nestedClassNames.stream())
.collect(joining("_"));
String generatedClassPackage =
processingEnv.getElementUtils().getPackageOf(annotatedClass).getQualifiedName().toString();
String generatedClassName =
generatedClassPackage.isEmpty()
? generatedClassSimpleName
: generatedClassPackage + "." + generatedClassSimpleName;
try (PrintWriter out =
new PrintWriter(
processingEnv.getFiler().createSourceFile(generatedClassName).openWriter())) {
out.printf("package %s;\n", generatedClassPackage);
out.printf("\n");
out.printf("class %s {\n", generatedClassSimpleName);
out.printf(" /**\n");
out.printf(" * The canonical name of the repository containing the Bazel target that\n");
out.printf(" * compiled {@link %s}.\n", annotatedClass.getQualifiedName().toString());
out.printf(" */\n");
out.printf(" static final String NAME = \"%s\";\n", repositoryName);
out.printf("\n");
out.printf(" private %s() {}\n", generatedClassSimpleName);
out.printf("}\n");
} catch (IOException e) {
processingEnv
.getMessager()
.printMessage(
Kind.ERROR,
String.format("Failed to generate %s: %s", generatedClassName, e.getMessage()),
annotatedClass);
}
}
}