Skip to content

Commit eea306f

Browse files
committed
Add support for the Gradle Playframework plugin.
Previously, the `index` command didn't work with Gradle projects that use the Playframework plugin. The codebase would compile, but no LSIF index would be created. Now, everything should work as expected. The problem was that the Playframework plugin uses the Scala plugin to compile auto-generated template and routes files. The Scala plugin runs Zinc (Scala incremental compiler) in a daemon process behind the scenes so it ignores the `javac` fork settings that `lsif-java index` adds to the build. The fix is to enable the SemanticDB Java agent on the Zinc daemon process and to add one more injection point to ensure that the SemanticDB compiler plugin is always on the classpath for all projects.
1 parent fb98dd4 commit eea306f

File tree

13 files changed

+218
-21
lines changed

13 files changed

+218
-21
lines changed

lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/BuildTool.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ abstract class BuildTool(val name: String, index: IndexCommand) {
1616

1717
def indexJdk(): Boolean = true
1818

19+
final def sourceroot: Path = index.workingDirectory
1920
final def targetroot: Path =
2021
AbsolutePath
2122
.of(index.targetroot.getOrElse(defaultTargetroot), index.workingDirectory)

lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/GradleBuildTool.scala

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) {
7575
buildCommand ++= index.finalBuildCommand(List("clean", "compileTestJava"))
7676
buildCommand += lsifJavaDependencies
7777

78-
val result = index.process(buildCommand.toList: _*)
78+
val result = index.process(buildCommand)
7979
printDebugLogs(toolchains.tmp)
8080
Embedded
8181
.reportUnexpectedJavacErrors(index.app.reporter, toolchains.tmp)
@@ -103,6 +103,9 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) {
103103
case None =>
104104
""
105105
}
106+
107+
val agentpath = Embedded.agentJar(tmp)
108+
val pluginpath = Embedded.semanticdbJar(tmp)
106109
val dependenciesPath = targetroot.resolve("dependencies.txt")
107110
Files.deleteIfExists(dependenciesPath)
108111
val script =
@@ -111,16 +114,36 @@ class GradleBuildTool(index: IndexCommand) extends BuildTool("Gradle", index) {
111114
| boolean isJavaEnabled = project.plugins.any {
112115
| it.getClass().getName().endsWith("org.gradle.api.plugins.JavaPlugin")
113116
| }
114-
| if (!isJavaEnabled) return
115-
| tasks.withType(JavaCompile) {
116-
| options.fork = true
117-
| options.incremental = false
118-
| $executable
117+
| boolean isScalaEnabled = project.plugins.any {
118+
| it.getClass().getName().endsWith("org.gradle.api.plugins.scala.ScalaPlugin")
119+
| }
120+
| if (isJavaEnabled) {
121+
| tasks.withType(JavaCompile) {
122+
| options.fork = true
123+
| options.incremental = false
124+
| $executable
125+
| }
126+
| }
127+
| if (isScalaEnabled) {
128+
| // The Scala plugin runs Zinc, an incremental compiler, in a separate daemon process.
129+
| // Zinc invokes the Java compiler directly to compile mixed Java/Scala projects
130+
| // instead of respecting the `JavaCompile` fork options from the Gradle Java plugin.
131+
| // By enabling the SemanticDB Java agent on the Zinc daemon process, we manage
132+
| // to configure Zinc to use the semanticdb-javac compiler plugin for Java compilation.
133+
| tasks.withType(ScalaCompile) {
134+
| scalaCompileOptions.forkOptions.with {
135+
| jvmArgs << '-javaagent:$agentpath'
136+
| jvmArgs << '-Dsemanticdb.pluginpath=$pluginpath'
137+
| jvmArgs << '-Dsemanticdb.sourceroot=$sourceroot'
138+
| jvmArgs << '-Dsemanticdb.targetroot=$targetroot'
139+
| }
140+
| }
119141
| }
120142
| }
121143
| task $lsifJavaDependencies {
122144
| def depsOut = java.nio.file.Paths.get('$dependenciesPath')
123145
| doLast {
146+
| java.nio.file.Files.createDirectories(depsOut.getParent())
124147
| tasks.withType(JavaCompile) {
125148
| try {
126149
| configurations.each { config ->

lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/GradleJavaToolchains.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ object GradleJavaToolchains {
9292
|}
9393
|""".stripMargin
9494
Files.write(scriptPath, script.getBytes(StandardCharsets.UTF_8))
95-
index.process(gradleCommand, "--init-script", scriptPath.toString, taskName)
95+
index.process(
96+
List(gradleCommand, "--init-script", scriptPath.toString, taskName)
97+
)
9698
val toolchains: List[GradleJavaCompiler] =
9799
if (Files.isRegularFile(toolchainsPath)) {
98100
Files

lsif-java/src/main/scala/com/sourcegraph/lsif_java/buildtools/MavenBuildTool.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class MavenBuildTool(index: IndexCommand) extends BuildTool("Maven", index) {
5353
)
5454
)
5555

56-
val exit = index.process(buildCommand.toList: _*)
56+
val exit = index.process(buildCommand)
5757
Embedded
5858
.reportUnexpectedJavacErrors(index.app.reporter, tmp)
5959
.getOrElse(exit)

lsif-java/src/main/scala/com/sourcegraph/lsif_java/commands/IndexCommand.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,19 @@ case class IndexCommand(
6161
app: Application = Application.default
6262
) extends Command {
6363

64-
def process(shellable: String*): CommandResult = {
65-
app.info(shellable.mkString(" "))
64+
def process(
65+
shellable: Shellable,
66+
env: Map[String, String] = Map.empty
67+
): CommandResult = {
68+
app.info(shellable.value.mkString(" "))
6669
app
67-
.process(Shellable(shellable))
70+
.process(shellable)
6871
.call(
6972
check = false,
7073
stdout = Inherit,
7174
stderr = Inherit,
72-
cwd = workingDirectory
75+
cwd = workingDirectory,
76+
env = env
7377
)
7478
}
7579

lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdb.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,21 @@ public static void run(LsifSemanticdbOptions options) throws IOException {
3737

3838
private void run() throws IOException {
3939
PackageTable packages = new PackageTable(options, writer);
40-
writer.emitMetaData();
41-
int projectId = writer.emitProject(options.language);
42-
4340
List<Path> files = SemanticdbWalker.findSemanticdbFiles(options);
4441
if (options.reporter.hasErrors()) return;
42+
if (files.isEmpty()) {
43+
options.reporter.error(
44+
"No SemanticDB files found. "
45+
+ "This typically means that `lsif-java` is unable to automatically "
46+
+ "index this codebase. If you are using Gradle or Maven, please report an issue to "
47+
+ "https://github.com/sourcegraph/lsif-java and include steps to reproduce. "
48+
+ "If you are using a different build tool, make sure that you have followed all "
49+
+ "of the steps from https://sourcegraph.github.io/lsif-java/docs/manual-configuration.html");
50+
return;
51+
}
4552
options.reporter.startProcessing(files.size());
53+
writer.emitMetaData();
54+
int projectId = writer.emitProject(options.language);
4655

4756
Set<String> isExportedSymbol = exportSymbols(files);
4857
List<Integer> documentIds =

lsif-semanticdb/src/main/java/com/sourcegraph/lsif_semanticdb/LsifSemanticdbReporter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
public abstract class LsifSemanticdbReporter {
1010
public void error(Throwable e) {}
1111

12+
public void error(String message) {
13+
error(new RuntimeException(message));
14+
}
15+
1216
public void startProcessing(int taskSize) {}
1317

1418
public void processedOneItem() {}

semanticdb-agent/src/main/java/com/sourcegraph/semanticdb_javac/SemanticdbAgent.java

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55

66
import java.io.File;
77
import java.io.IOException;
8+
import java.io.OutputStream;
89
import java.io.PrintStream;
910
import java.lang.instrument.Instrumentation;
1011
import java.nio.charset.StandardCharsets;
1112
import java.nio.file.Files;
13+
import java.nio.file.Path;
1214
import java.nio.file.Paths;
1315
import java.nio.file.StandardOpenOption;
1416
import java.util.ArrayList;
@@ -21,18 +23,77 @@
2123
public class SemanticdbAgent {
2224

2325
public static void premain(String agentArgs, Instrumentation inst) {
26+
// NOTE(olafur): Uncoment below if you want see all the loaded classes.
27+
// PrintStream logger = newLogger();
28+
// inst.addTransformer(
29+
// new ClassFileTransformer() {
30+
// @Override
31+
// public byte[] transform(
32+
// ClassLoader loader,
33+
// String className,
34+
// Class<?> classBeingRedefined,
35+
// ProtectionDomain protectionDomain,
36+
// byte[] classfileBuffer) {
37+
// logger.println(className);
38+
// return classfileBuffer;
39+
// }
40+
// });
41+
new AgentBuilder.Default()
42+
.disableClassFormatChanges()
43+
.type(
44+
named("org.gradle.api.internal.tasks.compile.DefaultJvmLanguageCompileSpec")
45+
.or(named("tests.GradleDefaultJvmLanguageCompileSpec")))
46+
.transform(
47+
new AgentBuilder.Transformer.ForAdvice()
48+
.advice(
49+
named("getCompileClasspath"),
50+
DefaultJvmLanguageCompileSpecAdvice.class.getName()))
51+
.installOn(inst);
2452
new AgentBuilder.Default()
2553
.type(
2654
named("org.gradle.api.internal.tasks.compile.JavaCompilerArgumentsBuilder")
27-
.or(named("tests.GradleOptionsBuilder")))
55+
.or(named("tests.GradleJavaCompilerArgumentsBuilder")))
2856
.transform(
2957
new AgentBuilder.Transformer.ForAdvice()
30-
.advice(named("build"), GradleAdvice.class.getName()))
58+
.advice(named("build"), JavaCompilerArgumentsBuilderAdvice.class.getName()))
3159
.installOn(inst);
3260
}
3361

62+
private static PrintStream newLogger() {
63+
Path path = Paths.get(System.getProperty("user.home"), ".lsif-java", "logs.txt");
64+
try {
65+
Files.createDirectories(path.getParent());
66+
OutputStream fos =
67+
Files.newOutputStream(
68+
path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
69+
return new PrintStream(fos);
70+
} catch (IOException e) {
71+
return new PrintStream(
72+
new OutputStream() {
73+
@Override
74+
public void write(int b) {}
75+
});
76+
}
77+
}
78+
79+
@SuppressWarnings("all")
80+
public static class DefaultJvmLanguageCompileSpecAdvice {
81+
@Advice.OnMethodExit
82+
public static void getClasspath(
83+
@Advice.Return(readOnly = false, typing = DYNAMIC) List<File> classpath) {
84+
String PLUGINPATH = System.getProperty("semanticdb.pluginpath");
85+
if (PLUGINPATH == null) throw new NoSuchElementException("-Dsemanticdb.pluginpath");
86+
File semanticdbJar = new File(PLUGINPATH);
87+
if (!classpath.contains(semanticdbJar)) {
88+
List<File> newClasspath = new ArrayList<>(classpath);
89+
newClasspath.add(semanticdbJar);
90+
classpath = newClasspath;
91+
}
92+
}
93+
}
94+
3495
@SuppressWarnings("all")
35-
public static class GradleAdvice {
96+
public static class JavaCompilerArgumentsBuilderAdvice {
3697

3798
/**
3899
* The bytecode of this method gets injected at the end of the following method in Gradle:
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package tests
2+
3+
import java.io.File
4+
import java.util
5+
6+
import scala.jdk.CollectionConverters._
7+
8+
class GradleDefaultJvmLanguageCompileSpec(base: List[String]) {
9+
def getCompileClasspath: util.List[File] = {
10+
base.map(new File(_)).asJava
11+
}
12+
}

tests/buildTools/src/main/scala/tests/GradleOptionsBuilder.scala renamed to tests/buildTools/src/main/scala/tests/GradleJavaCompilerArgumentsBuilder.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ package tests
22

33
import scala.jdk.CollectionConverters._
44

5-
class GradleOptionsBuilder(base: List[String]) {
5+
class GradleJavaCompilerArgumentsBuilder(base: List[String]) {
66
def build(): java.util.List[String] = base.asJava
77
}

0 commit comments

Comments
 (0)