diff --git a/examples/bazel-example/WORKSPACE b/examples/bazel-example/WORKSPACE index f2b84534..3296b34e 100644 --- a/examples/bazel-example/WORKSPACE +++ b/examples/bazel-example/WORKSPACE @@ -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", diff --git a/examples/bazel-example/src/main/java/source-generator-example/Animal.java b/examples/bazel-example/src/main/java/source-generator-example/Animal.java new file mode 100644 index 00000000..575666d1 --- /dev/null +++ b/examples/bazel-example/src/main/java/source-generator-example/Animal.java @@ -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(); +} diff --git a/examples/bazel-example/src/main/java/source-generator-example/BUILD b/examples/bazel-example/src/main/java/source-generator-example/BUILD new file mode 100644 index 00000000..c02d0062 --- /dev/null +++ b/examples/bazel-example/src/main/java/source-generator-example/BUILD @@ -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", + ], +) + diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbJavacOptions.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbJavacOptions.java index bea1f043..e6ed2c16 100644 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbJavacOptions.java +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbJavacOptions.java @@ -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 { @@ -25,6 +26,7 @@ public class SemanticdbJavacOptions { public final ArrayList errors; public boolean alreadyReportedErrors = false; public UriScheme uriScheme = UriScheme.DEFAULT; + public Path generatedTargetRoot; public static String stubClassName = "META-INF-semanticdb-stub"; @@ -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); } @@ -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")) { @@ -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")); } @@ -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)); @@ -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); } } diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbReporter.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbReporter.java index 3ca71fdb..e083b2ee 100644 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbReporter.java +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbReporter.java @@ -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; @@ -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( diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java index 96e64590..f46df649 100644 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbTaskListener.java @@ -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, @@ -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; @@ -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 = @@ -79,13 +79,15 @@ public void finished(TaskEvent e) { private void onFinishedAnalyze(TaskEvent e) { Result 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); + } } } @@ -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["}; @@ -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; @@ -184,6 +191,26 @@ private Result 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 " diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/TargetPaths.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/TargetPaths.java new file mode 100644 index 00000000..c523e353 --- /dev/null +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/TargetPaths.java @@ -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; + } +}