Skip to content

Commit 7fcb53c

Browse files
committed
Implement an aspect to automatically index Bazel/Java codebases
Previously, the Bazel example required monkeypatching the `java_library` rule in order to inject the SemanticDB compiler plugin. This PR fixes this problem by adding support to automatically index Bazel codebases without modifying the build configuration (WORKSPACE or BUILD files). See scip_java.bzl in the diff for more details about how this works. To make this functionality work, this commit extends the functionality of the SCIP build tool to optionally allow users to specify a custom classpath, processorpath, and javac options.
1 parent 9e9451d commit 7fcb53c

File tree

18 files changed

+580
-77
lines changed

18 files changed

+580
-77
lines changed

.github/workflows/ci.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
docker run -v $PWD/.repos/$REPO:/sources -w /sources sourcegraph/scip-java:latest scip-java index
5656
file .repos/$REPO/index.scip || (echo "$REPO SCIP index doesn't exist!"; exit 1)
5757
}
58-
58+
5959
sudo apt install parallel
6060
export -f check_repo
6161
@@ -76,6 +76,22 @@ jobs:
7676
- run: du -h index.scip
7777
working-directory: examples/bazel-example
7878

79+
bazel_aspect:
80+
runs-on: ubuntu-latest
81+
steps:
82+
- uses: actions/checkout@v2
83+
- run: yarn global add @bazel/bazelisk
84+
- run: sbt cli/pack
85+
- run: echo "$PWD/scip-java/target/pack/bin" >> $GITHUB_PATH
86+
- run: |
87+
scip-java index --build-tool=bazel --bazel-scip-java-binary=$(which scip-java)
88+
- run: du -h index.scip
89+
- run: |
90+
scip-java index --build-tool=bazel --bazel-scip-java-binary=$(which scip-java)
91+
working-directory: examples/bazel-example
92+
- run: du -h index.scip
93+
working-directory: examples/bazel-example
94+
7995
check:
8096
runs-on: ubuntu-latest
8197
steps:

BUILD

Whitespace-only changes.

build.sbt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,14 @@ lazy val javacPlugin = project
163163
Seq(
164164
ShadeRule
165165
.rename(
166+
// Don't rename SemanticdbPlugin since the fully-qualified name is
167+
// referenced from META-INF/services/com.sun.source.util.Plugin
168+
"com.sourcegraph.semanticdb_javac.SemanticdbPlugin" ->
169+
"com.sourcegraph.semanticdb_javac.SemanticdbPlugin",
166170
"com.google.**" -> "com.sourcegraph.shaded.com.google.@1",
171+
// Need to shade the semanticdb-javac compiler plugin in order to be
172+
// able to index the plugin code itself.
173+
"com.sourcegraph.**" -> "com.sourcegraph.shaded.com.sourcegraph.@1",
167174
"google.**" -> "com.sourcegraph.shaded.google.@1",
168175
"org.relaxng.**" -> "com.sourcegraph.shaded.relaxng.@1"
169176
)
@@ -250,7 +257,7 @@ lazy val cli = project
250257
}
251258

252259
addJar(
253-
(javacPlugin / Compile / Keys.`package`).value,
260+
(javacPlugin / Compile / assembly).value,
254261
"semanticdb-plugin.jar"
255262
)
256263
addJar(
@@ -259,7 +266,12 @@ lazy val cli = project
259266
)
260267
addJar((gradlePlugin / Compile / assembly).value, "gradle-plugin.jar")
261268

