Expose current repository name to Java with @AutoBazelRepository
Java targets depending on `@bazel_tools//tools/java/runfiles` can add the new `@AutoBazelRepository` to a class to have an annotation processor generate a companion class with a `BAZEL_REPOSITORY` constant containing the repository name of the target that compiled the class.
This requires a small addition to JavaBuilder to parse the repository name out of the target label and pass it to javac as a processor option.
Work towards #16124
Closes #16534.
PiperOrigin-RevId: 487573496
Change-Id: Id9b6526ce32268089c91c6d17363d1e7682f64a4
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
index b382b1f..313d1b0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCommon.java
@@ -371,9 +371,18 @@
/** Computes javacopts for the current rule. */
private ImmutableList<String> computeJavacOpts(Collection<String> extraRuleJavacOpts) {
- return ImmutableList.<String>builder()
- .addAll(javaToolchain.getJavacOptions(ruleContext))
- .addAll(extraRuleJavacOpts)
+ ImmutableList.Builder<String> javacOpts =
+ ImmutableList.<String>builder()
+ .addAll(javaToolchain.getJavacOptions(ruleContext))
+ .addAll(extraRuleJavacOpts);
+ if (activePlugins
+ .plugins()
+ .processorClasses()
+ .toSet()
+ .contains("com.google.devtools.build.runfiles.AutoBazelRepositoryProcessor")) {
+ javacOpts.add("-Abazel.repository=" + ruleContext.getRepository().getName());
+ }
+ return javacOpts
.addAll(computePerPackageJavacOpts(ruleContext, javaToolchain))
.addAll(addModuleJavacopts(ruleContext))
.addAll(ruleContext.getExpander().withDataLocations().tokenized("javacopts"))
@@ -538,8 +547,8 @@
public JavaTargetAttributes.Builder initCommon(
Collection<Artifact> extraSrcs, Iterable<String> extraJavacOpts) {
Preconditions.checkState(javacOpts == null);
- javacOpts = computeJavacOpts(ImmutableList.copyOf(extraJavacOpts));
activePlugins = collectPlugins();
+ javacOpts = computeJavacOpts(ImmutableList.copyOf(extraJavacOpts));
JavaTargetAttributes.Builder javaTargetAttributes = new JavaTargetAttributes.Builder(semantics);
javaCompilationHelper =
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaInfoBuildHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaInfoBuildHelper.java
index b698c00..5dca289 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaInfoBuildHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaInfoBuildHelper.java
@@ -284,6 +284,28 @@
JavaToolchainProvider toolchainProvider = javaToolchain;
+ JavaPluginInfo pluginInfo = mergeExportedJavaPluginInfo(plugins, deps);
+ ImmutableList.Builder<String> allJavacOptsBuilder =
+ ImmutableList.<String>builder()
+ .addAll(toolchainProvider.getJavacOptions(starlarkRuleContext.getRuleContext()))
+ .addAll(
+ javaSemantics.getCompatibleJavacOptions(
+ starlarkRuleContext.getRuleContext(), toolchainProvider));
+ if (pluginInfo
+ .plugins()
+ .processorClasses()
+ .toSet()
+ .contains("com.google.devtools.build.runfiles.AutoBazelRepositoryProcessor")) {
+ allJavacOptsBuilder.add(
+ "-Abazel.repository=" + starlarkRuleContext.getRuleContext().getRepository().getName());
+ }
+ allJavacOptsBuilder
+ .addAll(
+ JavaCommon.computePerPackageJavacOpts(
+ starlarkRuleContext.getRuleContext(), toolchainProvider))
+ .addAll(JavaModuleFlagsProvider.toFlags(addExports, addOpens))
+ .addAll(tokenize(javacOpts));
+
JavaLibraryHelper helper =
new JavaLibraryHelper(starlarkRuleContext.getRuleContext())
.setOutput(outputJar)
@@ -295,18 +317,7 @@
.setSourcePathEntries(sourcepathEntries)
.addAdditionalOutputs(annotationProcessorAdditionalOutputs)
.enableJspecify(enableJSpecify)
- .setJavacOpts(
- ImmutableList.<String>builder()
- .addAll(toolchainProvider.getJavacOptions(starlarkRuleContext.getRuleContext()))
- .addAll(
- javaSemantics.getCompatibleJavacOptions(
- starlarkRuleContext.getRuleContext(), toolchainProvider))
- .addAll(
- JavaCommon.computePerPackageJavacOpts(
- starlarkRuleContext.getRuleContext(), toolchainProvider))
- .addAll(JavaModuleFlagsProvider.toFlags(addExports, addOpens))
- .addAll(tokenize(javacOpts))
- .build());
+ .setJavacOpts(allJavacOptsBuilder.build());
if (injectingRuleKind != Starlark.NONE) {
helper.setInjectingRuleKind((String) injectingRuleKind);
@@ -316,7 +327,6 @@
streamProviders(deps, JavaCompilationArgsProvider.class).forEach(helper::addDep);
streamProviders(exports, JavaCompilationArgsProvider.class).forEach(helper::addExport);
helper.setCompilationStrictDepsMode(getStrictDepsMode(Ascii.toUpperCase(strictDepsMode)));
- JavaPluginInfo pluginInfo = mergeExportedJavaPluginInfo(plugins, deps);
// Optimization: skip this if there are no annotation processors, to avoid unnecessarily
// disabling the direct classpath optimization if `enable_annotation_processor = False`
// but there aren't any annotation processors.
diff --git a/src/test/shell/bazel/bazel_java_test.sh b/src/test/shell/bazel/bazel_java_test.sh
index c11137f..5e55ff3 100755
--- a/src/test/shell/bazel/bazel_java_test.sh
+++ b/src/test/shell/bazel/bazel_java_test.sh
@@ -1763,5 +1763,191 @@
bazel build //java/main:C2 &>"${TEST_log}" || fail "Expected to build"
}
+function test_auto_bazel_repository() {
+ cat >> WORKSPACE <<'EOF'
+local_repository(
+ name = "other_repo",
+ path = "other_repo",
+)
+EOF
+
+ mkdir -p pkg
+ cat > pkg/BUILD.bazel <<'EOF'
+java_library(
+ name = "library",
+ srcs = ["Library.java"],
+ deps = ["@bazel_tools//tools/java/runfiles"],
+ visibility = ["//visibility:public"],
+)
+
+java_binary(
+ name = "binary",
+ srcs = ["Binary.java"],
+ main_class = "com.example.Binary",
+ deps = [
+ ":library",
+ "@bazel_tools//tools/java/runfiles",
+ ],
+)
+
+java_test(
+ name = "test",
+ srcs = ["Test.java"],
+ main_class = "com.example.Test",
+ use_testrunner = False,
+ deps = [
+ ":library",
+ "@bazel_tools//tools/java/runfiles",
+ ],
+)
+EOF
+
+ cat > pkg/Library.java <<'EOF'
+package com.example;
+
+import com.google.devtools.build.runfiles.AutoBazelRepository;
+
+@AutoBazelRepository
+public class Library {
+ public static void printRepositoryName() {
+ System.out.printf("in pkg/Library.java: '%s'%n", AutoBazelRepository_Library.NAME);
+ }
+}
+EOF
+
+ cat > pkg/Binary.java <<'EOF'
+package com.example;
+
+import com.google.devtools.build.runfiles.AutoBazelRepository;
+
+public class Binary {
+ @AutoBazelRepository
+ private static class Class1 {
+ }
+
+ public static void main(String[] args) {
+ System.out.printf("in pkg/Binary.java: '%s'%n", AutoBazelRepository_Binary_Class1.NAME);
+ Library.printRepositoryName();
+ }
+}
+EOF
+
+ cat > pkg/Test.java <<'EOF'
+package com.example;
+
+import com.google.devtools.build.runfiles.AutoBazelRepository;
+
+public class Test {
+ private static class Class1 {
+ @AutoBazelRepository
+ private static class Class2 {
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.printf("in pkg/Test.java: '%s'%n", AutoBazelRepository_Test_Class1_Class2.NAME);
+ Library.printRepositoryName();
+ }
+}
+EOF
+
+ mkdir -p other_repo
+ touch other_repo/WORKSPACE
+
+ mkdir -p other_repo/pkg
+ cat > other_repo/pkg/BUILD.bazel <<'EOF'
+java_library(
+ name = "library2",
+ srcs = ["Library2.java"],
+ deps = ["@bazel_tools//tools/java/runfiles"],
+)
+
+java_binary(
+ name = "binary",
+ srcs = ["Binary.java"],
+ main_class = "com.example.Binary",
+ deps = [
+ ":library2",
+ "@//pkg:library",
+ "@bazel_tools//tools/java/runfiles",
+ ],
+)
+java_test(
+ name = "test",
+ srcs = ["Test.java"],
+ main_class = "com.example.Test",
+ use_testrunner = False,
+ deps = [
+ ":library2",
+ "@//pkg:library",
+ "@bazel_tools//tools/java/runfiles",
+ ],
+)
+EOF
+
+ cat > other_repo/pkg/Library2.java <<'EOF'
+package com.example;
+
+import com.google.devtools.build.runfiles.AutoBazelRepository;
+
+@AutoBazelRepository
+public class Library2 {
+ public static void printRepositoryName() {
+ System.out.printf("in external/other_repo/pkg/Library2.java: '%s'%n", AutoBazelRepository_Library2.NAME);
+ }
+}
+EOF
+
+ cat > other_repo/pkg/Binary.java <<'EOF'
+package com.example;
+
+import com.google.devtools.build.runfiles.AutoBazelRepository;
+import static com.example.AutoBazelRepository_Binary.NAME;
+
+@AutoBazelRepository
+public class Binary {
+ public static void main(String[] args) {
+ System.out.printf("in external/other_repo/pkg/Binary.java: '%s'%n", NAME);
+ Library2.printRepositoryName();
+ Library.printRepositoryName();
+ }
+}
+EOF
+
+ cat > other_repo/pkg/Test.java <<'EOF'
+package com.example;
+
+import com.google.devtools.build.runfiles.AutoBazelRepository;
+
+@AutoBazelRepository
+public class Test {
+ public static void main(String[] args) {
+ System.out.printf("in external/other_repo/pkg/Test.java: '%s'%n", AutoBazelRepository_Test.NAME);
+ Library2.printRepositoryName();
+ Library.printRepositoryName();
+ }
+}
+EOF
+
+ bazel run //pkg:binary &>"$TEST_log" || fail "Run should succeed"
+ expect_log "in pkg/Binary.java: ''"
+ expect_log "in pkg/Library.java: ''"
+
+ bazel test --test_output=streamed //pkg:test &>"$TEST_log" || fail "Test should succeed"
+ expect_log "in pkg/Test.java: ''"
+ expect_log "in pkg/Library.java: ''"
+
+ bazel run @other_repo//pkg:binary &>"$TEST_log" || fail "Run should succeed"
+ expect_log "in external/other_repo/pkg/Binary.java: 'other_repo'"
+ expect_log "in external/other_repo/pkg/Library2.java: 'other_repo'"
+ expect_log "in pkg/Library.java: ''"
+
+ bazel test --test_output=streamed \
+ @other_repo//pkg:test &>"$TEST_log" || fail "Test should succeed"
+ expect_log "in external/other_repo/pkg/Test.java: 'other_repo'"
+ expect_log "in external/other_repo/pkg/Library2.java: 'other_repo'"
+ expect_log "in pkg/Library.java: ''"
+}
+
run_suite "Java integration tests"
diff --git a/tools/java/runfiles/AutoBazelRepository.java b/tools/java/runfiles/AutoBazelRepository.java
new file mode 100644
index 0000000..6dc5330
--- /dev/null
+++ b/tools/java/runfiles/AutoBazelRepository.java
@@ -0,0 +1,29 @@
+// 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotating a class {@code Fooer} with this annotation generates a class {@code
+ * AutoBazelRepository_Fooer} defining a {@link String} constant {@code NAME} containing the
+ * canonical name of the repository containing the Bazel target that compiled the annotated class.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface AutoBazelRepository {}
diff --git a/tools/java/runfiles/AutoBazelRepositoryProcessor.java b/tools/java/runfiles/AutoBazelRepositoryProcessor.java
new file mode 100644
index 0000000..ae356d8
--- /dev/null
+++ b/tools/java/runfiles/AutoBazelRepositoryProcessor.java
@@ -0,0 +1,127 @@
+// 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);
+ }
+ }
+}
diff --git a/tools/java/runfiles/BUILD b/tools/java/runfiles/BUILD
index fdd782d..e0487a3 100644
--- a/tools/java/runfiles/BUILD
+++ b/tools/java/runfiles/BUILD
@@ -26,6 +26,8 @@
filegroup(
name = "java-srcs",
srcs = [
+ "AutoBazelRepository.java",
+ "AutoBazelRepositoryProcessor.java",
"Runfiles.java",
"Util.java",
],
@@ -33,6 +35,22 @@
java_library(
name = "runfiles",
- srcs = [":java-srcs"],
+ srcs = [
+ "Runfiles.java",
+ "Util.java",
+ ],
+ exported_plugins = [":auto_bazel_repository_processor"],
visibility = ["//tools/java/runfiles/testing:__pkg__"],
+ exports = [":auto_bazel_repository"],
+)
+
+java_library(
+ name = "auto_bazel_repository",
+ srcs = ["AutoBazelRepository.java"],
+)
+
+java_plugin(
+ name = "auto_bazel_repository_processor",
+ srcs = ["AutoBazelRepositoryProcessor.java"],
+ processor_class = "com.google.devtools.build.runfiles.AutoBazelRepositoryProcessor",
)
diff --git a/tools/java/runfiles/BUILD.tools b/tools/java/runfiles/BUILD.tools
index a2ac4f5..70cd870 100644
--- a/tools/java/runfiles/BUILD.tools
+++ b/tools/java/runfiles/BUILD.tools
@@ -4,5 +4,18 @@
"Runfiles.java",
"Util.java",
],
+ exported_plugins = [":auto_bazel_repository_processor"],
visibility = ["//visibility:public"],
+ exports = [":auto_bazel_repository"],
+)
+
+java_library(
+ name = "auto_bazel_repository",
+ srcs = ["AutoBazelRepository.java"],
+)
+
+java_plugin(
+ name = "auto_bazel_repository_processor",
+ srcs = ["AutoBazelRepositoryProcessor.java"],
+ processor_class = "com.google.devtools.build.runfiles.AutoBazelRepositoryProcessor",
)