From 7d89bd4e95df4c67f78e6fcf2ff17082978a69de Mon Sep 17 00:00:00 2001 From: jupblb Date: Mon, 22 Jun 2026 17:50:26 +0200 Subject: [PATCH 1/4] Migrate scip-gradle-plugin to Java --- build.sbt | 29 +- .../sourcegraph/gradle/scip/BuildInfo.java | 56 +++ .../com/sourcegraph/gradle/scip/Logging.java | 10 + .../gradle/scip/ScipGradlePlugin.java | 225 ++++++++++ .../gradle/scip/WriteDependencies.java | 154 +++++++ .../src/main/scala/ScipGradlePlugin.scala | 397 ------------------ 6 files changed, 459 insertions(+), 412 deletions(-) create mode 100644 scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/BuildInfo.java create mode 100644 scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/Logging.java create mode 100644 scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java create mode 100644 scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java delete mode 100644 scip-gradle-plugin/src/main/scala/ScipGradlePlugin.scala diff --git a/build.sbt b/build.sbt index 3f93c647..59a01bdf 100644 --- a/build.sbt +++ b/build.sbt @@ -93,27 +93,26 @@ lazy val gradlePlugin = project .in(file("scip-gradle-plugin")) .settings( name := "scip-gradle", - buildInfoPackage := "com.sourcegraph.scip_java", + javaOnlySettings, publish / skip := true, - scalacOptions ++= Seq("-target:11", "-release", "11"), libraryDependencies ++= List( "dev.gradleplugins" % "gradle-api" % V.gradle % Provided, - "dev.gradleplugins" % "gradle-test-kit" % V.gradle % Provided, - "org.jetbrains.kotlin" % "kotlin-gradle-plugin" % V.kotlinVersion % - Provided + "dev.gradleplugins" % "gradle-test-kit" % V.gradle % Provided ), - buildInfoKeys := - Seq[BuildInfoKey]( - version, - sbtVersion, - scalaVersion, - "javacModuleOptions" -> javacModuleOptions, - "scalametaVersion" -> V.scalameta, - "scala213" -> V.scala213 - ) + // The plugin's BuildInfo.version (the scip-javac Maven coordinate used as a + // fallback when no jar is provided) is read at runtime from this generated + // properties resource, mirroring the scip-java CLI's scip-java.properties. + (Compile / resourceGenerators) += + Def.task { + val file = (Compile / resourceManaged).value / "scip-gradle.properties" + IO.createDirectory(file.getParentFile) + val props = new Properties() + props.put("version", version.value) + IO.write(props, "scip-gradle", file) + Seq(file) + }.taskValue ) - .enablePlugins(BuildInfoPlugin) lazy val javacPlugin = project .in(file("scip-javac")) diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/BuildInfo.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/BuildInfo.java new file mode 100644 index 00000000..f73cca7d --- /dev/null +++ b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/BuildInfo.java @@ -0,0 +1,56 @@ +package com.sourcegraph.gradle.scip; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +/** + * Build metadata for the SCIP Gradle plugin. + * + *

Replaces the previously sbt-generated {@code com.sourcegraph.scip_java.BuildInfo}. The static + * {@link #javacModuleOptions} live here directly; the build-time {@link #version} is injected by + * sbt into the {@code scip-gradle.properties} classpath resource (see the {@code + * resourceGenerators} for the {@code gradlePlugin} project in build.sbt). + */ +final class BuildInfo { + + private BuildInfo() {} + + /** + * {@code --add-exports} flags required to access internal javac APIs from the SCIP compiler + * plugin. Java 11+ is the supported baseline. + * + *

Kept in sync with {@code javacModuleOptions} in build.sbt, which applies the same flags when + * compiling the plugin and the test fixtures. + */ + static final List javacModuleOptions = + Collections.unmodifiableList( + Arrays.asList( + "-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED")); + + /** + * The scip-java release version. Read from the {@code scip-gradle.properties} resource that sbt + * generates into the jar. Falls back to a placeholder when running without the generated resource + * on the classpath. + */ + static final String version = loadVersion(); + + private static String loadVersion() { + Properties props = new Properties(); + try (InputStream in = BuildInfo.class.getResourceAsStream("/scip-gradle.properties")) { + if (in != null) { + props.load(in); + } + } catch (IOException e) { + // Fall back to the placeholder below. + } + return props.getProperty("version", "0.0.0-SNAPSHOT"); + } +} diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/Logging.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/Logging.java new file mode 100644 index 00000000..61c811c0 --- /dev/null +++ b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/Logging.java @@ -0,0 +1,10 @@ +package com.sourcegraph.gradle.scip; + +final class Logging { + + private Logging() {} + + static void warn(String message) { + System.err.println("[WARNING] [scip-java.gradle] " + message); + } +} diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java new file mode 100644 index 00000000..700bc8db --- /dev/null +++ b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java @@ -0,0 +1,225 @@ +package com.sourcegraph.gradle.scip; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.ExtraPropertiesExtension; +import org.gradle.api.tasks.compile.ForkOptions; +import org.gradle.api.tasks.compile.JavaCompile; + +public class ScipGradlePlugin implements Plugin { + + @Override + public void apply(Project project) { + project.afterEvaluate(this::configureProject); + } + + private void configureProject(Project project) { + project.getRepositories().add(project.getRepositories().mavenCentral()); + project.getRepositories().add(project.getRepositories().mavenLocal()); + + ExtraPropertiesExtension extra = project.getExtensions().getExtraProperties(); + Map extraProperties = extra.getProperties(); + + Object targetRoot = extraProperties.getOrDefault("scipTarget", project.getBuildDir()); + + String javacPluginVersion = BuildInfo.version; + + Object javacPluginJar = extraProperties.get("javacPluginJar"); + + // We fall back to the javac plugin published to Maven if there is no jar + // specified. The JAR would usually be provided by the auto-indexer. + Object javacPluginDep = + javacPluginJar != null + ? project.files(javacPluginJar) + : "com.sourcegraph:scip-javac:" + javacPluginVersion; + + File sourceRoot = project.getRootDir(); + + // List of compilation commands that we will need to trigger to index all + // the sources in the project we care about. This list is built + // progressively as we check for java and kotlin plugins. + List triggers = new ArrayList<>(); + + if (project.getPlugins().hasPlugin("java")) { + triggers.add("compileJava"); + triggers.add("compileTestJava"); + + boolean hasAnnotationPath; + try { + Configuration apConfig = project.getConfigurations().getByName("annotationProcessor"); + hasAnnotationPath = apConfig.isCanBeResolved() && !apConfig.getDependencies().isEmpty(); + } catch (Exception exc) { + hasAnnotationPath = false; + } + + boolean compilerPluginAdded; + try { + project.getDependencies().add("compileOnly", javacPluginDep); + + if (hasAnnotationPath) { + project.getDependencies().add("annotationProcessor", javacPluginDep); + } + + project.getDependencies().add("testCompileOnly", javacPluginDep); + + compilerPluginAdded = true; + } catch (Exception exc) { + // The `compileOnly` configuration has likely already been resolved by + // another plugin or buildscript, so we can no longer add new + // dependencies to it. The project will be skipped (no SCIP output) and + // the post-build check in `GradleBuildTool` will surface a clearer + // error. + Logging.warn( + "scip-java: failed to attach SCIP compiler plugin to project '" + + project.getName() + + "' (" + + exc.getClass().getSimpleName() + + ": " + + exc.getMessage() + + "). This subproject will not be indexed."); + compilerPluginAdded = false; + } + + boolean pluginAdded = compilerPluginAdded; + project + .getTasks() + .withType(JavaCompile.class) + .configureEach( + task -> { + // Add --add-exports JVM args so our compiler plugin can access + // javac internals. Required on JDK 17+ (JEP 403), no-op on + // 11-16. + ForkOptions forkOptions = task.getOptions().getForkOptions(); + List jvmArgs = + BuildInfo.javacModuleOptions.stream() + .map(arg -> arg.startsWith("-J") ? arg.substring(2) : arg) + .collect(Collectors.toList()); + if (forkOptions.getJvmArgs() == null) { + forkOptions.setJvmArgs(jvmArgs); + } else { + forkOptions.getJvmArgs().addAll(jvmArgs); + } + + task.getOptions().setFork(true); + task.getOptions().setIncremental(false); + + if (pluginAdded) { + List args = task.getOptions().getCompilerArgs(); + + // It's important we don't add the plugin configuration more + // than once, as javac considers that an error. + boolean alreadyAdded = + args.stream().anyMatch(arg -> arg.startsWith("-Xplugin:scip")); + if (!alreadyAdded) { + // We add the random timestamp to ensure that the sources + // are _always_ recompiled, so that Gradle doesn't cache any + // state. + // TODO: before this plugin is published to Maven Central, we + // will need to revert this change - as it can have + // detrimental effect on people's builds. + args.add( + "-Xplugin:scip -targetroot:" + + targetRoot + + " -sourceroot:" + + sourceRoot + + " -randomtimestamp=" + + System.nanoTime()); + } + } + }); + } + + boolean isKotlinMultiplatform = false; + for (Plugin plugin : project.getPlugins()) { + if (plugin.getClass().getName().contains("KotlinMultiplatform")) { + isKotlinMultiplatform = true; + break; + } + } + + if (project.getPlugins().hasPlugin("kotlin") || isKotlinMultiplatform) { + if (isKotlinMultiplatform) { + triggers.add("compileKotlinJvm"); + triggers.add("compileTestKotlinJvm"); + } else { + triggers.add("compileKotlin"); + triggers.add("compileTestKotlin"); + } + + project + .getTasks() + .configureEach( + task -> configureKotlinCompileTask(task, extraProperties, sourceRoot, targetRoot)); + } + + project + .getTasks() + .create( + "scipCompileAll", + task -> { + for (String trigger : triggers) { + task.dependsOn(project.getTasks().getByName(trigger)); + } + }); + + project.getTasks().create("scipPrintDependencies", WriteDependencies.class); + } + + private static void configureKotlinCompileTask( + Task task, Map extraProperties, Object sourceRoot, Object targetRoot) { + if (!task.getClass().getSimpleName().contains("KotlinCompile")) { + return; + } + + // If we actually refer to KotlinCompile at _any_ point here, then the + // plugin fails with NoClassDefFoundError - because the plugin classpath is + // murky. + // + // We also don't want to bundle the kotlin plugin with this one as it can + // cause all sorts of troubles. + // + // Instead, we commit the sins of reflection for our limited needs. + try { + Object kotlinOptions = task.getClass().getMethod("getKotlinOptions").invoke(task); + + // The scip-kotlinc compiler plugin is now built and shipped together with + // the scip-java CLI. The CLI's init script writes the absolute path of + // the embedded jar into the `scipKotlincJar` extra property so we no + // longer need to resolve a separately-published artifact at apply-time. + Object scipKotlinc = extraProperties.get("scipKotlincJar"); + if (scipKotlinc == null) { + throw new IllegalStateException( + "scipKotlincJar extra property must be set by the " + + "scip-java init script when indexing Kotlin sources"); + } + + @SuppressWarnings("unchecked") + List freeCompilerArgs = + (List) + kotlinOptions.getClass().getMethod("getFreeCompilerArgs").invoke(kotlinOptions); + + List newArgs = new ArrayList<>(freeCompilerArgs.size() + 5); + newArgs.addAll(freeCompilerArgs); + newArgs.add("-Xplugin=" + scipKotlinc); + newArgs.add("-P"); + newArgs.add("plugin:scip-kotlinc:sourceroot=" + sourceRoot); + newArgs.add("-P"); + newArgs.add("plugin:scip-kotlinc:targetroot=" + targetRoot); + + kotlinOptions + .getClass() + .getMethod("setFreeCompilerArgs", List.class) + .invoke(kotlinOptions, newArgs); + } catch (ReflectiveOperationException exc) { + throw new RuntimeException( + "scip-java: failed to configure Kotlin compile task '" + task.getName() + "'", exc); + } + } +} diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java new file mode 100644 index 00000000..61eb7bb4 --- /dev/null +++ b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java @@ -0,0 +1,154 @@ +package com.sourcegraph.gradle.scip; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.gradle.api.DefaultTask; +import org.gradle.api.Project; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.TaskAction; + +public class WriteDependencies extends DefaultTask { + + private static final String CROSS_REPO_BANNER = + "This will not prevent a SCIP index from being created, but the symbols\n" + + "extracted from this project won't be available for cross-repository navigation,\n" + + "as this project doesn't define any Maven coordinates by which it can be referred back" + + " to.\n" + + "See here for more details:" + + " https://sourcegraph.github.io/scip-java/docs/manual-configuration.html#step-5-optional-enable-cross-repository-navigation"; + + @TaskAction + public void printResolvedDependencies() throws IOException { + Project project = getProject(); + + Optional depsOut = + Optional.ofNullable(project.getExtensions().getExtraProperties().get("dependenciesOut")) + .map(Object::toString) + .map(Paths::get); + + if (depsOut.isPresent()) { + Files.createDirectories(depsOut.get().getParent()); + } + + List deps = new ArrayList<>(); + String projectName = project.getName(); + String projectPath = project.getPath().replaceAll("[^a-z0-9A-Z_-]", "_"); + + // List the project itself as a dependency so that we can assign project + // name/version to symbols that are defined in this project. + Optional dependenciesPath = + depsOut.map( + path -> { + Path filename = path.getFileName(); + if (filename.endsWith("dependencies.txt")) { + String last = projectPath + "." + filename.toString(); + return path.getParent().resolve(last); + } else { + return path; + } + }); + + try { + PublishingExtension publishing = + project.getExtensions().findByType(PublishingExtension.class); + for (MavenPublication publication : + publishing.getPublications().withType(MavenPublication.class)) { + try { + SourceSet main = + project.getExtensions().getByType(SourceSetContainer.class).getByName("main"); + main.getOutput().getClassesDirs().getFiles().stream() + .map(File::getAbsolutePath) + .sorted() + .limit(1) + .forEach( + classesDirectory -> + deps.add( + String.join( + "\t", + publication.getGroupId(), + publication.getArtifactId(), + publication.getVersion(), + classesDirectory))); + } catch (Exception exception) { + String publicationName = + String.join( + ":", + publication.getGroupId(), + publication.getArtifactId(), + publication.getVersion()); + Logging.warn( + "Failed to extract `main` source set from publication `" + + publicationName + + "` in project `" + + projectName + + "`.\n" + + CROSS_REPO_BANNER + + "\nHere's the raw error message:\n \"" + + exception.getMessage() + + "\"\nContinuing without cross-repository support."); + } + } + } catch (Exception exception) { + Logging.warn( + "Failed to extract Maven publication from the project `" + + projectName + + "`.\n" + + CROSS_REPO_BANNER + + "\nHere's the raw error message (" + + exception.getClass() + + "):\n \"" + + exception.getMessage() + + "\"\nContinuing without cross-repository support."); + } + + project + .getConfigurations() + .forEach( + conf -> { + if (conf.isCanBeResolved()) { + try { + conf.getResolvedConfiguration() + .getResolvedArtifacts() + .forEach( + artifact -> + deps.add( + String.join( + "\t", + artifact.getModuleVersion().getId().getGroup(), + artifact.getModuleVersion().getId().getName(), + artifact.getModuleVersion().getId().getVersion(), + artifact.getFile().getAbsolutePath()))); + } catch (Exception exc) { + System.out.println( + "Skipping configuration '" + + conf.getName() + + "' due to resolution failure: " + + exc.getMessage()); + } + } + }); + + List dependencies = deps.stream().distinct().collect(Collectors.toList()); + + if (dependenciesPath.isPresent()) { + Files.write( + dependenciesPath.get(), + dependencies, + StandardOpenOption.APPEND, + StandardOpenOption.CREATE); + } else { + dependencies.forEach(System.out::println); + } + } +} diff --git a/scip-gradle-plugin/src/main/scala/ScipGradlePlugin.scala b/scip-gradle-plugin/src/main/scala/ScipGradlePlugin.scala deleted file mode 100644 index f635abb2..00000000 --- a/scip-gradle-plugin/src/main/scala/ScipGradlePlugin.scala +++ /dev/null @@ -1,397 +0,0 @@ -package com.sourcegraph.gradle.scip - -import java.nio.file.Files -import java.nio.file.Paths -import java.{util => ju} - -import scala.jdk.CollectionConverters._ -import scala.util._ - -import com.sourcegraph.scip_java.BuildInfo -import org.gradle.api.DefaultTask -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.publish.PublishingExtension -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.tasks.SourceSetContainer -import org.gradle.api.tasks.TaskAction -import org.gradle.api.tasks.compile.JavaCompile - -class ScipGradlePlugin extends Plugin[Project] { - import Logging._ - - override def apply(project: Project): Unit = { - project.afterEvaluate { project => - project.getRepositories().add(project.getRepositories().mavenCentral()) - project.getRepositories().add(project.getRepositories().mavenLocal()) - - val extra = project.getExtensions().getExtraProperties() - val extraProperties = extra.getProperties().asScala - - val targetRoot = extra - .getProperties() - .asScala - .getOrElse("scipTarget", project.getBuildDir()) - - val javacPluginVersion = BuildInfo.version - - val javacPluginJar = extraProperties - .get("javacPluginJar") - .map(_.asInstanceOf[String]) - - val javacPluginDep = javacPluginJar - .map[Object](jar => project.files(jar)) - // we fallback to javac plugin published to maven if there is no jar specified - // the JAR would usually be provided by auto-indexer - .getOrElse(s"com.sourcegraph:scip-javac:${javacPluginVersion}") - - val sourceRoot = project.getRootDir() - - val tasks = project.getTasks() - - // List of compilation commands that we will need to trigger - // to index all the sources in the project we care about. - // This list is built progressively as we check for java and kotlin - // plugins - val triggers = List.newBuilder[String] - - if (project.getPlugins().hasPlugin("java")) { - - triggers += "compileJava" - triggers += "compileTestJava" - - val hasAnnotationPath = Try( - project.getConfigurations().getByName("annotationProcessor") - ).map(apConfig => - if (apConfig.isCanBeResolved()) { - apConfig.getDependencies().size() > 0 - } else - false - ) - .toOption - .contains(true) - - val compilerPluginAdded = - try { - project.getDependencies().add("compileOnly", javacPluginDep) - - if (hasAnnotationPath) { - project - .getDependencies() - .add("annotationProcessor", javacPluginDep) - } - - project.getDependencies().add("testCompileOnly", javacPluginDep) - - true - } catch { - case exc: Exception => - // The `compileOnly` configuration has likely already been - // resolved by another plugin or buildscript, so we can no longer - // add new dependencies to it. The project will be skipped (no - // SCIP output) and the post-build check in - // `GradleBuildTool` will surface a clearer error. - warn( - s"scip-java: failed to attach SCIP compiler plugin to project '${project - .getName()}' (${exc.getClass().getSimpleName()}: ${exc - .getMessage()}). This subproject will not be indexed." - ) - false - } - - project - .getTasks() - .withType(classOf[JavaCompile]) - .configureEach { task => - // Add --add-exports JVM args so our compiler plugin can access - // javac internals. Required on JDK 17+ (JEP 403), no-op on 11-16. - val forkOptions = task.getOptions().getForkOptions() - val jvmArgs = - BuildInfo.javacModuleOptions.map(_.stripPrefix("-J")).asJava - forkOptions.getJvmArgs() match { - case null => - forkOptions.setJvmArgs(jvmArgs) - case _ => - forkOptions.getJvmArgs().addAll(jvmArgs) - } - - task.getOptions().setFork(true) - task.getOptions().setIncremental(false) - - if (compilerPluginAdded) { - val args = task.getOptions().getCompilerArgs() - - // It's important we don't add the plugin configuration more than - // once, as javac considers that an error - if (!args.asScala.exists(_.startsWith("-Xplugin:scip"))) { - args.addAll( - List( - // We add this to ensure that the sources are _always_ - // recompiled, so that Gradle doesn't cache any state - // TODO: before this plugin is published to Maven Central, - // we will need to revert this change - as it can have detrimental - // effect on people's builds - s"-Xplugin:scip -targetroot:$targetRoot -sourceroot:$sourceRoot -randomtimestamp=${System - .nanoTime()}" - ).asJava - ) - } - } - } - } - - val isKotlinMultiplatform = project - .getPlugins() - .asScala - .exists(_.getClass().getName().contains("KotlinMultiplatform")) - - if (project.getPlugins().hasPlugin("kotlin") || isKotlinMultiplatform) { - if (isKotlinMultiplatform) { - triggers += "compileKotlinJvm" - triggers += "compileTestKotlinJvm" - } else { - triggers += "compileKotlin" - triggers += "compileTestKotlin" - } - - project - .getTasks - .configureEach { task => - if (task.getClass().getSimpleName().contains("KotlinCompile")) { - - // I we actually refer to KotlinCompile at _any_ point here, then - // plugin fails with NoClassDefFoundError - because the plugin - // classpath is murky - // - // We also don't want to bundle kotlin plugin with this one as it - // can cause all sorts of troubles). - // - // Instead, we commit the sins of reflection for our limited - // needs. - val compilerArgs = task - .asInstanceOf[{ - def getKotlinOptions(): { - def getFreeCompilerArgs(): ju.List[String] - def setFreeCompilerArgs(args: ju.List[String]): Unit - // def getLanguageVersion(): Any - } - }] - .getKotlinOptions() - - // The scip-kotlinc compiler plugin is now built and shipped - // together with the scip-java CLI. The CLI's init script writes - // the absolute path of the embedded jar into the - // `scipKotlincJar` extra property so we no longer need to - // resolve a separately-published artifact at apply-time. - val scipKotlinc = extraProperties - .get("scipKotlincJar") - .map(_.asInstanceOf[String]) - .getOrElse { - throw new IllegalStateException( - "scipKotlincJar extra property must be set by the " + - "scip-java init script when indexing Kotlin sources" - ) - } - - val newArgs = - new ju.ArrayList[String]( - compilerArgs.getFreeCompilerArgs().size + 5 - ) - newArgs.addAll(compilerArgs.getFreeCompilerArgs()) - newArgs.addAll( - List( - "-Xplugin=" + scipKotlinc, - "-P", - s"plugin:scip-kotlinc:sourceroot=$sourceRoot", - "-P", - s"plugin:scip-kotlinc:targetroot=$targetRoot" - ).asJava - ) - - compilerArgs.setFreeCompilerArgs(newArgs) - } - } - } - - tasks.create( - "scipCompileAll", - { task => - triggers - .result() - .foldLeft(task) { case (tsk, trig) => - tsk.dependsOn(tasks.getByName(trig)) - } - - } - ) - - tasks.create("scipPrintDependencies", classOf[WriteDependencies]) - - } - - } - -} - -class WriteDependencies extends DefaultTask { - import Logging._ - - @TaskAction - def printResolvedDependencies(): Unit = { - - val depsOut = Option( - getProject().getExtensions().getExtraProperties().get("dependenciesOut") - ).map(_.toString).map(Paths.get(_)) - - depsOut.foreach(path => - java.nio.file.Files.createDirectories(path.getParent()) - ) - - val deps = List.newBuilder[String] - val project = getProject() - val projectName = project.getName() - val projectPath = project.getPath().replaceAll("[^a-z0-9A-Z_-]", "_") - val dependenciesPath = depsOut.map { path => - val filename = path.getFileName() - if (filename.endsWith("dependencies.txt")) { - val last = projectPath + "." + path.getFileName().toString() - path.getParent().resolve(last) - } else - path - } - - // List the project itself as a dependency so that we can assign project name/version to symbols that are defined in this project. - // The code below is roughly equivalent to the following with Groovy: - // deps += "$publication.groupId $publication.artifactId $publication.version $sourceSets.main.output.classesDirectory" - - val crossRepoBanner = - """ - |This will not prevent a SCIP index from being created, but the symbols - |extracted from this project won't be available for cross-repository navigation, - |as this project doesn't define any Maven coordinates by which it can be referred back to. - |See here for more details: https://sourcegraph.github.io/scip-java/docs/manual-configuration.html#step-5-optional-enable-cross-repository-navigation - """ - - Try( - project - .getExtensions() - .findByType(classOf[PublishingExtension]) - .getPublications() - .withType(classOf[MavenPublication]) - .asScala - ) match { - case Failure(exception) => - warn( - s""" - |Failed to extract Maven publication from the project `$projectName`. - $crossRepoBanner - |Here's the raw error message (${exception.getClass()}): - | "${exception.getMessage()}" - |Continuing without cross-repository support. - """.stripMargin.trim() - ) - - case Success(publications) => - publications.foreach { publication => - Try( - project - .getExtensions() - .getByType(classOf[SourceSetContainer]) - .getByName("main") - ) match { - case Failure(exception) => - val publicationName = List( - publication.getGroupId(), - publication.getArtifactId(), - publication.getVersion() - ).mkString(":") - - warn( - s""" - |Failed to extract `main` source set from publication `${publicationName}` in project `$projectName``. - $crossRepoBanner - |Here's the raw error message: - | "${exception.getMessage()}" - |Continuing without cross-repository support. - """.stripMargin.trim() - ) - - case Success(value) => - value - .getOutput() - .getClassesDirs() - .getFiles() - .asScala - .toList - .map(_.getAbsolutePath()) - .sorted - .take(1) - .foreach { classesDirectory => - deps += - List( - publication.getGroupId(), - publication.getArtifactId(), - publication.getVersion(), - classesDirectory - ).mkString("\t") - } - - } - } - } - - project - .getConfigurations() - .forEach { conf => - if (conf.isCanBeResolved()) { - try { - val resolved = conf.getResolvedConfiguration() - resolved - .getResolvedArtifacts() - .forEach { artif => - deps += - List( - artif.getModuleVersion().getId().getGroup(), - artif.getModuleVersion().getId().getName(), - artif.getModuleVersion().getId().getVersion(), - artif.getFile().getAbsolutePath() - ).mkString("\t") - - } - } catch { - case exc: Exception => - println( - s"Skipping configuration '${conf - .getName()}' due to resolution failure: ${exc.getMessage()}" - ) - } - - } - } - - val dependencies = deps.result().distinct - - dependenciesPath match { - case None => - dependencies.foreach(println) - case Some(path) => - Files.write( - path, - dependencies.asJava, - java.nio.file.StandardOpenOption.APPEND, - java.nio.file.StandardOpenOption.CREATE - ) - } - } -} - -private object Logging { - def info(msg: Any*) = System - .err - .println(s"[INFO] [scip-java.gradle] ${msg.mkString(" ")}") - - def warn(msg: Any*) = System - .err - .println(s"[WARNING] [scip-java.gradle] ${msg.mkString(" ")}") - -} From c8b01571df1b6997427003d8abb1bdb2b1de819b Mon Sep 17 00:00:00 2001 From: jupblb Date: Mon, 22 Jun 2026 20:12:42 +0200 Subject: [PATCH 2/4] Inline scip-gradle-plugin BuildInfo and Logging helpers --- build.sbt | 14 +---- .../sourcegraph/gradle/scip/BuildInfo.java | 56 ------------------- .../com/sourcegraph/gradle/scip/Logging.java | 10 ---- .../gradle/scip/ScipGradlePlugin.java | 52 ++++++++++------- .../gradle/scip/WriteDependencies.java | 38 +++++++------ 5 files changed, 53 insertions(+), 117 deletions(-) delete mode 100644 scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/BuildInfo.java delete mode 100644 scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/Logging.java diff --git a/build.sbt b/build.sbt index 59a01bdf..f43b619a 100644 --- a/build.sbt +++ b/build.sbt @@ -99,19 +99,7 @@ lazy val gradlePlugin = project List( "dev.gradleplugins" % "gradle-api" % V.gradle % Provided, "dev.gradleplugins" % "gradle-test-kit" % V.gradle % Provided - ), - // The plugin's BuildInfo.version (the scip-javac Maven coordinate used as a - // fallback when no jar is provided) is read at runtime from this generated - // properties resource, mirroring the scip-java CLI's scip-java.properties. - (Compile / resourceGenerators) += - Def.task { - val file = (Compile / resourceManaged).value / "scip-gradle.properties" - IO.createDirectory(file.getParentFile) - val props = new Properties() - props.put("version", version.value) - IO.write(props, "scip-gradle", file) - Seq(file) - }.taskValue + ) ) lazy val javacPlugin = project diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/BuildInfo.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/BuildInfo.java deleted file mode 100644 index f73cca7d..00000000 --- a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/BuildInfo.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.sourcegraph.gradle.scip; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Properties; - -/** - * Build metadata for the SCIP Gradle plugin. - * - *

Replaces the previously sbt-generated {@code com.sourcegraph.scip_java.BuildInfo}. The static - * {@link #javacModuleOptions} live here directly; the build-time {@link #version} is injected by - * sbt into the {@code scip-gradle.properties} classpath resource (see the {@code - * resourceGenerators} for the {@code gradlePlugin} project in build.sbt). - */ -final class BuildInfo { - - private BuildInfo() {} - - /** - * {@code --add-exports} flags required to access internal javac APIs from the SCIP compiler - * plugin. Java 11+ is the supported baseline. - * - *

Kept in sync with {@code javacModuleOptions} in build.sbt, which applies the same flags when - * compiling the plugin and the test fixtures. - */ - static final List javacModuleOptions = - Collections.unmodifiableList( - Arrays.asList( - "-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED")); - - /** - * The scip-java release version. Read from the {@code scip-gradle.properties} resource that sbt - * generates into the jar. Falls back to a placeholder when running without the generated resource - * on the classpath. - */ - static final String version = loadVersion(); - - private static String loadVersion() { - Properties props = new Properties(); - try (InputStream in = BuildInfo.class.getResourceAsStream("/scip-gradle.properties")) { - if (in != null) { - props.load(in); - } - } catch (IOException e) { - // Fall back to the placeholder below. - } - return props.getProperty("version", "0.0.0-SNAPSHOT"); - } -} diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/Logging.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/Logging.java deleted file mode 100644 index 61c811c0..00000000 --- a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/Logging.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.sourcegraph.gradle.scip; - -final class Logging { - - private Logging() {} - - static void warn(String message) { - System.err.println("[WARNING] [scip-java.gradle] " + message); - } -} diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java index 700bc8db..1aaaaaba 100644 --- a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java +++ b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java @@ -15,6 +15,17 @@ public class ScipGradlePlugin implements Plugin { + // `--add-exports` flags required so our compiler plugin can access javac + // internals. Required on JDK 17+ (JEP 403), no-op on 11-16. Kept in sync with + // `javacModuleOptions` in build.sbt. + private static final List JAVAC_MODULE_OPTIONS = + List.of( + "-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", + "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"); + @Override public void apply(Project project) { project.afterEvaluate(this::configureProject); @@ -29,17 +40,6 @@ private void configureProject(Project project) { Object targetRoot = extraProperties.getOrDefault("scipTarget", project.getBuildDir()); - String javacPluginVersion = BuildInfo.version; - - Object javacPluginJar = extraProperties.get("javacPluginJar"); - - // We fall back to the javac plugin published to Maven if there is no jar - // specified. The JAR would usually be provided by the auto-indexer. - Object javacPluginDep = - javacPluginJar != null - ? project.files(javacPluginJar) - : "com.sourcegraph:scip-javac:" + javacPluginVersion; - File sourceRoot = project.getRootDir(); // List of compilation commands that we will need to trigger to index all @@ -61,6 +61,16 @@ private void configureProject(Project project) { boolean compilerPluginAdded; try { + // The CLI's init script writes the absolute path of the embedded + // scip-javac jar into the `javacPluginJar` extra property. + Object javacPluginJar = extraProperties.get("javacPluginJar"); + if (javacPluginJar == null) { + throw new IllegalStateException( + "javacPluginJar extra property must be set by the " + + "scip-java init script when indexing Java sources"); + } + Object javacPluginDep = project.files(javacPluginJar); + project.getDependencies().add("compileOnly", javacPluginDep); if (hasAnnotationPath) { @@ -76,14 +86,16 @@ private void configureProject(Project project) { // dependencies to it. The project will be skipped (no SCIP output) and // the post-build check in `GradleBuildTool` will surface a clearer // error. - Logging.warn( - "scip-java: failed to attach SCIP compiler plugin to project '" - + project.getName() - + "' (" - + exc.getClass().getSimpleName() - + ": " - + exc.getMessage() - + "). This subproject will not be indexed."); + project + .getLogger() + .warn( + "scip-java: failed to attach SCIP compiler plugin to project '" + + project.getName() + + "' (" + + exc.getClass().getSimpleName() + + ": " + + exc.getMessage() + + "). This subproject will not be indexed."); compilerPluginAdded = false; } @@ -98,7 +110,7 @@ private void configureProject(Project project) { // 11-16. ForkOptions forkOptions = task.getOptions().getForkOptions(); List jvmArgs = - BuildInfo.javacModuleOptions.stream() + JAVAC_MODULE_OPTIONS.stream() .map(arg -> arg.startsWith("-J") ? arg.substring(2) : arg) .collect(Collectors.toList()); if (forkOptions.getJvmArgs() == null) { diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java index 61eb7bb4..4c3a65ff 100644 --- a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java +++ b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java @@ -87,29 +87,31 @@ public void printResolvedDependencies() throws IOException { publication.getGroupId(), publication.getArtifactId(), publication.getVersion()); - Logging.warn( - "Failed to extract `main` source set from publication `" - + publicationName - + "` in project `" + getLogger() + .warn( + "Failed to extract `main` source set from publication `" + + publicationName + + "` in project `" + + projectName + + "`.\n" + + CROSS_REPO_BANNER + + "\nHere's the raw error message:\n \"" + + exception.getMessage() + + "\"\nContinuing without cross-repository support."); + } + } + } catch (Exception exception) { + getLogger() + .warn( + "Failed to extract Maven publication from the project `" + projectName + "`.\n" + CROSS_REPO_BANNER - + "\nHere's the raw error message:\n \"" + + "\nHere's the raw error message (" + + exception.getClass() + + "):\n \"" + exception.getMessage() + "\"\nContinuing without cross-repository support."); - } - } - } catch (Exception exception) { - Logging.warn( - "Failed to extract Maven publication from the project `" - + projectName - + "`.\n" - + CROSS_REPO_BANNER - + "\nHere's the raw error message (" - + exception.getClass() - + "):\n \"" - + exception.getMessage() - + "\"\nContinuing without cross-repository support."); } project From dfdf8ae58fd31e266446979aebaa4cb28a98ccd8 Mon Sep 17 00:00:00 2001 From: jupblb Date: Mon, 22 Jun 2026 20:33:51 +0200 Subject: [PATCH 3/4] Drop unneeded --add-exports flags and fork from gradle plugin --- .../gradle/scip/ScipGradlePlugin.java | 31 ++----------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java index 1aaaaaba..15da5598 100644 --- a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java +++ b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java @@ -4,28 +4,15 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.plugins.ExtraPropertiesExtension; -import org.gradle.api.tasks.compile.ForkOptions; import org.gradle.api.tasks.compile.JavaCompile; public class ScipGradlePlugin implements Plugin { - // `--add-exports` flags required so our compiler plugin can access javac - // internals. Required on JDK 17+ (JEP 403), no-op on 11-16. Kept in sync with - // `javacModuleOptions` in build.sbt. - private static final List JAVAC_MODULE_OPTIONS = - List.of( - "-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", - "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED", - "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED", - "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", - "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"); - @Override public void apply(Project project) { project.afterEvaluate(this::configureProject); @@ -105,21 +92,9 @@ private void configureProject(Project project) { .withType(JavaCompile.class) .configureEach( task -> { - // Add --add-exports JVM args so our compiler plugin can access - // javac internals. Required on JDK 17+ (JEP 403), no-op on - // 11-16. - ForkOptions forkOptions = task.getOptions().getForkOptions(); - List jvmArgs = - JAVAC_MODULE_OPTIONS.stream() - .map(arg -> arg.startsWith("-J") ? arg.substring(2) : arg) - .collect(Collectors.toList()); - if (forkOptions.getJvmArgs() == null) { - forkOptions.setJvmArgs(jvmArgs); - } else { - forkOptions.getJvmArgs().addAll(jvmArgs); - } - - task.getOptions().setFork(true); + // Disable incremental compilation so the random timestamp added + // below forces a full recompile and Gradle doesn't cache stale + // SCIP state. task.getOptions().setIncremental(false); if (pluginAdded) { From 238bd49c66474de00acb4c6187beadaad63571ed Mon Sep 17 00:00:00 2001 From: jupblb Date: Mon, 22 Jun 2026 21:01:02 +0200 Subject: [PATCH 4/4] Simplify scip-gradle-plugin implementation --- .../gradle/scip/ScipGradlePlugin.java | 141 +++++++----------- .../gradle/scip/WriteDependencies.java | 141 ++++++++---------- 2 files changed, 121 insertions(+), 161 deletions(-) diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java index 15da5598..8d264bb1 100644 --- a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java +++ b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/ScipGradlePlugin.java @@ -1,6 +1,5 @@ package com.sourcegraph.gradle.scip; -import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -8,7 +7,6 @@ import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; -import org.gradle.api.plugins.ExtraPropertiesExtension; import org.gradle.api.tasks.compile.JavaCompile; public class ScipGradlePlugin implements Plugin { @@ -19,19 +17,20 @@ public void apply(Project project) { } private void configureProject(Project project) { + // Inject Maven Central/local so the indexer (and plugins like protobuf that + // resolve their own artifacts) can resolve dependencies even when the build + // being indexed doesn't declare any repositories of its own. project.getRepositories().add(project.getRepositories().mavenCentral()); project.getRepositories().add(project.getRepositories().mavenLocal()); - ExtraPropertiesExtension extra = project.getExtensions().getExtraProperties(); - Map extraProperties = extra.getProperties(); + Map extraProperties = + project.getExtensions().getExtraProperties().getProperties(); - Object targetRoot = extraProperties.getOrDefault("scipTarget", project.getBuildDir()); + String targetRoot = requiredExtra(extraProperties, "scipTarget").toString(); + String sourceRoot = project.getRootDir().toString(); - File sourceRoot = project.getRootDir(); - - // List of compilation commands that we will need to trigger to index all - // the sources in the project we care about. This list is built - // progressively as we check for java and kotlin plugins. + // Compilation tasks we need to trigger to index all the sources we care + // about. Built up as we detect the java and kotlin plugins. List triggers = new ArrayList<>(); if (project.getPlugins().hasPlugin("java")) { @@ -46,47 +45,9 @@ private void configureProject(Project project) { hasAnnotationPath = false; } - boolean compilerPluginAdded; - try { - // The CLI's init script writes the absolute path of the embedded - // scip-javac jar into the `javacPluginJar` extra property. - Object javacPluginJar = extraProperties.get("javacPluginJar"); - if (javacPluginJar == null) { - throw new IllegalStateException( - "javacPluginJar extra property must be set by the " - + "scip-java init script when indexing Java sources"); - } - Object javacPluginDep = project.files(javacPluginJar); - - project.getDependencies().add("compileOnly", javacPluginDep); - - if (hasAnnotationPath) { - project.getDependencies().add("annotationProcessor", javacPluginDep); - } - - project.getDependencies().add("testCompileOnly", javacPluginDep); + Object javacPluginDep = project.files(requiredExtra(extraProperties, "javacPluginJar")); + boolean pluginAdded = tryAddJavacPlugin(project, javacPluginDep, hasAnnotationPath); - compilerPluginAdded = true; - } catch (Exception exc) { - // The `compileOnly` configuration has likely already been resolved by - // another plugin or buildscript, so we can no longer add new - // dependencies to it. The project will be skipped (no SCIP output) and - // the post-build check in `GradleBuildTool` will surface a clearer - // error. - project - .getLogger() - .warn( - "scip-java: failed to attach SCIP compiler plugin to project '" - + project.getName() - + "' (" - + exc.getClass().getSimpleName() - + ": " - + exc.getMessage() - + "). This subproject will not be indexed."); - compilerPluginAdded = false; - } - - boolean pluginAdded = compilerPluginAdded; project .getTasks() .withType(JavaCompile.class) @@ -105,9 +66,8 @@ private void configureProject(Project project) { boolean alreadyAdded = args.stream().anyMatch(arg -> arg.startsWith("-Xplugin:scip")); if (!alreadyAdded) { - // We add the random timestamp to ensure that the sources - // are _always_ recompiled, so that Gradle doesn't cache any - // state. + // The random timestamp ensures the sources are _always_ + // recompiled, so Gradle doesn't cache any state. // TODO: before this plugin is published to Maven Central, we // will need to revert this change - as it can have // detrimental effect on people's builds. @@ -140,53 +100,59 @@ private void configureProject(Project project) { triggers.add("compileTestKotlin"); } + // The CLI's init script provides the path of the embedded scip-kotlinc jar. + Object scipKotlinc = requiredExtra(extraProperties, "scipKotlincJar"); project .getTasks() .configureEach( - task -> configureKotlinCompileTask(task, extraProperties, sourceRoot, targetRoot)); + task -> configureKotlinCompileTask(task, scipKotlinc, sourceRoot, targetRoot)); } - project - .getTasks() - .create( - "scipCompileAll", - task -> { - for (String trigger : triggers) { - task.dependsOn(project.getTasks().getByName(trigger)); - } - }); + project.getTasks().create("scipCompileAll").dependsOn(triggers); project.getTasks().create("scipPrintDependencies", WriteDependencies.class); } + private static boolean tryAddJavacPlugin( + Project project, Object javacPluginDep, boolean hasAnnotationPath) { + try { + project.getDependencies().add("compileOnly", javacPluginDep); + if (hasAnnotationPath) { + project.getDependencies().add("annotationProcessor", javacPluginDep); + } + project.getDependencies().add("testCompileOnly", javacPluginDep); + return true; + } catch (Exception exc) { + // The `compileOnly` configuration has likely already been resolved by + // another plugin or buildscript, so we can no longer add new dependencies + // to it. The project will be skipped (no SCIP output) and the post-build + // check in `GradleBuildTool` will surface a clearer error. + project + .getLogger() + .warn( + "scip-java: failed to attach SCIP compiler plugin to project '" + + project.getName() + + "' (" + + exc.getClass().getSimpleName() + + ": " + + exc.getMessage() + + "). This subproject will not be indexed."); + return false; + } + } + private static void configureKotlinCompileTask( - Task task, Map extraProperties, Object sourceRoot, Object targetRoot) { + Task task, Object scipKotlinc, String sourceRoot, String targetRoot) { if (!task.getClass().getSimpleName().contains("KotlinCompile")) { return; } - // If we actually refer to KotlinCompile at _any_ point here, then the - // plugin fails with NoClassDefFoundError - because the plugin classpath is - // murky. - // - // We also don't want to bundle the kotlin plugin with this one as it can - // cause all sorts of troubles. - // - // Instead, we commit the sins of reflection for our limited needs. + // Referring to KotlinCompile directly here triggers NoClassDefFoundError - + // the plugin classpath is murky and we deliberately don't bundle the Kotlin + // Gradle plugin. So we commit the sins of reflection for our limited needs. try { Object kotlinOptions = task.getClass().getMethod("getKotlinOptions").invoke(task); - // The scip-kotlinc compiler plugin is now built and shipped together with - // the scip-java CLI. The CLI's init script writes the absolute path of - // the embedded jar into the `scipKotlincJar` extra property so we no - // longer need to resolve a separately-published artifact at apply-time. - Object scipKotlinc = extraProperties.get("scipKotlincJar"); - if (scipKotlinc == null) { - throw new IllegalStateException( - "scipKotlincJar extra property must be set by the " - + "scip-java init script when indexing Kotlin sources"); - } - @SuppressWarnings("unchecked") List freeCompilerArgs = (List) @@ -209,4 +175,13 @@ private static void configureKotlinCompileTask( "scip-java: failed to configure Kotlin compile task '" + task.getName() + "'", exc); } } + + private static Object requiredExtra(Map extraProperties, String name) { + Object value = extraProperties.get(name); + if (value == null) { + throw new IllegalStateException( + name + " extra property must be set by the scip-java Gradle init script"); + } + return value; + } } diff --git a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java index 4c3a65ff..8f121a5e 100644 --- a/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java +++ b/scip-gradle-plugin/src/main/java/com/sourcegraph/gradle/scip/WriteDependencies.java @@ -6,10 +6,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; +import java.util.LinkedHashSet; +import java.util.Set; import org.gradle.api.DefaultTask; import org.gradle.api.Project; import org.gradle.api.publish.PublishingExtension; @@ -32,72 +30,68 @@ public class WriteDependencies extends DefaultTask { public void printResolvedDependencies() throws IOException { Project project = getProject(); - Optional depsOut = - Optional.ofNullable(project.getExtensions().getExtraProperties().get("dependenciesOut")) - .map(Object::toString) - .map(Paths::get); + // Always set by the scip-java Gradle init script. + Path depsOut = + Paths.get(project.getExtensions().getExtraProperties().get("dependenciesOut").toString()); + Files.createDirectories(depsOut.getParent()); - if (depsOut.isPresent()) { - Files.createDirectories(depsOut.get().getParent()); - } - - List deps = new ArrayList<>(); String projectName = project.getName(); String projectPath = project.getPath().replaceAll("[^a-z0-9A-Z_-]", "_"); - // List the project itself as a dependency so that we can assign project - // name/version to symbols that are defined in this project. - Optional dependenciesPath = - depsOut.map( - path -> { - Path filename = path.getFileName(); - if (filename.endsWith("dependencies.txt")) { - String last = projectPath + "." + filename.toString(); - return path.getParent().resolve(last); - } else { - return path; - } - }); + // Write to a per-project file so multi-module builds don't collide or + // corrupt each other via parallel appends. The CLI globs every file whose + // name ends with "dependencies.txt". + Path dependenciesPath = + depsOut.getFileName().toString().endsWith("dependencies.txt") + ? depsOut.resolveSibling(projectPath + "." + depsOut.getFileName()) + : depsOut; + // LinkedHashSet keeps first-seen order while dropping duplicates. + Set deps = new LinkedHashSet<>(); + + // List the project itself as a dependency so we can assign its Maven + // coordinates to the symbols it defines. try { PublishingExtension publishing = project.getExtensions().findByType(PublishingExtension.class); - for (MavenPublication publication : - publishing.getPublications().withType(MavenPublication.class)) { - try { - SourceSet main = - project.getExtensions().getByType(SourceSetContainer.class).getByName("main"); - main.getOutput().getClassesDirs().getFiles().stream() - .map(File::getAbsolutePath) - .sorted() - .limit(1) - .forEach( - classesDirectory -> - deps.add( - String.join( - "\t", - publication.getGroupId(), - publication.getArtifactId(), - publication.getVersion(), - classesDirectory))); - } catch (Exception exception) { - String publicationName = - String.join( - ":", - publication.getGroupId(), - publication.getArtifactId(), - publication.getVersion()); - getLogger() - .warn( - "Failed to extract `main` source set from publication `" - + publicationName - + "` in project `" - + projectName - + "`.\n" - + CROSS_REPO_BANNER - + "\nHere's the raw error message:\n \"" - + exception.getMessage() - + "\"\nContinuing without cross-repository support."); + if (publishing != null) { + for (MavenPublication publication : + publishing.getPublications().withType(MavenPublication.class)) { + try { + SourceSet main = + project.getExtensions().getByType(SourceSetContainer.class).getByName("main"); + main.getOutput().getClassesDirs().getFiles().stream() + .map(File::getAbsolutePath) + .sorted() + .limit(1) + .forEach( + classesDirectory -> + deps.add( + String.join( + "\t", + publication.getGroupId(), + publication.getArtifactId(), + publication.getVersion(), + classesDirectory))); + } catch (Exception exception) { + String publicationName = + String.join( + ":", + publication.getGroupId(), + publication.getArtifactId(), + publication.getVersion()); + getLogger() + .warn( + "Failed to extract `main` source set from publication `" + + publicationName + + "` in project `" + + projectName + + "`.\n" + + CROSS_REPO_BANNER + + "\nHere's the raw error message:\n \"" + + exception.getMessage() + + "\"\nContinuing without cross-repository support."); + } } } } catch (Exception exception) { @@ -132,25 +126,16 @@ public void printResolvedDependencies() throws IOException { artifact.getModuleVersion().getId().getVersion(), artifact.getFile().getAbsolutePath()))); } catch (Exception exc) { - System.out.println( - "Skipping configuration '" - + conf.getName() - + "' due to resolution failure: " - + exc.getMessage()); + getLogger() + .warn( + "Skipping configuration '" + + conf.getName() + + "' due to resolution failure: " + + exc.getMessage()); } } }); - List dependencies = deps.stream().distinct().collect(Collectors.toList()); - - if (dependenciesPath.isPresent()) { - Files.write( - dependenciesPath.get(), - dependencies, - StandardOpenOption.APPEND, - StandardOpenOption.CREATE); - } else { - dependencies.forEach(System.out::println); - } + Files.write(dependenciesPath, deps, StandardOpenOption.APPEND, StandardOpenOption.CREATE); } }