From ddb6d19337092e64c254eb10518f933bf3475c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Thu, 26 Aug 2021 11:50:13 +0200 Subject: [PATCH 1/2] Add support for Gradle Scala projects Previously, running `lsif-java index` in a Gradle project would only index the Java parts of the codebase. Now, the command also indexes Scala files. Fixes #301 --- build.sbt | 6 ++ docs/getting-started.md | 12 ++-- .../buildtools/GradleBuildTool.scala | 68 ++++++++++++++++++- .../buildtools/GradleJavaToolchains.scala | 17 ++++- .../semanticdb_javac/SemanticdbAgent.java | 2 + .../SemanticdbTaskListener.java | 11 +-- .../scala/tests/GradleBuildToolSuite.scala | 29 ++++++++ 7 files changed, 126 insertions(+), 19 deletions(-) diff --git a/build.sbt b/build.sbt index a323f743..5612a755 100644 --- a/build.sbt +++ b/build.sbt @@ -147,6 +147,12 @@ lazy val cli = project version, sbtVersion, scalaVersion, + "semanticdbScalacVersions" -> + com + .sourcegraph + .sbtsourcegraph + .Versions + .cachedSemanticdbVersionsByScalaVersion, "sbtSourcegraphVersion" -> com.sourcegraph.sbtsourcegraph.BuildInfo.version, "semanticdbVersion" -> V.scalameta, diff --git a/docs/getting-started.md b/docs/getting-started.md index 0ceaea4f..a350ac2c 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -134,11 +134,11 @@ com.sourcegraph.lsif_java.LsifJava.printHelp(Console.out) ## Supported programming languages -| Programming language | Gradle | Maven | sbt | Tracking issue | -| -------------------- | ------ | ----- | --- | --------------------------------------------------------------------------------------------------------------------------- | -| Java | ✅ | ✅ | ✅ | | -| Scala | ❌ | ❌ | ✅ | [Maven](https://github.com/sourcegraph/lsif-java/issues/301), [Gradle](https://github.com/sourcegraph/lsif-java/issues/302) | -| Kotlin | ❌ | ❌ | ❌ | [#302](https://github.com/sourcegraph/lsif-java/issues/302) | +| Programming language | Gradle | Maven | sbt | Tracking issue | +| -------------------- | ------ | ----- | --- | ----------------------------------------------------------- | +| Java | ✅ | ✅ | ✅ | | +| Scala | ✅ | ❌ | ✅ | [#302](https://github.com/sourcegraph/lsif-java/issues/302) | +| Kotlin | ❌ | ❌ | ❌ | [#304](https://github.com/sourcegraph/lsif-java/issues/304) | ### Java @@ -216,7 +216,7 @@ projects. However, the following Gradle integrations are not yet supported: | ----------- | --------- | -------------------------------------------------------------------------------- | | Android | ❌ | [sourcegraph/lsif-java#304](https://github.com/sourcegraph/lsif-java/issues/304) | | Kotlin | ❌ | [sourcegraph/lsif-java#177](https://github.com/sourcegraph/lsif-java/issues/177) | -| Scala | ❌ | [sourcegraph/lsif-java#302](https://github.com/sourcegraph/lsif-java/issues/302) | +| Scala | ✅ | | ### Maven 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 9bf98c31..93507c27 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 @@ -6,6 +6,8 @@ import java.nio.file._ import scala.collection.mutable.ListBuffer import scala.util.Properties +import com.sourcegraph.io.DeleteVisitor +import com.sourcegraph.lsif_java.BuildInfo import com.sourcegraph.lsif_java.Embedded import com.sourcegraph.lsif_java.commands.IndexCommand import os.CommandResult @@ -78,9 +80,20 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) { buildCommand += s"-Porg.gradle.java.installations.paths=${toolchains.paths()}" } - buildCommand ++= index.finalBuildCommand(List("clean", "compileTestJava")) + buildCommand ++= + index.finalBuildCommand( + List[Option[String]]( + Some("clean"), + Some("compileTestJava"), + if (toolchains.isScalaEnabled) + Some("compileTestScala") + else + None + ).flatten + ) buildCommand += lsifJavaDependencies + Files.walkFileTree(targetroot, new DeleteVisitor()) val result = index.process(buildCommand, env = Map("TERM" -> "dumb")) printDebugLogs(toolchains.tmp) Embedded @@ -112,6 +125,11 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) { val agentpath = Embedded.agentJar(tmp) val pluginpath = Embedded.semanticdbJar(tmp) + def handleExceptionGroovySyntax(): String = + if (index.verbose) + "e.printStackTrace()" + else + "" val dependenciesPath = targetroot.resolve("dependencies.txt") Files.deleteIfExists(dependenciesPath) val script = @@ -137,6 +155,22 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) { | // By enabling the SemanticDB Java agent on the Zinc daemon process, we manage | // to configure Zinc to use the semanticdb-javac compiler plugin for Java compilation. | tasks.withType(ScalaCompile) { + | + | if (scalaCompileOptions.additionalParameters == null) scalaCompileOptions.additionalParameters = [] + | try { + | def scalaVersion = lsifJavaScalaVersion(project, configurations) + | def semanticdbVersion = lsifJavaSemanticdbScalacVersions(scalaVersion) + | def semanticdbScalacDependency ="org.scalameta:semanticdb-scalac_$$scalaVersion:$$semanticdbVersion" + | def semanticdbScalac = project.configurations.detachedConfiguration(dependencies.create(semanticdbScalacDependency)).files[0] + | scalaCompileOptions.additionalParameters << '-Xplugin:' + semanticdbScalac + | scalaCompileOptions.additionalParameters << '-P:semanticdb:sourceroot:$sourceroot' + | scalaCompileOptions.additionalParameters << '-P:semanticdb:targetroot:$targetroot' + | scalaCompileOptions.additionalParameters << '-P:semanticdb:exclude:(src/play/twirl|src/play/routes)' // Ignore autogenerated Playframework files + | scalaCompileOptions.additionalParameters << '-P:semanticdb:failures:warning' + | scalaCompileOptions.additionalParameters << '-Xplugin-require:semanticdb' + | } catch (Exception e) { + | ${handleExceptionGroovySyntax()} + | } | scalaCompileOptions.forkOptions.with { | jvmArgs << '-javaagent:$agentpath' | jvmArgs << '-Dsemanticdb.pluginpath=$pluginpath' @@ -180,11 +214,41 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) { | } | } |} - """.stripMargin + |def lsifJavaSemanticdbScalacVersions(scalaVersion) { + | ${semanticdbScalacGroovySyntax()}[scalaVersion] + |} + |def lsifJavaScalaVersion(project, configurations) { + | for (config in configurations) { + | if (config.name == "zinc") continue + | if (config.canBeResolved) { + | def artifacts = config.incoming.artifactView { view -> + | view.lenient = true + | }.artifacts + | for (artifact in artifacts) { + | def id = artifact.id.componentIdentifier + | if (id instanceof org.gradle.api.artifacts.component.ModuleComponentIdentifier + | && id.group == "org.scala-lang" + | && id.module == "scala-library") { + | return id.version + | } + | } + | } + | } + | return null + |} + | """.stripMargin Files.write( tmp.resolve("init-script.gradle"), script.getBytes(StandardCharsets.UTF_8) ) } + def semanticdbScalacGroovySyntax(): String = + BuildInfo + .semanticdbScalacVersions + .map { case (key, value) => + s"'$key':'$value'" + } + .mkString("[", ", ", "]") + } diff --git a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/GradleJavaToolchains.scala b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/GradleJavaToolchains.scala index 63e9c197..81ae6bc9 100644 --- a/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/GradleJavaToolchains.scala +++ b/lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/GradleJavaToolchains.scala @@ -14,6 +14,7 @@ case class GradleJavaToolchains( tool: GradleBuildTool, index: IndexCommand, gradleVersion: Option[String], + isScalaEnabled: Boolean, gradleCommand: String, tmp: Path ) { @@ -55,6 +56,7 @@ object GradleJavaToolchains { ): GradleJavaToolchains = { val scriptPath = tmp.resolve("java-toolchains.gradle") val toolchainsPath = tmp.resolve("java-toolchains.txt") + val scalaEnabledPath = tmp.resolve("scala-enabled.txt") val gradleVersionPath = tmp.resolve("gradle-version.txt") val taskName = "lsifDetectJavaToolchains" val script = @@ -70,7 +72,7 @@ object GradleJavaToolchains { |} |allprojects { | task $taskName { - | def out = java.nio.file.Paths.get('$toolchainsPath') + | def toolchainsOut = java.nio.file.Paths.get('$toolchainsPath') | doLast { | tasks.withType(JavaCompile) { | try { @@ -79,7 +81,7 @@ object GradleJavaToolchains { | def version = javaCompiler.get().getMetadata().getLanguageVersion().asInt() | def line = "$$version $$path" | java.nio.file.Files.write( - | out, + | toolchainsOut, | [line], | java.nio.file.StandardOpenOption.APPEND, | java.nio.file.StandardOpenOption.CREATE) @@ -87,6 +89,16 @@ object GradleJavaToolchains { | // Ignore errors. | } | } + | boolean isScalaEnabled = project.plugins.any { + | it.getClass().getName().endsWith("org.gradle.api.plugins.scala.ScalaPlugin") + | } + | if (isScalaEnabled) { + | java.nio.file.Files.write( + | java.nio.file.Paths.get('$scalaEnabledPath'), + | ["true"], + | java.nio.file.StandardOpenOption.APPEND, + | java.nio.file.StandardOpenOption.CREATE) + | } | } | } |} @@ -119,6 +131,7 @@ object GradleJavaToolchains { tool, index, gradleVersion = gradleVersion, + isScalaEnabled = Files.isRegularFile(scalaEnabledPath), gradleCommand = gradleCommand, tmp = tmp ) diff --git a/semanticdb-agent/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbAgent.java b/semanticdb-agent/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbAgent.java index e147bcc1..db624aa2 100644 --- a/semanticdb-agent/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbAgent.java +++ b/semanticdb-agent/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbAgent.java @@ -7,12 +7,14 @@ import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; +import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; +import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; 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 6133cb7e..4ebcc7e1 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 @@ -68,6 +68,7 @@ private void writeSemanticdb(Path output, Semanticdb.TextDocument textDocument) try { byte[] bytes = Semanticdb.TextDocuments.newBuilder().addDocuments(textDocument).build().toByteArray(); + Files.createDirectories(output.getParent()); Files.write(output, bytes); } catch (IOException e) { reporter.exception(e); @@ -101,15 +102,7 @@ private Result semanticdbOutputPath(SemanticdbJavacOptions options .resolve("semanticdb") .resolve(relativePath) .resolveSibling(filename); - try { - Files.createDirectories(semanticdbOutputPath.getParent()); - return Result.ok(semanticdbOutputPath); - } catch (IOException exception) { - return Result.error( - String.format( - "failed to create parent directory for '%s'. Error message: %s", - semanticdbOutputPath, exception.getMessage())); - } + return Result.ok(semanticdbOutputPath); } else { return Result.error( String.format( diff --git a/tests/buildTools/src/test/scala/tests/GradleBuildToolSuite.scala b/tests/buildTools/src/test/scala/tests/GradleBuildToolSuite.scala index 8aa5ae0f..f29edbd2 100644 --- a/tests/buildTools/src/test/scala/tests/GradleBuildToolSuite.scala +++ b/tests/buildTools/src/test/scala/tests/GradleBuildToolSuite.scala @@ -233,4 +233,33 @@ class GradleBuildToolSuite extends BaseBuildToolSuite { 2, initCommand = gradleVersion("6.8.3") ) + + checkBuild( + "scala", + """|/build.gradle + |plugins { + | id 'scala' + |} + |repositories { + | mavenCentral() + |} + |dependencies { + | implementation 'org.scala-lang:scala-library:2.12.12' + |} + |/src/main/java/foo/JExample.java + |package foo; + |public class JExample {} + |/src/main/scala/foo/Example.scala + |package foo + |object Example {} + |/src/test/java/foo/JExampleSuite.java + |package foo; + |public class JExampleSuite {} + |/src/test/scala/foo/ExampleSuite.scala + |package foo + |class ExampleSuite {} + |""".stripMargin, + 4 + ) + } From b226a3e7e9123ac9a77172866f1cd013ddba09e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20P=C3=A1ll=20Geirsson?= Date: Sat, 28 Aug 2021 00:15:12 +0200 Subject: [PATCH 2/2] Handle Scala 2.12.3 --- .../sourcegraph/lsif_java/buildtools/GradleBuildTool.scala | 6 ++++-- .../com/sourcegraph/semanticdb_javac/SemanticdbAgent.java | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) 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 93507c27..1b018eed 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 @@ -246,9 +246,11 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) { def semanticdbScalacGroovySyntax(): String = BuildInfo .semanticdbScalacVersions + .removed( + "2.12.3" + ) // Not supported because the last semanticdb-scalac_2.12.3 release doesn't support the option -P:semanticdb:targetroot:PATH. .map { case (key, value) => s"'$key':'$value'" - } - .mkString("[", ", ", "]") + }.mkString("[", ", ", "]") } diff --git a/semanticdb-agent/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbAgent.java b/semanticdb-agent/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbAgent.java index db624aa2..e147bcc1 100644 --- a/semanticdb-agent/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbAgent.java +++ b/semanticdb-agent/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbAgent.java @@ -7,14 +7,12 @@ import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; -import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException;