From 455454a56e961affb041a1d4a9214f7f313a05aa Mon Sep 17 00:00:00 2001 From: Fabian Meumertzheim Date: Thu, 10 Nov 2022 10:18:57 -0800 Subject: [PATCH] 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 --- .../build/lib/rules/java/JavaCommon.java | 17 +- .../lib/rules/java/JavaInfoBuildHelper.java | 36 ++-- src/test/shell/bazel/bazel_java_test.sh | 186 ++++++++++++++++++ tools/java/runfiles/AutoBazelRepository.java | 29 +++ .../AutoBazelRepositoryProcessor.java | 127 ++++++++++++ tools/java/runfiles/BUILD | 20 +- tools/java/runfiles/BUILD.tools | 13 ++ 7 files changed, 410 insertions(+), 18 deletions(-) create mode 100644 tools/java/runfiles/AutoBazelRepository.java create mode 100644 tools/java/runfiles/AutoBazelRepositoryProcessor.java 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 b382b1f39f7a36..313d1b07383d88 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 @@ public final void initializeJavacOpts() { /** Computes javacopts for the current rule. */ private ImmutableList computeJavacOpts(Collection extraRuleJavacOpts) { - return ImmutableList.builder() - .addAll(javaToolchain.getJavacOptions(ruleContext)) - .addAll(extraRuleJavacOpts) + ImmutableList.Builder javacOpts = + ImmutableList.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() { public JavaTargetAttributes.Builder initCommon( Collection extraSrcs, Iterable 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 b698c005d684c1..5dca2899790d07 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 @@ public JavaInfo createJavaCompileAction( JavaToolchainProvider toolchainProvider = javaToolchain; + JavaPluginInfo pluginInfo = mergeExportedJavaPluginInfo(plugins, deps); + ImmutableList.Builder allJavacOptsBuilder = + ImmutableList.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 @@ public JavaInfo createJavaCompileAction( .setSourcePathEntries(sourcepathEntries) .addAdditionalOutputs(annotationProcessorAdditionalOutputs) .enableJspecify(enableJSpecify) - .setJavacOpts( - ImmutableList.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 @@ public JavaInfo createJavaCompileAction( 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 c11137ffc2f7dd..5e55ff302f6b1b 100755 --- a/src/test/shell/bazel/bazel_java_test.sh +++ b/src/test/shell/bazel/bazel_java_test.sh @@ -1763,5 +1763,191 @@ EOF 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 00000000000000..6dc533001c299e --- /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 00000000000000..ae356d800f6d03 --- /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 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 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 fdd782d87db9dd..e0487a301761e0 100644 --- a/tools/java/runfiles/BUILD +++ b/tools/java/runfiles/BUILD @@ -26,6 +26,8 @@ filegroup( filegroup( name = "java-srcs", srcs = [ + "AutoBazelRepository.java", + "AutoBazelRepositoryProcessor.java", "Runfiles.java", "Util.java", ], @@ -33,6 +35,22 @@ filegroup( 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 a2ac4f58cd0677..70cd870d46ff5f 100644 --- a/tools/java/runfiles/BUILD.tools +++ b/tools/java/runfiles/BUILD.tools @@ -4,5 +4,18 @@ java_library( "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", )