Skip to content

Commit 7d89bd4

Browse files
committed
Migrate scip-gradle-plugin to Java
1 parent b4c0ca4 commit 7d89bd4

6 files changed

Lines changed: 459 additions & 412 deletions

File tree

build.sbt

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,27 +93,26 @@ lazy val gradlePlugin = project
9393
.in(file("scip-gradle-plugin"))
9494
.settings(
9595
name := "scip-gradle",
96-
buildInfoPackage := "com.sourcegraph.scip_java",
96+
javaOnlySettings,
9797
publish / skip := true,
98-
scalacOptions ++= Seq("-target:11", "-release", "11"),
9998
libraryDependencies ++=
10099
List(
101100
"dev.gradleplugins" % "gradle-api" % V.gradle % Provided,
102-
"dev.gradleplugins" % "gradle-test-kit" % V.gradle % Provided,
103-
"org.jetbrains.kotlin" % "kotlin-gradle-plugin" % V.kotlinVersion %
104-
Provided
101+
"dev.gradleplugins" % "gradle-test-kit" % V.gradle % Provided
105102
),
106-
buildInfoKeys :=
107-
Seq[BuildInfoKey](
108-
version,
109-
sbtVersion,
110-
scalaVersion,
111-
"javacModuleOptions" -> javacModuleOptions,
112-
"scalametaVersion" -> V.scalameta,
113-
"scala213" -> V.scala213
114-
)
103+
// The plugin's BuildInfo.version (the scip-javac Maven coordinate used as a
104+
// fallback when no jar is provided) is read at runtime from this generated
105+
// properties resource, mirroring the scip-java CLI's scip-java.properties.
106+
(Compile / resourceGenerators) +=
107+
Def.task {
108+
val file = (Compile / resourceManaged).value / "scip-gradle.properties"
109+
IO.createDirectory(file.getParentFile)
110+
val props = new Properties()
111+
props.put("version", version.value)
112+
IO.write(props, "scip-gradle", file)
113+
Seq(file)
114+
}.taskValue
115115
)
116-
.enablePlugins(BuildInfoPlugin)
117116