262-
IO.copy(outs)
269+
IO.copy(
270+
outs,
271+
overwrite = true,
272+
preserveLastModified = false,
273+
preserveExecutable = true
274+
)
263275
val props = new Properties()
264276
val propsFile = out.resolve("scip-java.properties").toFile
265277
val copiedJars = outs.collect { case (_, out) =>

examples/bazel-example/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
aspects/scip_java.bzl
12
bazel-bazel-example

examples/bazel-example/WORKSPACE

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# This is an end-to-end example of how to consume scip-java from an external repository.
22
workspace(name = "scip_java_example")
3+
34
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
45

56
##############
@@ -20,11 +21,11 @@ http_archive(
2021
##############
2122
local_repository(
2223
name = "scip_java",
23-
path = "../.."
24+
path = "../..",
2425
)
2526

2627
# Copy and paste this, not the local_repository:
27-
#
28+
#
2829
# SCIP_JAVA_VERSION="0.8.20"
2930
# http_archive(
3031
# name = "scip_java",
@@ -44,36 +45,47 @@ http_archive(
4445
"https://github.com/bazelbuild/rules_proto/archive/refs/tags/4.0.0-3.20.0.tar.gz",
4546
],
4647
)
48+
4749
load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
50+
4851
rules_proto_dependencies()
52+
4953
rules_proto_toolchains()
5054

5155
##############
5256
# JVM External
5357
##############
5458
# To update this version, copy-paste instructions from https://github.com/bazelbuild/rules_jvm_external/releases
5559
RULES_JVM_EXTERNAL_TAG = "4.2"
60+
5661
RULES_JVM_EXTERNAL_SHA = "cd1a77b7b02e8e008439ca76fd34f5b07aecb8c752961f9640dea15e9e5ba1ca"
62+
5763
http_archive(
5864
name = "rules_jvm_external",
59-
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
6065
sha256 = RULES_JVM_EXTERNAL_SHA,
66+
strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
6167
url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
6268
)
69+
6370
load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
71+
6472
rules_jvm_external_deps()
73+
6574
load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
75+
6676
rules_jvm_external_setup()
77+
6778
load("@rules_jvm_external//:defs.bzl", "maven_install")
79+
6880
maven_install(
6981
artifacts = [
70-
"com.google.protobuf:protobuf-java:3.15.6", # Required dependency by scip-java.
71-
"com.google.protobuf:protobuf-java-util:3.15.6", # Required dependency by scip-java.
82+
"com.google.protobuf:protobuf-java:3.15.6", # Required dependency by scip-java.
83+
"com.google.protobuf:protobuf-java-util:3.15.6", # Required dependency by scip-java.
7284
# These dependencies are only required for the tests
73-
"com.google.guava:guava:31.0-jre",
85+
"com.google.guava:guava:31.0-jre",
7486
"com.google.auto.value:auto-value:1.5.3",
7587
],
7688
repositories = [
77-
"https://repo1.maven.org/maven2",
89+
"https://repo1.maven.org/maven2",
7890
],
7991
)

examples/bazel-example/aspects/BUILD

Whitespace-only changes.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""
2+
Bazel aspect to run scip-java against a Java Bazel codebase.
3+
4+
You can optionally commit this file into your git repository, gitignore it, or
5+
just delete it. When you run `scip-java index` in a Bazel codebase, this file
6+
will get re-created and the command will error if the file already exists but with
7+
different contents.
8+
9+
This aspect is needed for scip-java to inspect the structure of the Bazel build
10+
and register actions to index all java_library/java_test/java_binary targets.
11+
The result of running this aspect is that your bazel-bin/ directory will contain
12+
many *.scip (https://github.com/sourcegraph/scip) and
13+
*.semanticdb (https://scalameta.org/docs/semanticdb/specification.html) files.
14+
These files encode information about which symbols are referenced from which
15+
locations in your source code.
16+
17+
Use the command below to merge all of these SCIP files into a single index:
18+
19+
find bazel-bin/ -type f -name '*.scip' | xargs cat > index.scip
20+
21+
Use `src code-intel upload` to upload the unified SCIP file to Sourcegraph:
22+
23+
npm install -g @sourcegraph/src
24+
export SRC_ENDPOINT=SOURCEGRAPH_URL
25+
export SRC_ACCESS_TOKEN=TOKEN_VALUE
26+
src login # confirm you are correctly authenticated
27+
src code-intel upload -file=index.scip
28+
29+
Example command to run this aspect directly:
30+
31+
bazel build //... --aspects path/to/scip_java.bzl%scip_java_aspect --output_groups=scip --define=sourceroot=$(pwd) --define=scip_java_binary=$(which scip-java) --define=java_home=$JAVA_HOME
32+
33+
To learn more about aspects: https://bazel.build/extending/aspects
34+
"""
35+
36+
def _scip_java(target, ctx):
37+
if JavaInfo not in target or not hasattr(ctx.rule.attr, "srcs"):
38+
return None
39+
40+
javac_action = None
41+
for a in target.actions:
42+
if a.mnemonic == "Javac":
43+
javac_action = a
44+
break
45+
46+
if not javac_action:
47+
return None
48+
49+
info = target[JavaInfo]
50+
compilation = info.compilation_info
51+
annotations = info.annotation_processing
52+
53+
source_files = []
54+
for src in ctx.rule.files.srcs:
55+
source_files.append(src.path)
56+
if len(source_files) == 0:
57+
return None
58+
59+
classpath = [j.path for j in compilation.compilation_classpath.to_list()]
60+
bootclasspath = [j.path for j in compilation.boot_classpath]
61+
62+
processorpath = []
63+
processors = []
64+
if annotations and annotations.enabled:
65+
processorpath += [j.path for j in annotations.processor_classpath.to_list()]
66+
processors = annotations.processor_classnames
67+
68+
build_config = struct(**{
69+
"javaHome": ctx.var["java_home"],
70+
"classpath": classpath,
71+
"sourceFiles": source_files,
72+
"javacOptions": compilation.javac_options,
73+
"processors": processors,
74+
"processorpath": processorpath,
75+
"bootclasspath": bootclasspath,
76+
})
77+
build_config_path = ctx.actions.declare_file(ctx.label.name + ".scip.json")
78+
79+
scip_output = ctx.actions.declare_file(ctx.label.name + ".scip")
80+
targetroot = ctx.actions.declare_directory(ctx.label.name + ".semanticdb")
81+
ctx.actions.write(
82+
output = build_config_path,
83+
content = build_config.to_json(),
84+
)
85+
ctx.actions.run_shell(
86+
command = "\"{}\" index --no-cleanup --index-semanticdb.allow-empty-index --cwd \"{}\" --targetroot {} --scip-config \"{}\" --output \"{}\"".format(
87+
ctx.var["scip_java_binary"],
88+
ctx.var["sourceroot"],
89+
targetroot.path,
90+
build_config_path.path,
91+
scip_output.path,
92+
),
93+
env = {
94+
"JAVA_HOME": ctx.var["java_home"],
95+
"NO_PROGRESS_BAR": "true",
96+
},
97+
inputs = [build_config_path],
98+
outputs = [scip_output, targetroot],
99+
)
100+
101+
return scip_output
102+
103+
def _scip_java_aspect(target, ctx):
104+
scip = _scip_java(target, ctx)
105+
if not scip:
106+
return struct()
107+
return [OutputGroupInfo(scip = [scip])]
108+
109+
scip_java_aspect = aspect(
110+
_scip_java_aspect,
111+
)
112+
113+
def _scip_java_impl(ctx):
114+
output = ctx.attr.compilation[OutputGroupInfo]
115+
return [
116+
OutputGroupInfo(scip = output.scip),
117+
DefaultInfo(files = output.scip),
118+
]
119+
120+
scip_java = rule(
121+
implementation = _scip_java_impl,
122+
attrs = {"compilation": attr.label(aspects = [scip_java_aspect])},
123+
)

scip-java/src/main/scala/com/sourcegraph/scip_java/Embedded.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ object Embedded {
112112
}
113113
}
114114

115+
def bazelAspectFile(tmpDir: Path): String = {
116+
val tmpFile = copyFile(tmpDir, "scip-java/scip_java.bzl")
117+
val contents =
118+
new String(Files.readAllBytes(tmpFile), StandardCharsets.UTF_8)
119+
Files.deleteIfExists(tmpFile)
120+
contents
121+
}
122+
115123
private def copyFile(tmpDir: Path, filename: String): Path = {
116124
val in = this.getClass.getResourceAsStream(s"/$filename")
117125
val out = tmpDir.resolve(filename)

0 commit comments

Comments
 (0)