From c2175342452533e7dcc1a50ad9870a5915631ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Wed, 4 Aug 2021 13:51:58 +0200 Subject: [PATCH] Add build tool support for sbt. Previously, the `lsif-java index` command only worked with Gradle and Maven builds. This commits adds support for sbt as well. The Scala community mostly uses sbt so this new feature is a significant milestone towards supporting Scala as well. --- build.sbt | 15 ++- .../com/sourcegraph/io/AutoDeletedFile.scala | 45 +++++++++ .../lsif_java/buildtools/BuildTool.scala | 43 ++++++--- .../buildtools/GradleBuildTool.scala | 14 ++- .../lsif_java/buildtools/LsifBuildTool.scala | 25 +++-- .../lsif_java/buildtools/MavenBuildTool.scala | 16 +++- .../lsif_java/buildtools/SbtBuildTool.scala | 94 +++++++++++++++++++ .../lsif_java/commands/IndexCommand.scala | 28 +++--- .../lsif_semanticdb/LsifSemanticdb.java | 7 +- .../lsif_semanticdb/SignatureFormatter.java | 16 +++- .../SignatureFormatterException.java | 20 ++++ project/plugins.sbt | 2 +- .../SemanticdbJavacOptions.java | 3 + .../SemanticdbTaskListener.java | 18 +++- .../semanticdb_javac/SemanticdbVisitor.java | 3 +- .../semanticdb_javac/UriScheme.java | 6 ++ .../scala/tests/MissingBuildToolSuite.scala | 2 +- .../test/scala/tests/SbtBuildToolSuite.scala | 40 ++++++++ 18 files changed, 341 insertions(+), 56 deletions(-) create mode 100644 lsif-java/src/main/scala/com/sourcegraph/io/AutoDeletedFile.scala create mode 100644 lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/SbtBuildTool.scala create mode 100644 lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/SignatureFormatterException.java create mode 100644 semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/UriScheme.java create mode 100644 tests/buildTools/src/test/scala/tests/SbtBuildToolSuite.scala diff --git a/build.sbt b/build.sbt index a7ae18cc..a323f743 100644 --- a/build.sbt +++ b/build.sbt @@ -17,7 +17,7 @@ lazy val V = def scala211 = "2.11.12" def scala3 = "3.0.1" def metals = "0.10.6-M1" - def scalameta = "4.4.25" + def scalameta = "4.4.26" def testcontainers = "0.39.3" def requests = "0.6.5" } @@ -145,8 +145,12 @@ lazy val cli = project buildInfoKeys := Seq[BuildInfoKey]( version, + sbtVersion, scalaVersion, - "mtags" -> V.metals, + "sbtSourcegraphVersion" -> + com.sourcegraph.sbtsourcegraph.BuildInfo.version, + "semanticdbVersion" -> V.scalameta, + "mtagsVersion" -> V.metals, "scala211" -> V.scala211, "scala212" -> V.scala212, "scala213" -> V.scala213, @@ -268,6 +272,7 @@ lazy val minimizedSettings = List[Def.Setting[_]]( s"-Arandomtimestamp=${System.nanoTime()}", List( s"-Xplugin:semanticdb", + s"-build-tool:sbt", s"-text:on", s"-verbose", s"-sourceroot:${(ThisBuild / baseDirectory).value}", @@ -290,7 +295,11 @@ lazy val minimized8 = project lazy val minimized15 = project .in(file("tests/minimized/.j15")) - .settings(minimizedSettings, javaToolchainVersion := "15") + .settings( + minimizedSettings, + javaToolchainVersion := "15", + Compile / javaHome := None + ) .dependsOn(agent, plugin) .disablePlugins(JavaFormatterPlugin) diff --git a/lsif-java/src/main/scala/com/sourcegraph/io/AutoDeletedFile.scala b/lsif-java/src/main/scala/com/sourcegraph/io/AutoDeletedFile.scala new file mode 100644 index 00000000..2574e0f7 --- /dev/null +++ b/lsif-java/src/main/scala/com/sourcegraph/io/AutoDeletedFile.scala @@ -0,0 +1,45 @@ +package com.sourcegraph.io + +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardOpenOption + +import scala.util.Using.Releasable + +class AutoDeletedFile private ( + val path: Path, + val oldContent: Option[Array[Byte]] +) + +object AutoDeletedFile { + def fromPath(path: Path, newContent: String): AutoDeletedFile = { + val oldContent = + if (Files.isRegularFile(path)) + Some(Files.readAllBytes(path)) + else + None + Files.createDirectories(path.getParent) + Files.write( + path, + newContent.getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ) + new AutoDeletedFile(path, oldContent) + } + implicit val releasableAutoDeletedFile: Releasable[AutoDeletedFile] = { + file => + file.oldContent match { + case Some(oldBytes) => + Files.write( + file.path, + oldBytes, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING + ) + case None => + Files.deleteIfExists(file.path) + } + } +} diff --git a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/BuildTool.scala b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/BuildTool.scala index 86784b82..15d1328d 100644 --- a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/BuildTool.scala +++ b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/BuildTool.scala @@ -1,29 +1,20 @@ package com.sourcegraph.lsif_java.buildtools +import java.nio.file.Files import java.nio.file.Path -import com.sourcegraph.io.AbsolutePath import com.sourcegraph.lsif_java.commands.IndexCommand +import com.sourcegraph.lsif_java.commands.IndexSemanticdbCommand +import os.CommandResult /** * A build tool such as Gradle, Maven or Bazel. */ abstract class BuildTool(val name: String, index: IndexCommand) { - - protected def defaultTargetroot: Path - def isHidden: Boolean = false - def buildKind: String = "" - final def sourceroot: Path = index.workingDirectory - final def targetroot: Path = - AbsolutePath - .of(index.targetroot.getOrElse(defaultTargetroot), index.workingDirectory) - def usedInCurrentDirectory(): Boolean - - def generateSemanticdb(): os.CommandResult - + def generateLsif(): Int } object BuildTool { @@ -31,8 +22,32 @@ object BuildTool { List( new GradleBuildTool(index), new MavenBuildTool(index), - new LsifBuildTool(index) + new LsifBuildTool(index), + new SbtBuildTool(index) ) def allNames: String = all(IndexCommand()).filterNot(_.isHidden).map(_.name).mkString(", ") + + def generateLsifFromTargetroot( + generateSemanticdbResult: CommandResult, + targetroot: Path, + index: IndexCommand, + buildKind: String = "" + ): Int = { + if (!Files.isDirectory(targetroot)) { + generateSemanticdbResult.exitCode + } else if (index.app.reporter.hasErrors()) { + index.app.reporter.exitCode() + } else if (generateSemanticdbResult.exitCode != 0) { + generateSemanticdbResult.exitCode + } else { + IndexSemanticdbCommand( + output = index.finalOutput, + targetroot = List(targetroot), + packagehub = index.packagehub, + buildKind = buildKind, + app = index.app + ).run() + } + } } diff --git a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/GradleBuildTool.scala b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/GradleBuildTool.scala index 24f234eb..9bf98c31 100644 --- a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/GradleBuildTool.scala +++ b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/GradleBuildTool.scala @@ -12,9 +12,6 @@ import os.CommandResult class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) { - override def defaultTargetroot: Path = - Paths.get("build", "semanticdb-targetroot") - override def usedInCurrentDirectory(): Boolean = { Files.isRegularFile(index.workingDirectory.resolve("settings.gradle")) || Files.isRegularFile(index.workingDirectory.resolve("gradlew")) || @@ -22,7 +19,16 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) { Files.isRegularFile(index.workingDirectory.resolve("build.gradle.kts")) } - override def generateSemanticdb(): CommandResult = { + override def generateLsif(): Int = { + BuildTool + .generateLsifFromTargetroot(generateSemanticdb(), targetroot, index) + } + + def targetroot: Path = index.finalTargetroot(defaultTargetroot) + + private def defaultTargetroot: Path = + Paths.get("build", "semanticdb-targetroot") + private def generateSemanticdb(): CommandResult = { val gradleWrapper: Path = index .workingDirectory .resolve( diff --git a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/LsifBuildTool.scala b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/LsifBuildTool.scala index 1f2777f4..a978c727 100644 --- a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/LsifBuildTool.scala +++ b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/LsifBuildTool.scala @@ -76,14 +76,25 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) { .getDefault .getPathMatcher("glob:**.{java,scala}") private val moduleInfo = Paths.get("module-info.java") - protected def defaultTargetroot: Path = Paths.get("target") + + override def usedInCurrentDirectory(): Boolean = + Files.isRegularFile(configFile) + override def isHidden: Boolean = true + override def generateLsif(): Int = { + BuildTool.generateLsifFromTargetroot( + generateSemanticdb(), + index.finalTargetroot(defaultTargetroot), + index, + buildKind + ) + } + + private def targetroot: Path = index.finalTargetroot(defaultTargetroot) + private def defaultTargetroot: Path = Paths.get("target") private def configFile = index.workingDirectory.resolve(LsifBuildTool.ConfigFileName) - def usedInCurrentDirectory(): Boolean = Files.isRegularFile(configFile) - override def isHidden: Boolean = true - override def buildKind: String = - parsedConfig.fold(_.kind, _ => super.buildKind) - def generateSemanticdb(): CommandResult = { + private def buildKind: String = parsedConfig.fold(_.kind, _ => "") + private def generateSemanticdb(): CommandResult = { parsedConfig match { case ValueResult(value) => clean() @@ -224,7 +235,7 @@ class LsifBuildTool(index: IndexCommand) extends BuildTool("LSIF", index) { ) } val mtags = Dependencies.resolveDependencies( - List(s"org.scalameta:mtags_${scalaVersion}:${BuildInfo.mtags}") + List(s"org.scalameta:mtags_${scalaVersion}:${BuildInfo.mtagsVersion}") ) val scalaLibrary = mtags .classpath diff --git a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/MavenBuildTool.scala b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/MavenBuildTool.scala index 8d26bfe5..5b039a7f 100644 --- a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/MavenBuildTool.scala +++ b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/MavenBuildTool.scala @@ -11,12 +11,22 @@ import com.sourcegraph.lsif_java.commands.IndexCommand import os.CommandResult class MavenBuildTool(index: IndexCommand) extends BuildTool("Maven", index) { - override def defaultTargetroot: Path = - Paths.get("target", "semanticdb-targetroot") + override def usedInCurrentDirectory(): Boolean = Files.isRegularFile(index.workingDirectory.resolve("pom.xml")) - override def generateSemanticdb(): CommandResult = { + override def generateLsif(): Int = { + BuildTool.generateLsifFromTargetroot( + generateSemanticdb(), + index.finalTargetroot(defaultTargetroot), + index + ) + } + + private def defaultTargetroot: Path = + Paths.get("target", "semanticdb-targetroot") + + private def generateSemanticdb(): CommandResult = { TemporaryFiles.withDirectory(index) { tmp => val mvnw = index.workingDirectory.resolve("mvnw") val mavenScript = diff --git a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/SbtBuildTool.scala b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/SbtBuildTool.scala new file mode 100644 index 00000000..f71f686b --- /dev/null +++ b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/SbtBuildTool.scala @@ -0,0 +1,94 @@ +package com.sourcegraph.lsif_java.buildtools + +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import java.util.Properties + +import scala.util.Using + +import com.sourcegraph.io.AutoDeletedFile +import com.sourcegraph.lsif_java.BuildInfo +import com.sourcegraph.lsif_java.commands.IndexCommand + +class SbtBuildTool(index: IndexCommand) extends BuildTool("sbt", index) { + override def usedInCurrentDirectory(): Boolean = { + Files.isRegularFile(index.workingDirectory.resolve("build.sbt")) || + sbtVersion().isDefined + } + + override def generateLsif(): Int = { + sbtVersion() match { + case Some(version) => + if (isSupportedSbtVersion(version)) { + unconditionallyGenerateLsif() + } else { + failFast( + s"Unsupported sbt version '$version'. " + + s"To fix this problem, upgrade to sbt ${BuildInfo.sbtVersion} and try again." + ) + } + case None => + failFast( + s"No sbt version detected. " + + s"To fix this problem, run the following command and try again: " + + s"echo 'sbt.version=${BuildInfo.sbtVersion}' >> project/build.properties" + ) + } + } + + private def failFast(message: String): Int = { + index.app.error(message) + 1 + } + + private def unconditionallyGenerateLsif(): Int = + Using.resource(sourcegraphSbtPluginFile()) { _ => + val sourcegraphLsif = index + .process(List("sbt", "sourcegraphEnable", "sourcegraphLsif")) + val inputDump = index + .workingDirectory + .resolve("target") + .resolve("sbt-sourcegraph") + .resolve("dump.lsif") + if (sourcegraphLsif.exitCode == 0 && Files.isRegularFile(inputDump)) { + val outputDump = index.workingDirectory.resolve("dump.lsif") + Files.copy(inputDump, outputDump, StandardCopyOption.REPLACE_EXISTING) + index.app.info(outputDump.toString) + } + sourcegraphLsif.exitCode + } + + private def isSupportedSbtVersion(version: String): Boolean = { + (!version.startsWith("0.13") || version.startsWith("0.13.17")) && + !version.startsWith("1.0") && !version.startsWith("1.1") + } + + private def sbtVersion(): Option[String] = { + val buildProperties = index + .workingDirectory + .resolve("project") + .resolve("build.properties") + if (Files.isRegularFile(buildProperties)) { + val props = new Properties() + val in = Files.newInputStream(buildProperties) + try props.load(in) + finally in.close() + Option(props.getProperty("sbt.version")) + } else { + None + } + } + + private def sourcegraphSbtPluginFile(): AutoDeletedFile = { + val addSbtPluginFile = index + .workingDirectory + .resolve("project") + .resolve("sourcegraph_generated.sbt") + val version = BuildInfo.sbtSourcegraphVersion + AutoDeletedFile.fromPath( + addSbtPluginFile, + s"""addSbtPlugin("com.sourcegraph" % "sbt-sourcegraph" % "$version") + |""".stripMargin + ) + } +} diff --git a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/commands/IndexCommand.scala b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/commands/IndexCommand.scala index bcddd847..07cc2e52 100644 --- a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/commands/IndexCommand.scala +++ b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/commands/IndexCommand.scala @@ -66,7 +66,16 @@ case class IndexCommand( shellable: Shellable, env: Map[String, String] = Map.empty ): CommandResult = { - app.out.println(Color.DarkGray(shellable.value.mkString("$ ", " ", ""))) + val commandSyntax = shellable + .value + .map { line => + if (line.contains(" ")) + s"""'$line'""" + else + line + } + .mkString("$ ", " ", "") + app.out.println(Color.DarkGray(commandSyntax)) app .process(shellable) .call( @@ -149,22 +158,7 @@ case class IndexCommand( } 1 case tool :: Nil => - val generateSemanticdbResult = tool.generateSemanticdb() - if (!Files.isDirectory(tool.targetroot)) { - generateSemanticdbResult.exitCode - } else if (app.reporter.hasErrors()) { - app.reporter.exitCode() - } else if (generateSemanticdbResult.exitCode != 0) { - generateSemanticdbResult.exitCode - } else { - IndexSemanticdbCommand( - output = finalOutput, - targetroot = List(tool.targetroot), - packagehub = packagehub, - buildKind = tool.buildKind, - app = app - ).run() - } + tool.generateLsif() case many => val names = many.map(_.name).mkString(", ") app.error( diff --git a/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdb.java b/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdb.java index 729b51ef..3c68f94e 100644 --- a/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdb.java +++ b/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdb.java @@ -1,5 +1,6 @@ package com.sourcegraph.lsif_semanticdb; +import com.google.protobuf.CodedInputStream; import com.sourcegraph.lsif_protocol.MarkupKind; import com.sourcegraph.semanticdb_javac.Semanticdb; import com.sourcegraph.semanticdb_javac.Semanticdb.SymbolInformation; @@ -188,11 +189,13 @@ private Integer processDocumentUnsafe( private Stream parseTextDocument(Path semanticdbPath) { try { - return Semanticdb.TextDocuments.parseFrom(Files.readAllBytes(semanticdbPath)) - .getDocumentsList().stream() + CodedInputStream in = CodedInputStream.newInstance(Files.readAllBytes(semanticdbPath)); + in.setRecursionLimit(1000); + return Semanticdb.TextDocuments.parseFrom(in).getDocumentsList().stream() .filter(sdb -> !sdb.getOccurrencesList().isEmpty()) .map(sdb -> new LsifTextDocument(semanticdbPath, sdb, options.sourceroot)); } catch (IOException e) { + options.reporter.error("invalid protobuf: " + semanticdbPath); options.reporter.error(e); return Stream.empty(); } diff --git a/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/SignatureFormatter.java b/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/SignatureFormatter.java index f47646ef..3112152e 100644 --- a/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/SignatureFormatter.java +++ b/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/SignatureFormatter.java @@ -52,6 +52,14 @@ public SignatureFormatter(SymbolInformation symbolInformation, Symtab symtab) { } public String formatSymbol() { + try { + return formatSymbolUnsafe(); + } catch (Exception e) { + throw new SignatureFormatterException(symbolInformation, e); + } + } + + private String formatSymbolUnsafe() { Signature signature = symbolInformation.getSignature(); if (signature.hasClassSignature()) { formatClassSignature(signature.getClassSignature()); @@ -243,6 +251,7 @@ private void formatMethodSignature(MethodSignature methodSignature) { } private String formatTermParameter(SymbolInformation info) { + if (info == null) return ""; if (isScala) { return info.getDisplayName() + ": " @@ -354,7 +363,10 @@ private void formatTypeParameterSignature(TypeSignature typeSignature) { private List getSymlinks(Scope scope) { ArrayList symlinks = new ArrayList<>(); for (int i = 0; i < scope.getSymlinksCount(); i++) { - symlinks.add(symtab.symbols.get(scope.getSymlinks(i))); + SymbolInformation info = symtab.symbols.get(scope.getSymlinks(i)); + if (info != null) { + symlinks.add(info); + } } return symlinks; } @@ -549,7 +561,7 @@ private String formatType(Type type) { } else { b.append( typeRef.getTypeArgumentsList().stream() - .limit(n) + .limit(Math.max(0, n)) .map(this::formatType) .collect(Collectors.joining(", ", "(", ")"))); } diff --git a/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/SignatureFormatterException.java b/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/SignatureFormatterException.java new file mode 100644 index 00000000..636f8c20 --- /dev/null +++ b/lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/SignatureFormatterException.java @@ -0,0 +1,20 @@ +package com.sourcegraph.lsif_semanticdb; + +import com.sourcegraph.semanticdb_javac.Semanticdb; + +public class SignatureFormatterException extends RuntimeException { + public SignatureFormatterException( + Semanticdb.SymbolInformation symbolInformation, Throwable cause) { + super( + String.format( + "failed to format symbol '%s'\n%s", symbolInformation.getSymbol(), symbolInformation), + cause); + } + + @Override + public synchronized Throwable fillInStackTrace() { + // This exception doesn't have a relevant stack trace. The cause exception + // already points to the culprit filename and line number. + return this; + } +} diff --git a/project/plugins.sbt b/project/plugins.sbt index b00458d2..ca2efc13 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -6,7 +6,7 @@ addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.2.21") addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.29") addSbtPlugin("com.thesamet" % "sbt-protoc" % "1.0.0") addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.4.8-98-e7d4e01e") -addSbtPlugin("com.sourcegraph" % "sbt-sourcegraph" % "0.2.1") +addSbtPlugin("com.sourcegraph" % "sbt-sourcegraph" % "0.3.0-M2") addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % "0.6.0") addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") 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 e124e555..4867f721 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 @@ -23,6 +23,7 @@ public class SemanticdbJavacOptions { public boolean includeText = false; public boolean verboseEnabled = false; public final ArrayList errors; + public UriScheme uriScheme = UriScheme.DEFAULT; public static String stubClassName = "META-INF-semanticdb-stub"; @@ -53,6 +54,8 @@ public static SemanticdbJavacOptions parse(String[] args, Context ctx) { } } else if (arg.startsWith("-sourceroot:")) { result.sourceroot = Paths.get(arg.substring("-sourceroot:".length())).normalize(); + } else if (arg.equals("-build-tool:sbt")) { + result.uriScheme = UriScheme.SBT; } else if (arg.equals("-text:on")) { result.includeText = true; } else if (arg.equals("-text:off")) { 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 de632542..6133cb7e 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 @@ -7,6 +7,7 @@ import com.sun.tools.javac.model.JavacTypes; import java.io.IOException; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -73,8 +74,23 @@ private void writeSemanticdb(Path output, Semanticdb.TextDocument textDocument) } } + public static Path absolutePathFromUri(SemanticdbJavacOptions options, URI uri) { + if (options.uriScheme == UriScheme.SBT + && uri.getScheme().equals("vf") + && uri.toString().startsWith("vf://tmp/")) { + String[] parts = uri.toString().split("/", 5); + if (parts.length == 5) { + return options.sourceroot.resolve(Paths.get(parts[4])); + } else { + throw new IllegalArgumentException("unsupported URI: " + uri); + } + } else { + return Paths.get(uri); + } + } + private Result semanticdbOutputPath(SemanticdbJavacOptions options, TaskEvent e) { - Path absolutePath = Paths.get(e.getSourceFile().toUri()).normalize(); + Path absolutePath = absolutePathFromUri(options, e.getSourceFile().toUri()); if (absolutePath.startsWith(options.sourceroot)) { Path relativePath = options.sourceroot.relativize(absolutePath); String filename = relativePath.getFileName().toString() + ".semanticdb"; diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java index 3b75c5b0..32795cb6 100644 --- a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbVisitor.java @@ -400,7 +400,8 @@ private Semanticdb.Access semanticdbAccess(Symbol sym) { } private String semanticdbUri() { - Path absolutePath = Paths.get(event.getSourceFile().toUri()); + Path absolutePath = + SemanticdbTaskListener.absolutePathFromUri(options, event.getSourceFile().toUri()); Path relativePath = options.sourceroot.relativize(absolutePath); StringBuilder out = new StringBuilder(); Iterator it = relativePath.iterator(); diff --git a/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/UriScheme.java b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/UriScheme.java new file mode 100644 index 00000000..d633209c --- /dev/null +++ b/semanticdb-javac/src/main/java/com/sourcegraph/semanticdb_javac/UriScheme.java @@ -0,0 +1,6 @@ +package com.sourcegraph.semanticdb_javac; + +public enum UriScheme { + DEFAULT, + SBT +} diff --git a/tests/buildTools/src/test/scala/tests/MissingBuildToolSuite.scala b/tests/buildTools/src/test/scala/tests/MissingBuildToolSuite.scala index e60ad1d3..24e775a6 100644 --- a/tests/buildTools/src/test/scala/tests/MissingBuildToolSuite.scala +++ b/tests/buildTools/src/test/scala/tests/MissingBuildToolSuite.scala @@ -5,7 +5,7 @@ class MissingBuildToolSuite extends BaseBuildToolSuite { "basic", List("index"), expectedOutput = - s"""|error: No build tool detected in workspace '/workingDirectory'. At the moment, the only supported build tools are: Gradle, Maven. + s"""|error: No build tool detected in workspace '/workingDirectory'. At the moment, the only supported build tools are: Gradle, Maven, sbt. |""".stripMargin, workingDirectoryLayout = "" ) diff --git a/tests/buildTools/src/test/scala/tests/SbtBuildToolSuite.scala b/tests/buildTools/src/test/scala/tests/SbtBuildToolSuite.scala new file mode 100644 index 00000000..99d6b40e --- /dev/null +++ b/tests/buildTools/src/test/scala/tests/SbtBuildToolSuite.scala @@ -0,0 +1,40 @@ +package tests + +import munit.IgnoreSuite + +// Ignored because the sbt-sourcegraph plugin adds the -build-tool:sbt flag, which +// is yet not supported by any stable release of lsif-java. We can un-ignore this +// test suite after the next release. +@IgnoreSuite +class SbtBuildToolSuite extends BaseBuildToolSuite { + List("1.5.2", "0.13.17").foreach { version => + checkBuild( + s"basic-$version", + s"""|/build.sbt + |scalaVersion := "2.11.9" + |libraryDependencies += "junit" % "junit" % "4.13.2" + |/project/build.properties + |sbt.version=1.5.2 + |/src/main/java/example/ExampleJava.java + |package example; + |import org.junit.Assert; + |public class ExampleJava {} + |/src/main/scala/example/ExampleScala.scala + |package example + |import org.junit.Assert + |class ExampleScala() + |/src/test/java/example/ExampleJavaSuite.java + |package example; + |public class ExampleSuite {} + |/src/test/scala/example/ExampleScalaSuite.java + |package example + |class ExampleSuite() {} + |""".stripMargin, + expectedSemanticdbFiles = 4, + expectedPackages = + """|maven:junit:junit:4.13.1 + |""".stripMargin + ) + } + +}