118117
lazy val javacPlugin = project
119118
.in(file("scip-javac"))
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.sourcegraph.gradle.scip;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.util.Arrays;
6+
import java.util.Collections;
7+
import java.util.List;
8+
import java.util.Properties;
9+
10+
/**
11+
* Build metadata for the SCIP Gradle plugin.
12+
*
13+
* <p>Replaces the previously sbt-generated {@code com.sourcegraph.scip_java.BuildInfo}. The static
14+
* {@link #javacModuleOptions} live here directly; the build-time {@link #version} is injected by
15+
* sbt into the {@code scip-gradle.properties} classpath resource (see the {@code
16+
* resourceGenerators} for the {@code gradlePlugin} project in build.sbt).
17+
*/
18+
final class BuildInfo {
19+
20+
private BuildInfo() {}
21+
22+
/**
23+
* {@code --add-exports} flags required to access internal javac APIs from the SCIP compiler
24+
* plugin. Java 11+ is the supported baseline.
25+
*
26+
* <p>Kept in sync with {@code javacModuleOptions} in build.sbt, which applies the same flags when
27+
* compiling the plugin and the test fixtures.
28+
*/
29+
static final List<String> javacModuleOptions =
30+
Collections.unmodifiableList(
31+
Arrays.asList(
32+
"-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
33+
"-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
34+
"-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
35+
"-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
36+
"-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"));
37+
38+
/**
39+
* The scip-java release version. Read from the {@code scip-gradle.properties} resource that sbt
40+
* generates into the jar. Falls back to a placeholder when running without the generated resource
41+
* on the classpath.
42+
*/
43+
static final String version = loadVersion();
44+
45+
private static String loadVersion() {
46+
Properties props = new Properties();
47+
try (InputStream in = BuildInfo.class.getResourceAsStream("/scip-gradle.properties")) {
48+
if (in != null) {
49+
props.load(in);
50+
}
51+
} catch (IOException e) {
52+
// Fall back to the placeholder below.
53+
}
54+
return props.getProperty("version", "0.0.0-SNAPSHOT");
55+
}
56+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.sourcegraph.gradle.scip;
2+
3+
final class Logging {
4+
5+
private Logging() {}
6+
7+
static void warn(String message) {
8+
System.err.println("[WARNING] [scip-java.gradle] " + message);
9+
}
10+
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package com.sourcegraph.gradle.scip;
2+
3+
import java.io.File;
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.stream.Collectors;
8+
import org.gradle.api.Plugin;
9+
import org.gradle.api.Project;
10+
import org.gradle.api.Task;
11+
import org.gradle.api.artifacts.Configuration;
12+
import org.gradle.api.plugins.ExtraPropertiesExtension;
13+
import org.gradle.api.tasks.compile.ForkOptions;
14+
import org.gradle.api.tasks.compile.JavaCompile;
15+
16+
public class ScipGradlePlugin implements Plugin<Project> {
17+
18+
@Override
19+
public void apply(Project project) {
20+
project.afterEvaluate(this::configureProject);
21+
}
22+
23+
private void configureProject(Project project) {
24+
project.getRepositories().add(project.getRepositories().mavenCentral());
25+
project.getRepositories().add(project.getRepositories().mavenLocal());
26+
27+
ExtraPropertiesExtension extra = project.getExtensions().getExtraProperties();
28+
Map<String, Object> extraProperties = extra.getProperties();
29+
30+
Object targetRoot = extraProperties.getOrDefault("scipTarget", project.getBuildDir());
31+
32+
String javacPluginVersion = BuildInfo.version;
33+
34+
Object javacPluginJar = extraProperties.get("javacPluginJar");
35+
36+
// We fall back to the javac plugin published to Maven if there is no jar
37+
// specified. The JAR would usually be provided by the auto-indexer.
38+
Object javacPluginDep =
39+
javacPluginJar != null
40+
? project.files(javacPluginJar)
41+
: "com.sourcegraph:scip-javac:" + javacPluginVersion;
42+
43+
File sourceRoot = project.getRootDir();
44+
45+
// List of compilation commands that we will need to trigger to index all
46+
// the sources in the project we care about. This list is built
47+
// progressively as we check for java and kotlin plugins.
48+
List<String> triggers = new ArrayList<>();
49+
50+
if (project.getPlugins().hasPlugin("java")) {
51+
triggers.add("compileJava");
52+
triggers.add("compileTestJava");
53+
54+
boolean hasAnnotationPath;
55+
try {
56+
Configuration apConfig = project.getConfigurations().getByName("annotationProcessor");
57+
hasAnnotationPath = apConfig.isCanBeResolved() && !apConfig.getDependencies().isEmpty();
58+
} catch (Exception exc) {
59+
hasAnnotationPath = false;
60+
}
61+
62+
boolean compilerPluginAdded;
63+
try {
64+
project.getDependencies().add("compileOnly", javacPluginDep);
65+
66+
if (hasAnnotationPath) {
67+
project.getDependencies().add("annotationProcessor", javacPluginDep);
68+
}
69+
70+
project.getDependencies().add("testCompileOnly", javacPluginDep);
71+
72+
compilerPluginAdded = true;
73+
} catch (Exception exc) {
74+
// The `compileOnly` configuration has likely already been resolved by
75+
// another plugin or buildscript, so we can no longer add new
76+
// dependencies to it. The project will be skipped (no SCIP output) and
77+
// the post-build check in `GradleBuildTool` will surface a clearer
78+
// error.
79+
Logging.warn(
80+
"scip-java: failed to attach SCIP compiler plugin to project '"
81+
+ project.getName()
82+
+ "' ("
83+
+ exc.getClass().getSimpleName()
84+
+ ": "
85+
+ exc.getMessage()
86+
+ "). This subproject will not be indexed.");
87+
compilerPluginAdded = false;
88+
}
89+
90+
boolean pluginAdded = compilerPluginAdded;
91+
project
92+
.getTasks()
93+
.withType(JavaCompile.class)
94+
.configureEach(
95+
task -> {
96+
// Add --add-exports JVM args so our compiler plugin can access
97+
// javac internals. Required on JDK 17+ (JEP 403), no-op on
98+
// 11-16.
99+
ForkOptions forkOptions = task.getOptions().getForkOptions();
100+
List<String> jvmArgs =
101+
BuildInfo.javacModuleOptions.stream()
102+
.map(arg -> arg.startsWith("-J") ? arg.substring(2) : arg)
103+
.collect(Collectors.toList());
104+
if (forkOptions.getJvmArgs() == null) {
105+
forkOptions.setJvmArgs(jvmArgs);
106+
} else {
107+
forkOptions.getJvmArgs().addAll(jvmArgs);
108+
}
109+
110+
task.getOptions().setFork(true);
111+
task.getOptions().setIncremental(false);
112+
113+
if (pluginAdded) {
114+
List<String> args = task.getOptions().getCompilerArgs();
115+
116+
// It's important we don't add the plugin configuration more
117+
// than once, as javac considers that an error.
118+
boolean alreadyAdded =
119+
args.stream().anyMatch(arg -> arg.startsWith("-Xplugin:scip"));
120+
if (!alreadyAdded) {
121+
// We add the random timestamp to ensure that the sources
122+
// are _always_ recompiled, so that Gradle doesn't cache any
123+
// state.
124+
// TODO: before this plugin is published to Maven Central, we
125+
// will need to revert this change - as it can have
126+
// detrimental effect on people's builds.
127+
args.add(
128+
"-Xplugin:scip -targetroot:"
129+
+ targetRoot
130+
+ " -sourceroot:"
131+
+ sourceRoot
132+
+ " -randomtimestamp="
133+
+ System.nanoTime());
134+
}
135+
}
136+
});
137+
}
138+
139+
boolean isKotlinMultiplatform = false;
140+
for (Plugin<?> plugin : project.getPlugins()) {
141+
if (plugin.getClass().getName().contains("KotlinMultiplatform")) {
142+
isKotlinMultiplatform = true;
143+
break;
144+
}
145+
}
146+
147+
if (project.getPlugins().hasPlugin("kotlin") || isKotlinMultiplatform) {
148+
if (isKotlinMultiplatform) {
149+
triggers.add("compileKotlinJvm");
150+
triggers.add("compileTestKotlinJvm");
151+
} else {
152+
triggers.add("compileKotlin");
153+
triggers.add("compileTestKotlin");
154+
}
155+
156+
project
157+
.getTasks()
158+
.configureEach(
159+
task -> configureKotlinCompileTask(task, extraProperties, sourceRoot, targetRoot));
160+
}
161+
162+
project
163+
.getTasks()
164+
.create(
165+
"scipCompileAll",
166+
task -> {
167+
for (String trigger : triggers) {
168+
task.dependsOn(project.getTasks().getByName(trigger));
169+
}
170+
});
171+
172+
project.getTasks().create("scipPrintDependencies", WriteDependencies.class);
173+
}
174+
175+
private static void configureKotlinCompileTask(
176+
Task task, Map<String, Object> extraProperties, Object sourceRoot, Object targetRoot) {
177+
if (!task.getClass().getSimpleName().contains("KotlinCompile")) {
178+
return;
179+
}
180+
181+
// If we actually refer to KotlinCompile at _any_ point here, then the
182+
// plugin fails with NoClassDefFoundError - because the plugin classpath is
183+
// murky.
184+
//
185+
// We also don't want to bundle the kotlin plugin with this one as it can
186+
// cause all sorts of troubles.
187+
//
188+
// Instead, we commit the sins of reflection for our limited needs.
189+
try {
190+
Object kotlinOptions = task.getClass().getMethod("getKotlinOptions").invoke(task);
191+
192+
// The scip-kotlinc compiler plugin is now built and shipped together with
193+
// the scip-java CLI. The CLI's init script writes the absolute path of
194+
// the embedded jar into the `scipKotlincJar` extra property so we no
195+
// longer need to resolve a separately-published artifact at apply-time.
196+
Object scipKotlinc = extraProperties.get("scipKotlincJar");
197+
if (scipKotlinc == null) {
198+
throw new IllegalStateException(
199+
"scipKotlincJar extra property must be set by the "
200+
+ "scip-java init script when indexing Kotlin sources");
201+
}
202+
203+
@SuppressWarnings("unchecked")
204+
List<String> freeCompilerArgs =
205+
(List<String>)
206+
kotlinOptions.getClass().getMethod("getFreeCompilerArgs").invoke(kotlinOptions);
207+
208+
List<String> newArgs = new ArrayList<>(freeCompilerArgs.size() + 5);
209+
newArgs.addAll(freeCompilerArgs);
210+
newArgs.add("-Xplugin=" + scipKotlinc);
211+
newArgs.add("-P");
212+
newArgs.add("plugin:scip-kotlinc:sourceroot=" + sourceRoot);
213+
newArgs.add("-P");
214+
newArgs.add("plugin:scip-kotlinc:targetroot=" + targetRoot);
215+
216+
kotlinOptions
217+
.getClass()
218+
.getMethod("setFreeCompilerArgs", List.class)
219+
.invoke(kotlinOptions, newArgs);
220+
} catch (ReflectiveOperationException exc) {
221+
throw new RuntimeException(
222+
"scip-java: failed to configure Kotlin compile task '" + task.getName() + "'", exc);
223+
}
224+
}
225+
}

0 commit comments

Comments
 (0)