Skip to content

Commit

Permalink
Expose current repository name to Java with @AutoBazelRepository
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fmeum committed Oct 24, 2022
1 parent 7190f22 commit 2ad9baa
Show file tree
Hide file tree
Showing 8 changed files with 420 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static com.google.common.collect.ImmutableSet.toImmutableSet;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
Expand Down Expand Up @@ -422,6 +423,16 @@ void addJavacArguments(BlazeJavacArguments.Builder builder) {
javacArguments.add("-proc:none");
}

if (getTargetLabel() != null) {
// The canonical repository name is the part of the label right after any '@' (if any) and
// before any `/`. We prefix it with a single '@' as empty annotation processor arguments
// would end up as null entries in the options map.
String repositoryPart = getTargetLabel().split("/", 2)[0];
String repositoryName = '@' + CharMatcher.is('@').trimLeadingFrom(repositoryPart);
// Consumed by com.google.devtools.build.runfiles.AutoBazelRepositoryProcessor.
javacArguments.add("-Abazel.repository=" + repositoryName);
}

for (String option : javacOpts) {
if (option.startsWith("-J")) { // ignore the VM options.
continue;
Expand Down
177 changes: 177 additions & 0 deletions src/test/shell/bazel/bazel_java17_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,182 @@ EOF
expect_log "^World\$"
}

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.BAZEL_REPOSITORY);
}
}
EOF

cat > pkg/Binary.java <<'EOF'
package com.example;
import com.google.devtools.build.runfiles.AutoBazelRepository;
@AutoBazelRepository
public class Binary {
public static void main(String[] args) {
System.out.printf("in pkg/Binary.java: '%s'%n", AutoBazelRepository_Binary.BAZEL_REPOSITORY);
Library.printRepositoryName();
}
}
EOF

cat > 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 pkg/Test.java: '%s'%n", AutoBazelRepository_Test.BAZEL_REPOSITORY);
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.BAZEL_REPOSITORY);
}
}
EOF

cat > other_repo/pkg/Binary.java <<'EOF'
package com.example;
import com.google.devtools.build.runfiles.AutoBazelRepository;
@AutoBazelRepository
public class Binary {
public static void main(String[] args) {
System.out.printf("in external/other_repo/pkg/Binary.java: '%s'%n", AutoBazelRepository_Binary.BAZEL_REPOSITORY);
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.BAZEL_REPOSITORY);
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 "Tests Java 17 language features"
32 changes: 32 additions & 0 deletions tools/java/runfiles/AutoBazelRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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 BAZEL_REPOSITORY}
* 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 {

}
102 changes: 102 additions & 0 deletions tools/java/runfiles/AutoBazelRepositoryProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// 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.annotation.processing.SupportedSourceVersion;
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;

@SupportedAnnotationTypes("com.google.devtools.build.runfiles.AutoBazelRepository")
@SupportedOptions("bazel.repository")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public final class AutoBazelRepositoryProcessor extends AbstractProcessor {

@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 JavaBuilder.
String repositoryName = processingEnv.getOptions()
.getOrDefault("bazel.repository", "@for testing only");
if (!repositoryName.startsWith("@")) {
processingEnv.getMessager()
.printMessage(Kind.ERROR, "Expected value of bazel.repository option to start with '@'",
annotatedClass);
return;
}
repositoryName = repositoryName.substring(1);

// 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 BAZEL_REPOSITORY = \"%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);
}
}
}
20 changes: 19 additions & 1 deletion tools/java/runfiles/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,31 @@ filegroup(
filegroup(
name = "java-srcs",
srcs = [
"AutoBazelRepository.java",
"AutoBazelRepositoryProcessor.java",
"Runfiles.java",
"Util.java",
],
)

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",
)
13 changes: 13 additions & 0 deletions tools/java/runfiles/BUILD.tools
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Loading

0 comments on commit 2ad9baa

Please sign in to comment.