Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Special handling of Bazel with source generators #602

Merged
merged 5 commits into from
Jul 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/bazel-example/WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ maven_install(
artifacts = [
"com.google.protobuf:protobuf-java:3.15.6", # Required dependency by scip-java.
"com.google.protobuf:protobuf-java-util:3.15.6", # Required dependency by scip-java.
"com.google.guava:guava:31.0-jre", # Not required dependency, only used by tests.
# These dependencies are only required for the tests
"com.google.guava:guava:31.0-jre",
"com.google.auto.value:auto-value:1.5.3",
],
repositories = [
"https://repo1.maven.org/maven2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

import com.google.auto.value.AutoValue;

@AutoValue
abstract class Animal {
static Animal create(String name, int numberOfLegs) {
return new AutoValue_Animal(name, numberOfLegs);
}

abstract String name();
abstract int numberOfLegs();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
load("@scip_java//semanticdb-javac:defs.bzl", "java_library")
load("@rules_java//java:defs.bzl", "java_plugin")

package(
default_visibility = ["//visibility:public"],
)

java_library(
name = "animal",
srcs = ["Animal.java"],
deps = [
":autovalue",
],
)

java_plugin(
name = "autovalue_plugin",
processor_class = "com.google.auto.value.processor.AutoValueProcessor",
deps = [
"@maven//:com_google_auto_value_auto_value",
],
)

java_library(
name = "autovalue",
exported_plugins = [
":autovalue_plugin",
],
neverlink = 1,
exports = [
"@maven//:com_google_auto_value_auto_value",
],
)

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.sun.tools.javac.util.Context;

import static javax.tools.StandardLocation.CLASS_OUTPUT;
import static javax.tools.StandardLocation.SOURCE_OUTPUT;

/** Settings that can be configured alongside the -Xplugin compiler option. */
public class SemanticdbJavacOptions {
Expand All @@ -25,6 +26,7 @@ public class SemanticdbJavacOptions {
public final ArrayList<String> errors;
public boolean alreadyReportedErrors = false;
public UriScheme uriScheme = UriScheme.DEFAULT;
public Path generatedTargetRoot;

public static String stubClassName = "META-INF-semanticdb-stub";

Expand All @@ -43,13 +45,14 @@ public static String missingRequiredDirectoryOption(String option) {

public static SemanticdbJavacOptions parse(String[] args, Context ctx) {
SemanticdbJavacOptions result = new SemanticdbJavacOptions();

boolean useJavacClassesDir = false;
for (String arg : args) {
if (arg.startsWith("-targetroot:")) {
String argValue = arg.substring("-targetroot:".length());
if (argValue.equals(JAVAC_CLASSES_DIR_ARG)) {
useJavacClassesDir = true;
result.targetroot = getJavacClassesDir(result, ctx);
result.targetroot = getJavacClassesDir(result, ctx).classes;
} else {
result.targetroot = Paths.get(argValue);
}
Expand All @@ -60,7 +63,9 @@ public static SemanticdbJavacOptions parse(String[] args, Context ctx) {
} else if (arg.equals("-build-tool:bazel")) {
result.uriScheme = UriScheme.BAZEL;
useJavacClassesDir = true;
result.targetroot = getJavacClassesDir(result, ctx);
TargetPaths paths = getJavacClassesDir(result, ctx);
result.targetroot = paths.classes;
result.generatedTargetRoot = paths.sources;
} else if (arg.equals("-text:on")) {
result.includeText = true;
} else if (arg.equals("-text:off")) {
Expand All @@ -79,9 +84,11 @@ public static SemanticdbJavacOptions parse(String[] args, Context ctx) {
result.errors.add(missingRequiredDirectoryOption("targetroot"));
}
if (!isSourcerootDefined(result)) {
// When using -build-tool:bazel, the sourceroot is automatically inferred from the first
// When using -build-tool:bazel, the sourceroot is automatically inferred from
// the first
// compilation unit.
// See `SemanticdbTaskListener.inferBazelSourceroot()` for the method that infers the
// See `SemanticdbTaskListener.inferBazelSourceroot()` for the method that
// infers the
// sourceroot.
result.errors.add(missingRequiredDirectoryOption("sourceroot"));
}
Expand All @@ -95,14 +102,18 @@ private static boolean isSourcerootDefined(SemanticdbJavacOptions options) {
return options.sourceroot != null;
}

private static Path getJavacClassesDir(SemanticdbJavacOptions result, Context ctx) {
private static TargetPaths getJavacClassesDir(SemanticdbJavacOptions result, Context ctx) {
// I'm not aware of a better way to get the class output directory from javac
Path outputDir = null;
Path classOutputDir = null;
Path sourceOutputDir = null;
try {
JavaFileManager fm = ctx.get(JavaFileManager.class);
FileObject outputDirStub =
FileObject sourceOutputDirStub =
fm.getJavaFileForOutput(SOURCE_OUTPUT, stubClassName, JavaFileObject.Kind.SOURCE, null);
FileObject clasSOutputDirStub =
fm.getJavaFileForOutput(CLASS_OUTPUT, stubClassName, JavaFileObject.Kind.CLASS, null);
outputDir = Paths.get(outputDirStub.toUri()).toAbsolutePath().getParent();
classOutputDir = Paths.get(clasSOutputDirStub.toUri()).toAbsolutePath().getParent();
sourceOutputDir = Paths.get(sourceOutputDirStub.toUri()).toAbsolutePath().getParent();
} catch (Exception e) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
e.printStackTrace(new PrintStream(out));
Expand All @@ -112,6 +123,6 @@ private static Path getJavacClassesDir(SemanticdbJavacOptions result, Context ct
JAVAC_CLASSES_DIR_ARG, out.toString());
result.errors.add(errorMsg);
}
return outputDir;
return new TargetPaths(classOutputDir, sourceOutputDir);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.Trees;
import com.sun.source.util.TaskEvent;

import javax.tools.Diagnostic;
import java.io.ByteArrayOutputStream;
Expand Down Expand Up @@ -30,8 +31,29 @@ public void exception(Throwable e, Tree tree, CompilationUnitTree root) {
trees.printMessage(Diagnostic.Kind.ERROR, baos.toString(), tree, root);
}

public void exception(Throwable e, TaskEvent task) {
this.exception(e, task.getCompilationUnit(), task.getCompilationUnit());
}

public void info(String message, TaskEvent e) {
trees.printMessage(
Diagnostic.Kind.NOTE,
"semanticdb-javac: " + message,
e.getCompilationUnit(),
e.getCompilationUnit());
}

public void error(String message, TaskEvent e) {
trees.printMessage(
Diagnostic.Kind.ERROR,
"semanticdb-javac: " + message,
e.getCompilationUnit(),
e.getCompilationUnit());
}

public void error(String message, Tree tree, CompilationUnitTree root) {
// NOTE(olafur): ideally, this message should be reported as a compiler diagnostic, but I dind't
// NOTE(olafur): ideally, this message should be reported as a compiler
// diagnostic, but I dind't
// find
// the reporter API so the message goes to stderr instead for now.
trees.printMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public final class SemanticdbTaskListener implements TaskListener {
private final SemanticdbReporter reporter;
private final JavacTypes javacTypes;
private final Trees trees;
private boolean sourceGeneratorsMessageIsLogged = false;

public SemanticdbTaskListener(
SemanticdbJavacOptions options,
Expand All @@ -50,11 +51,7 @@ public void finished(TaskEvent e) {
if (!options.alreadyReportedErrors) {
options.alreadyReportedErrors = true;
for (String error : options.errors) {
trees.printMessage(
Diagnostic.Kind.ERROR,
"semanticdb-javac: " + error,
e.getCompilationUnit(),
e.getCompilationUnit());
reporter.error(error, e);
}
}
return;
Expand All @@ -63,9 +60,12 @@ public void finished(TaskEvent e) {
try {
onFinishedAnalyze(e);
} catch (Throwable ex) {
// Catch exceptions because we don't want to stop the compilation even if this plugin has a
// bug. We report the full stack trace because it's helpful for bug reports. Exceptions
// should only happen in *exceptional* situations and they should be reported upstream.
// Catch exceptions because we don't want to stop the compilation even if this
// plugin has a
// bug. We report the full stack trace because it's helpful for bug reports.
// Exceptions
// should only happen in *exceptional* situations and they should be reported
// upstream.
Throwable throwable = ex;
if (e.getSourceFile() != null) {
throwable =
Expand All @@ -79,13 +79,15 @@ public void finished(TaskEvent e) {

private void onFinishedAnalyze(TaskEvent e) {
Result<Path, String> path = semanticdbOutputPath(options, e);
if (path.isOk()) {
Semanticdb.TextDocument textDocument =
new SemanticdbVisitor(task, globals, e, options, javacTypes)
.buildTextDocument(e.getCompilationUnit());
writeSemanticdb(e, path.getOrThrow(), textDocument);
} else {
reporter.error(path.getErrorOrThrow(), e.getCompilationUnit(), e.getCompilationUnit());
if (path != null) {
if (path.isOk()) {
Semanticdb.TextDocument textDocument =
new SemanticdbVisitor(task, globals, e, options, javacTypes)
.buildTextDocument(e.getCompilationUnit());
writeSemanticdb(e, path.getOrThrow(), textDocument);
} else {
reporter.error(path.getErrorOrThrow(), e);
}
}
}

Expand All @@ -112,12 +114,17 @@ public static Path absolutePathFromUri(SemanticdbJavacOptions options, JavaFileO
throw new IllegalArgumentException("unsupported URI: " + uri);
}
} else if (options.uriScheme == UriScheme.BAZEL) {
String toString = file.toString();
// This solution is hacky, and it would be very nice to use a dedicated API instead.
// The Bazel Java compiler constructs `SimpleFileObject/DirectoryFileObject` with a
// "user-friendly" name that points to the original source file and an underlying/actual
// file path in a temporary directory. We're constrained by having to use only public APIs of
// the Java compiler and `toString()` seems to be the only way to access the user-friendly
String toString = file.toString().replace(":", "/");
// This solution is hacky, and it would be very nice to use a dedicated API
// instead.
// The Bazel Java compiler constructs `SimpleFileObject/DirectoryFileObject`
// with a
// "user-friendly" name that points to the original source file and an
// underlying/actual
// file path in a temporary directory. We're constrained by having to use only
// public APIs of
// the Java compiler and `toString()` seems to be the only way to access the
// user-friendly
// path.
String[] knownBazelToStringPatterns =
new String[] {"SimpleFileObject[", "DirectoryFileObject["};
Expand Down Expand Up @@ -146,11 +153,11 @@ private void inferBazelSourceroot(JavaFileObject file) {
// /private/var/tmp/com/example/Hello.java
//
// We infer sourceroot by iterating the names of both files in reverse order
// and stop at the first entry where the two paths are different. For the
// and stop at the first entry where the two paths are different. For the
// example above, we compare "Hello.java", then "example", then "com", and
// when we reach "repo" != "tmp" then we guess that "/home/repo" is the
// sourceroot. This logic is brittle and it would be nice to use more
// dedicated APIs, but Bazel actively makes an effort to sandbox
// sourceroot. This logic is brittle and it would be nice to use more
// dedicated APIs, but Bazel actively makes an effort to sandbox
// compilation and hide access to the original workspace, which is why we
// resort to solutions like this.
int relativePathDepth = 0;
Expand Down Expand Up @@ -184,6 +191,26 @@ private Result<Path, String> semanticdbOutputPath(SemanticdbJavacOptions options
.resolveSibling(filename);
return Result.ok(semanticdbOutputPath);
} else {

if (options.uriScheme == UriScheme.BAZEL && options.generatedTargetRoot != null) {
try {
if (absolutePath.toRealPath().startsWith(options.generatedTargetRoot)) {
if (!sourceGeneratorsMessageIsLogged) {
sourceGeneratorsMessageIsLogged = true;
reporter.info(
"Usage of source generators detected - scip-java does not produce SemanticDB files for generated files.\n"
+ "This message is logged only once",
e);
}

return null;
}
} catch (IOException exc) {
reporter.exception(exc, e);
return null;
}
}

return Result.error(
String.format(
"sourceroot '%s does not contain path '%s'. To fix this problem, update the -sourceroot flag to "
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.sourcegraph.semanticdb_javac;

import java.nio.file.Path;

public class TargetPaths {
public Path classes;
public Path sources;

public TargetPaths(Path classesDir, Path sourcesDir) {
classes = classesDir;
sources = sourcesDir;
}
}