Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@ fun parseSourceFile(file: File, psiManager: PsiManager): SourceFileMetadata? {
* Prefer the overload that accepts a [PsiManager] for batch parsing.
*/
fun parseSourceFile(file: File): SourceFileMetadata? {
if (!file.isFile) return null
if (file.extension != "kt" && file.extension != "java") return null
return PsiEnvironment().use { env -> parseSourceFile(file, env.psiManager) }
if (!file.isFile || file.extension !in setOf("kt", "java")) return null
val env = PsiEnvironment.shared() ?: return null
return synchronized(env) { parseSourceFile(file, env.psiManager) }
}

/** Resolved type-level info from the first class/object in a Kotlin file. */
Expand Down Expand Up @@ -303,8 +303,8 @@ fun scanSources(srcDirs: List<File>): List<SourceFileMetadata> {
}
if (files.isEmpty()) return emptyList()

return PsiEnvironment().use { env ->
val psiManager = env.psiManager
files.mapNotNull { file -> parseSourceFile(file, psiManager) }
val env = PsiEnvironment.shared() ?: return emptyList()
return synchronized(env) {
files.mapNotNull { file -> parseSourceFile(file, env.psiManager) }
}
}
40 changes: 40 additions & 0 deletions src/main/kotlin/zone/clanker/gradle/srcx/parse/PsiEnvironment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,44 @@ class PsiEnvironment : AutoCloseable {
override fun close() {
Disposer.dispose(disposable)
}

companion object {
private val logger =
org.gradle.api.logging.Logging
.getLogger(PsiEnvironment::class.java)

@Volatile
private var shared: PsiEnvironment? = null
private val lock = Any()

/**
* Get or create a shared PsiEnvironment instance.
*
* The IntelliJ platform's ApplicationManager is not thread-safe,
* so we create exactly one instance and reuse it across all
* analysis calls. The lock ensures only one thread initializes.
*/
fun shared(): PsiEnvironment? {
shared?.let { return it }
synchronized(lock) {
shared?.let { return it }
return runCatching { PsiEnvironment() }
.onSuccess { shared = it }
.onFailure { e ->
logger.error(
"srcx: PSI environment initialization failed: ${e.message}. " +
"Hub classes and entry points will not be available.",
)
}.getOrNull()
}
}

/** Release the shared instance. Call once when all analysis is done. */
fun closeShared() {
synchronized(lock) {
shared?.close()
shared = null
}
}
}
}
2 changes: 2 additions & 0 deletions src/main/kotlin/zone/clanker/gradle/srcx/task/ContextTask.kt
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ abstract class ContextTask : DefaultTask() {
writeSplitFiles(dir, summaryList, includedBuildSummaries, buildEdges, aggregatedSummary)

ReportWriter.writeGitignore(root, outDir)
zone.clanker.gradle.srcx.parse.PsiEnvironment
.closeShared()
logger.lifecycle("srcx: context written to $outDir/context.md")
}

Expand Down
Loading