Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
23f6355
Don't add project repositories when settings repositories are set
nicolas-guichard Jul 7, 2026
6a8cc6c
Update to Kotlin 2.2.20
nicolas-guichard Jul 3, 2026
e8deeff
scip-kotlinc: Populate SymbolInformation.Kind
nicolas-guichard Jul 3, 2026
df1a7fc
scip-kotlinc: Rework getters and setters to be property children
nicolas-guichard Jul 3, 2026
d199e21
scip-kotlinc: Add enclosing_symbol field
nicolas-guichard Jul 3, 2026
44d0d50
scip-kotlinc: Ignore FirFileSymbols without warning
nicolas-guichard Jul 3, 2026
9dc22e6
scip-kotlinc: Clear AnalyzerCheckers.visitors after consuming them
nicolas-guichard Jul 3, 2026
9a3bd39
Update to Kotlin 2.3.10
rvandermeulen Jul 3, 2026
c7c6385
scip-kotlinc: Fix compiler warnings
rvandermeulen Jul 3, 2026
81a6715
scip-kotlinc: Fix PostAnalysisExtension to use the compilation's mess…
rvandermeulen Jul 3, 2026
05ad506
scip-kotlinc: Add test for lambda parameters
rvandermeulen Jul 3, 2026
5b0d4ae
scip-kotlinc: Add tests for local functions and user-defined class re…
rvandermeulen Jul 3, 2026
abe0751
scip-kotlinc: Fix displayName() for type aliases and type parameters
rvandermeulen Jul 3, 2026
50d70ca
scip-kotlinc: Fix extension receiver, enum entry, and is/as type occu…
rvandermeulen Jul 3, 2026
2681cbe
scip-kotlinc: Add tests for multiple supertypes and overload disambig…
rvandermeulen Jul 3, 2026
f489b7f
scip-kotlinc: Fix misleading LineMap docstrings
rvandermeulen Jul 3, 2026
6ebc606
scip-kotlinc: Extend enclosing_range test coverage
rvandermeulen Jul 3, 2026
e940c18
Update to Kotlin 2.3.20
rvandermeulen Jul 3, 2026
a2683f0
feat: add Kotlin K2 graph snapshots
samchon Sep 4, 2026
bef34e5
Add resident Kotlin graph build service
samchon Sep 4, 2026
84ad06a
fix: advertise Kotlin graph output capability
samchon Sep 4, 2026
558d470
fix: preserve Kotlin universe across source edits
samchon Sep 4, 2026
647d095
chore: normalize Kotlin graph sources
samchon Sep 4, 2026
22ec4bd
fix: keep the Windows launcher below cmd limits
samchon Sep 4, 2026
3a1565d
fix: reserve Kotlin graph server stdout
samchon Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
[versions]
clikt = "5.1.0"
gradle-api = "8.11.1"
gradle-tooling-api = "7.3-20210825160000+0000"
junit-jupiter = "5.11.4"
kctfork = "0.7.1"
kctfork = "0.12.1"
kotest = "6.2.1"
kotlin = "2.2.0"
kotlin = "2.3.20"
kotlinx-serialization = "1.11.0"
lombok = "1.18.46"
maven-plugin-annotations = "3.15.2"
Expand All @@ -21,9 +22,11 @@ vanniktech-maven-publish = "0.37.0"
clikt-jvm = { module = "com.github.ajalt.clikt:clikt-jvm", version.ref = "clikt" }
gradle-api = { module = "dev.gradleplugins:gradle-api", version.ref = "gradle-api" }
gradle-test-kit = { module = "dev.gradleplugins:gradle-test-kit", version.ref = "gradle-api" }
gradle-tooling-api = { module = "org.gradle:gradle-tooling-api", version.ref = "gradle-tooling-api" }
kctfork-core = { module = "dev.zacsweers.kctfork:core", version.ref = "kctfork" }
kotest-assertions-core = { module = "io.kotest:kotest-assertions-core-jvm", version.ref = "kotest" }
kotlin-compiler-embeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" }
kotlin-gradle-plugin-api = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlin-scripting-common = { module = "org.jetbrains.kotlin:kotlin-scripting-common", version.ref = "kotlin" }
kotlin-scripting-dependencies = { module = "org.jetbrains.kotlin:kotlin-scripting-dependencies", version.ref = "kotlin" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,74 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.gradle.api.InvalidUserCodeException;
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.initialization.Settings;
import org.gradle.api.internal.GradleInternal;
import org.gradle.api.tasks.compile.JavaCompile;

public class ScipGradlePlugin implements Plugin<Project> {

@Override
public void apply(Project project) {
Map<String, Object> extra = project.getExtensions().getExtraProperties().getProperties();
boolean kotlinGraphEnabled =
Boolean.parseBoolean(String.valueOf(extra.getOrDefault("scipKotlinGraphEnabled", false)));
if (kotlinGraphEnabled) {
project
.getPluginManager()
.withPlugin(
"org.jetbrains.kotlin.jvm",
ignored -> project.getPlugins().apply("org.scip-code.kotlin-graph"));
project
.getPluginManager()
.withPlugin(
"org.jetbrains.kotlin.multiplatform",
ignored ->
project
.getLogger()
.warn(
"scip-java: Kotlin graph exporter declines multiplatform project '{}'; only Kotlin/JVM is supported",
project.getPath()));
project
.getPluginManager()
.withPlugin(
"com.android.base",
ignored ->
project
.getLogger()
.warn(
"scip-java: Kotlin graph exporter declines Android project '{}'",
project.getPath()));
}
project.afterEvaluate(this::configureProject);
}

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.
try {
// See https://github.com/gradle/gradle/issues/27260
Settings settings = ((GradleInternal) (project.getGradle())).getSettings();

if (settings.getDependencyResolutionManagement().getRepositories().isEmpty()) {
// 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());
} catch (InvalidUserCodeException exc) {
// FAIL_ON_PROJECT_REPOS forbids project repositories; they are declared
// in settings instead, so the injection isn't needed (issue #847).
project
.getLogger()
.info("scip-java: not injecting Maven Central/local repositories: " + exc.getMessage());
} else {
// repositories are declared in settings instead, so the injection isn't needed (issue #847).
project.getLogger().info("scip-java: not injecting Maven Central/local repositories");
}

Map<String, Object> extraProperties =
project.getExtensions().getExtraProperties().getProperties();

String targetRoot = requiredExtra(extraProperties, "scipTarget").toString();
String sourceRoot = project.getRootDir().toString();
boolean kotlinGraphEnabled =
Boolean.parseBoolean(
String.valueOf(extraProperties.getOrDefault("scipKotlinGraphEnabled", false)));

// Compilation tasks we need to trigger to index all the sources we care
// about. Built up as we detect the java and kotlin plugins.
Expand Down Expand Up @@ -111,11 +145,13 @@ private void configureProject(Project project) {
}

// 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, scipKotlinc, sourceRoot, targetRoot));
if (!kotlinGraphEnabled) {
Object scipKotlinc = requiredExtra(extraProperties, "scipKotlincJar");
project
.getTasks()
.configureEach(
task -> configureKotlinCompileTask(task, scipKotlinc, sourceRoot, targetRoot));
}
}

project.getTasks().create("scipCompileAll").dependsOn(triggers);
Expand Down
24 changes: 24 additions & 0 deletions scip-java/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import org.scip_code.scip_java.buildlogic.JavacInternals
import org.scip_code.scip_java.buildlogic.registerGeneratedFile
import org.scip_code.scip_java.buildlogic.shadowJarArtifact
import org.gradle.jvm.application.tasks.CreateStartScripts

plugins {
id("scip.java-base")
Expand All @@ -13,6 +14,8 @@ description = "Java and Kotlin indexer for SCIP"

val javacShadowJar = shadowJarArtifact(":scip-javac", "javacShadowJar")
val gradlePluginShadowJar = shadowJarArtifact(":scip-gradle-plugin", "gradlePluginShadowJar")
val kotlinGradlePluginShadowJar =
shadowJarArtifact(":scip-kotlin-gradle-plugin", "kotlinGradlePluginShadowJar")
val kotlincShadowJar = shadowJarArtifact(":scip-kotlinc", "kotlincShadowJar")

dependencies {
Expand All @@ -25,6 +28,7 @@ dependencies {
implementation(libs.kotlin.scripting.dependencies)
implementation(libs.kotlin.scripting.dependencies.maven)
implementation(libs.kotlinx.serialization.json.jvm)
implementation(libs.gradle.tooling.api)

testImplementation(libs.kotlin.test)
testImplementation(libs.kotlin.test.junit5)
Expand All @@ -39,13 +43,33 @@ application {
mainClass.set("org.scip_code.scip_java.ScipJava")
}

// Expanding one absolute distribution path for every runtime jar can push the
// generated batch file past cmd.exe's 8,191-character command-line limit. Java
// expands a classpath wildcard itself, after cmd.exe has parsed the short
// command, while the distribution still copies the exact runtime classpath.
tasks.named<CreateStartScripts>("startScripts") {
doLast {
val script = windowsScript.readText()
val classpath = Regex("(?m)^set CLASSPATH=.*$")
check(classpath.containsMatchIn(script)) {
"generated Windows launcher has no classpath assignment"
}
windowsScript.writeText(
script.replace(classpath) { "set CLASSPATH=%APP_HOME%\\lib\\*" },
)
}
}

val generateEmbeddedResources = tasks.register<Sync>("generateEmbeddedResources") {
from(javacShadowJar) {
rename { "scip-plugin.jar" }
}
from(gradlePluginShadowJar) {
rename { "gradle-plugin.jar" }
}
from(kotlinGradlePluginShadowJar) {
rename { "kotlin-gradle-plugin.jar" }
}
from(kotlincShadowJar) {
rename { "scip-kotlinc.jar" }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.scip_code.scip_java

import java.io.InputStream
import java.io.PrintStream
import java.nio.file.Path
import java.nio.file.Paths
Expand All @@ -13,11 +14,14 @@ import java.nio.file.Paths
data class CliEnvironment(
val workingDirectory: Path = Paths.get("").toAbsolutePath(),
val environmentVariables: Map<String, String> = System.getenv(),
val standardInput: InputStream = System.`in`,
val standardOutput: PrintStream = System.out,
val standardError: PrintStream = System.err,
) {
fun withWorkingDirectory(cwd: Path): CliEnvironment = copy(workingDirectory = cwd)

fun withStandardInput(input: InputStream): CliEnvironment = copy(standardInput = input)

fun withStandardOutput(out: PrintStream): CliEnvironment = copy(standardOutput = out)

fun withStandardError(err: PrintStream): CliEnvironment = copy(standardError = err)
Expand Down
2 changes: 2 additions & 0 deletions scip-java/src/main/kotlin/org/scip_code/scip_java/Embedded.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ object Embedded {

fun gradlePluginJar(tmpDir: Path): Path = copyFile(tmpDir, "gradle-plugin.jar")

fun kotlinGradlePluginJar(tmpDir: Path): Path = copyFile(tmpDir, "kotlin-gradle-plugin.jar")

fun scipKotlincJar(tmpDir: Path): Path = copyFile(tmpDir, "scip-kotlinc.jar")

private fun javacErrorpath(tmp: Path): Path = tmp.resolve("errorpath.txt")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.scip_code.scip_java.buildtools.ProcessResult
import org.scip_code.scip_java.buildtools.ProcessRunner
import org.scip_code.scip_java.commands.AggregateCommand
import org.scip_code.scip_java.commands.IndexCommand
import org.scip_code.scip_java.commands.KotlinGraphServerCommand
import org.scip_code.scip_java.commands.SnapshotCommand

/**
Expand Down Expand Up @@ -66,7 +67,12 @@ class ScipJavaApp {
val processedArgs = applyGlobalCwd(rewriteNestedOptions(args))
val root = RootCommand(this)
root.versionOption(ScipJava.version, names = setOf("--version", "-v"))
root.subcommands(IndexCommand(), AggregateCommand(), SnapshotCommand())
root.subcommands(
IndexCommand(),
AggregateCommand(),
SnapshotCommand(),
KotlinGraphServerCommand(),
)
return try {
root.parse(processedArgs)
// Commands signal failure only by throwing; reaching here is a clean exit.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.scip_code.scip_java.buildtools

import java.nio.charset.StandardCharsets
import java.nio.file.AtomicMoveNotSupportedException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardCopyOption
import org.scip_code.scip_java.Embedded
import org.scip_code.scip_java.commands.IndexCommand
import org.scip_code.scip_java.commands.KotlinGraphAggregateRunner

class GradleBuildTool(index: IndexCommand) : BuildTool("Gradle", index) {

Expand All @@ -16,6 +19,11 @@ class GradleBuildTool(index: IndexCommand) : BuildTool("Gradle", index) {

override fun generateScip(): Int {
val gradleResult = runBuild()
val graphOutput = index.kotlinGraphOutput
if (graphOutput != null) {
if (gradleResult.exitCode != 0) return gradleResult.exitCode
return KotlinGraphAggregateRunner.run(graphOutput, listOf(targetroot()), index.app)
}
if (gradleResult.exitCode == 0) {
val missing = reportMissingScipOutput()
if (missing != 0) return missing
Expand Down Expand Up @@ -74,59 +82,97 @@ This means our SCIP compiler plugin was not attached to one or more JavaCompile
private val defaultTargetroot: Path = Paths.get("build", "scip-targetroot")

private fun runBuild(): ProcessResult {
val gradleWrapper = index.workingDirectory.resolve("gradlew")
val windows = System.getProperty("os.name").startsWith("Windows", ignoreCase = true)
val gradleWrapper =
index.workingDirectory.resolve(if (windows) "gradlew.bat" else "gradlew")
val gradleCommand =
if (Files.isRegularFile(gradleWrapper) && Files.isExecutable(gradleWrapper))
if (
Files.isRegularFile(gradleWrapper) && (windows || Files.isExecutable(gradleWrapper))
)
gradleWrapper.toString()
else "gradle"
else if (windows) "gradle.bat" else "gradle"
return TemporaryFiles.withDirectory(index) { tmp -> runCompileCommand(tmp, gradleCommand) }
}

private fun runCompileCommand(tmp: Path, gradleCommand: String): ProcessResult {
val script = initScript(tmp).toString()
val cmd = mutableListOf<String>()
cmd += gradleCommand
cmd += "--no-daemon"
cmd += "--init-script"
cmd += script
cmd += "-Pkotlin.compiler.execution.strategy=in-process"
cmd += "-Dscip.targetroot=${targetroot()}"
cmd += index.finalBuildCommand(listOf("clean", "scipPrintDependencies", "scipCompileAll"))

targetroot().toFile().deleteRecursively()
if (index.kotlinGraphOutput == null) {
cmd += "--no-daemon"
cmd += "-Pkotlin.compiler.execution.strategy=in-process"
cmd +=
index.finalBuildCommand(listOf("clean", "scipPrintDependencies", "scipCompileAll"))
targetroot().toFile().deleteRecursively()
} else {
cmd += "-Pkotlin.build.report.output=json"
cmd +=
"-Pkotlin.build.report.json.directory=${targetroot().resolve("META-INF/kotlin-build-reports")}"
cmd += index.finalBuildCommand(listOf("samchonCommitKotlinGraph"))
}
val result = index.app.runProcess(cmd, env = mapOf("TERM" to "dumb"))
return Embedded.reportUnexpectedJavacErrors(index.app.reporter, tmp) ?: result
}

private fun initScript(tmp: Path): Path {
if (index.kotlinGraphOutput != null) {
return KotlinGraphGradleIntegration.prepare(index.workingDirectory, targetroot(), tmp)
.initScript
}
val pluginpath = Embedded.scipJar(tmp)
val gradlePluginPath = Embedded.gradlePluginJar(tmp)
val scipKotlincPath = Embedded.scipKotlincJar(tmp)
val dependenciesPath = targetroot().resolve("dependencies.txt")
Files.deleteIfExists(dependenciesPath)
fun scriptPath(path: Path): String = path.toString().replace('\\', '/')

val script =
"""
initscript {
repositories {
mavenCentral()
}
dependencies{
classpath(files("${gradlePluginPath}"))
classpath(files("${scriptPath(gradlePluginPath)}"))
}
}

import org.scip_code.scip_java.gradle.ScipGradlePlugin

allprojects {
project.ext["scipTarget"] = "${targetroot()}"
project.ext["javacPluginJar"] = "$pluginpath"
project.ext["dependenciesOut"] = "$dependenciesPath"
project.ext["scipKotlincJar"] = "$scipKotlincPath"
project.ext["scipTarget"] = "${scriptPath(targetroot())}"
project.ext["javacPluginJar"] = "${scriptPath(pluginpath)}"
project.ext["dependenciesOut"] = "${scriptPath(dependenciesPath)}"
project.ext["scipKotlincJar"] = "${scriptPath(scipKotlincPath)}"
project.ext["scipKotlinGraphEnabled"] = false
apply plugin: ScipGradlePlugin
}
"""
.trimIndent()

val out = tmp.resolve("init-script.gradle")
Files.write(out, script.toByteArray(StandardCharsets.UTF_8))
writeIfChanged(out, script.toByteArray(StandardCharsets.UTF_8))
return out
}

private fun writeIfChanged(output: Path, bytes: ByteArray) {
if (Files.isRegularFile(output) && Files.readAllBytes(output).contentEquals(bytes)) return
Files.createDirectories(output.parent)
val temporary =
output.resolveSibling("${output.fileName}.tmp-${ProcessHandle.current().pid()}")
Files.write(temporary, bytes)
try {
Files.move(
temporary,
output,
StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.REPLACE_EXISTING,
)
} catch (_: AtomicMoveNotSupportedException) {
Files.move(temporary, output, StandardCopyOption.REPLACE_EXISTING)
}
}
}
Loading