diff --git a/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt b/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt index 8ec87e5b0..0b7265f4c 100644 --- a/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt +++ b/server/src/main/kotlin/org/javacs/kt/CompilerClassPath.kt @@ -23,6 +23,7 @@ class CompilerClassPath(private val config: CompilerConfiguration, private val d val classPath = mutableSetOf() val outputDirectory: File = Files.createTempDirectory("klsBuildOutput").toFile() val javaHome: String? = System.getProperty("java.home", null) + var classpathCached = false var compiler = Compiler(javaSourcePath, classPath.map { it.compiledJar }.toSet(), buildScriptClassPath, outputDirectory) private set @@ -45,17 +46,18 @@ class CompilerClassPath(private val config: CompilerConfiguration, private val d if (updateClassPath) { val newClassPath = resolver.classpathOrEmpty - if (newClassPath != classPath) { + if (newClassPath.entries != classPath) { synchronized(classPath) { - syncPaths(classPath, newClassPath, "class path") { it.compiledJar } + syncPaths(classPath, newClassPath.entries, "class path") { it.compiledJar } } + classpathCached = newClassPath.cacheHit refreshCompiler = true } async.compute { val newClassPathWithSources = resolver.classpathWithSources synchronized(classPath) { - syncPaths(classPath, newClassPathWithSources, "class path with sources") { it.compiledJar } + syncPaths(classPath, newClassPathWithSources.entries, "class path with sources") { it.compiledJar } } } } diff --git a/server/src/main/kotlin/org/javacs/kt/SourcePath.kt b/server/src/main/kotlin/org/javacs/kt/SourcePath.kt index dff7e0d7e..ac473141d 100644 --- a/server/src/main/kotlin/org/javacs/kt/SourcePath.kt +++ b/server/src/main/kotlin/org/javacs/kt/SourcePath.kt @@ -290,8 +290,6 @@ class SourcePath( } fun refreshDependencyIndexes() { - compileAllFiles() - val module = files.values.first { it.module != null }.module if (module != null) { refreshDependencyIndexes(module) @@ -315,7 +313,7 @@ class SourcePath( * Refreshes the indexes. If already done, refreshes only the declarations in the files that were changed. */ private fun refreshDependencyIndexes(module: ModuleDescriptor) = indexAsync.execute { - if (indexEnabled) { + if (indexEnabled && !cp.classpathCached) { val declarations = getDeclarationDescriptors(files.values) index.refresh(module, declarations) } @@ -344,6 +342,7 @@ class SourcePath( LOG.info("Refreshing source path") files.values.forEach { it.clean() } files.values.forEach { it.compile() } + refreshDependencyIndexes() } } diff --git a/server/src/main/kotlin/org/javacs/kt/index/SymbolIndex.kt b/server/src/main/kotlin/org/javacs/kt/index/SymbolIndex.kt index 5fbb9b66b..99cccc4ed 100644 --- a/server/src/main/kotlin/org/javacs/kt/index/SymbolIndex.kt +++ b/server/src/main/kotlin/org/javacs/kt/index/SymbolIndex.kt @@ -85,17 +85,15 @@ class SymbolIndex( private val databaseService: DatabaseService ) { private val db: Database by lazy { - databaseService.db ?: Database.connect("jdbc:h2:mem:symbolindex;DB_CLOSE_DELAY=-1", "org.h2.Driver") - } - - var progressFactory: Progress.Factory = Progress.Factory.None - - init { + val db = databaseService.db ?: Database.connect("jdbc:h2:mem:symbolindex;DB_CLOSE_DELAY=-1", "org.h2.Driver") transaction(db) { SchemaUtils.createMissingTablesAndColumns(Symbols, Locations, Ranges, Positions) } + db } + var progressFactory: Progress.Factory = Progress.Factory.None + /** Rebuilds the entire index. May take a while. */ fun refresh(module: ModuleDescriptor, exclusions: Sequence) { val started = System.currentTimeMillis() diff --git a/server/src/test/kotlin/org/javacs/kt/ClassPathTest.kt b/server/src/test/kotlin/org/javacs/kt/ClassPathTest.kt index 386db010a..b53dd57c8 100644 --- a/server/src/test/kotlin/org/javacs/kt/ClassPathTest.kt +++ b/server/src/test/kotlin/org/javacs/kt/ClassPathTest.kt @@ -23,7 +23,7 @@ class ClassPathTest { val resolvers = defaultClassPathResolver(listOf(workspaceRoot)) print(resolvers) - val classPath = resolvers.classpathOrEmpty.map { it.toString() } + val classPath = resolvers.classpathOrEmpty.entries.map { it.toString() } assertThat(classPath, hasItem(containsString("junit"))) } @@ -36,7 +36,7 @@ class ClassPathTest { val resolvers = defaultClassPathResolver(listOf(workspaceRoot)) print(resolvers) - val classPath = resolvers.classpathOrEmpty.map { it.toString() } + val classPath = resolvers.classpathOrEmpty.entries.map { it.toString() } assertThat(classPath, hasItem(containsString("junit"))) } diff --git a/shared/src/main/kotlin/org/javacs/kt/classpath/BackupClassPathResolver.kt b/shared/src/main/kotlin/org/javacs/kt/classpath/BackupClassPathResolver.kt index c8b6d6b45..517a99bcf 100644 --- a/shared/src/main/kotlin/org/javacs/kt/classpath/BackupClassPathResolver.kt +++ b/shared/src/main/kotlin/org/javacs/kt/classpath/BackupClassPathResolver.kt @@ -13,7 +13,7 @@ import java.nio.file.Paths /** Backup classpath that find Kotlin in the user's Maven/Gradle home or kotlinc's libraries folder. */ object BackupClassPathResolver : ClassPathResolver { override val resolverType: String = "Backup" - override val classpath: Set get() = findKotlinStdlib()?.let { setOf(it) }.orEmpty().map { ClassPathEntry(it, null) }.toSet() + override val classpath: ClassPathResult get() = ClassPathResult(findKotlinStdlib()?.let { setOf(it) }.orEmpty().map { ClassPathEntry(it, null) }.toSet()) } fun findKotlinStdlib(): Path? = diff --git a/shared/src/main/kotlin/org/javacs/kt/classpath/CachedClassPathResolver.kt b/shared/src/main/kotlin/org/javacs/kt/classpath/CachedClassPathResolver.kt index 8eae7dd4b..0e85c1f3b 100644 --- a/shared/src/main/kotlin/org/javacs/kt/classpath/CachedClassPathResolver.kt +++ b/shared/src/main/kotlin/org/javacs/kt/classpath/CachedClassPathResolver.kt @@ -107,16 +107,16 @@ internal class CachedClassPathResolver( } } - override val classpath: Set get() { + override val classpath: ClassPathResult get() { cachedClassPathEntries.let { if (!dependenciesChanged()) { LOG.info("Classpath has not changed. Fetching from cache") - return it + return ClassPathResult(it, cacheHit = true) } } LOG.info("Cached classpath is outdated or not found. Resolving again") val newClasspath = wrapped.classpath - updateClasspathCache(newClasspath, false) + updateClasspathCache(newClasspath.entries, false) return newClasspath } @@ -135,11 +135,13 @@ internal class CachedClassPathResolver( return newBuildScriptClasspath } - override val classpathWithSources: Set get() { - cachedClassPathMetadata?.let { if (!dependenciesChanged() && it.includesSources) return cachedClassPathEntries } + override val classpathWithSources: ClassPathResult get() { + cachedClassPathMetadata?.let { if (!dependenciesChanged() && it.includesSources) { + return ClassPathResult(cachedClassPathEntries, cacheHit = true) + } } val newClasspath = wrapped.classpathWithSources - updateClasspathCache(newClasspath, true) + updateClasspathCache(newClasspath.entries, true) return newClasspath } diff --git a/shared/src/main/kotlin/org/javacs/kt/classpath/ClassPathResolver.kt b/shared/src/main/kotlin/org/javacs/kt/classpath/ClassPathResolver.kt index d7641f102..84f650919 100644 --- a/shared/src/main/kotlin/org/javacs/kt/classpath/ClassPathResolver.kt +++ b/shared/src/main/kotlin/org/javacs/kt/classpath/ClassPathResolver.kt @@ -8,13 +8,13 @@ import kotlin.math.max interface ClassPathResolver { val resolverType: String - val classpath: Set // may throw exceptions - val classpathOrEmpty: Set // does not throw exceptions + val classpath: ClassPathResult // may throw exceptions + val classpathOrEmpty: ClassPathResult // does not throw exceptions get() = try { classpath } catch (e: Exception) { LOG.warn("Could not resolve classpath using {}: {}", resolverType, e.message) - emptySet() + ClassPathResult(emptySet()) } val buildScriptClasspath: Set @@ -27,7 +27,7 @@ interface ClassPathResolver { emptySet() } - val classpathWithSources: Set get() = classpath + val classpathWithSources: ClassPathResult get() = classpath /** * This should return the current build file version. @@ -43,7 +43,7 @@ interface ClassPathResolver { /** A default empty classpath implementation */ val empty = object : ClassPathResolver { override val resolverType = "[]" - override val classpath = emptySet() + override val classpath = ClassPathResult(emptySet()) } } } @@ -61,22 +61,22 @@ infix fun ClassPathResolver.or(other: ClassPathResolver): ClassPathResolver = Fi /** The union of two class path resolvers. */ internal class UnionClassPathResolver(val lhs: ClassPathResolver, val rhs: ClassPathResolver) : ClassPathResolver { override val resolverType: String get() = "(${lhs.resolverType} + ${rhs.resolverType})" - override val classpath get() = lhs.classpath + rhs.classpath - override val classpathOrEmpty get() = lhs.classpathOrEmpty + rhs.classpathOrEmpty + override val classpath get() = ClassPathResult(lhs.classpath.entries + rhs.classpath.entries) + override val classpathOrEmpty get() = ClassPathResult(lhs.classpathOrEmpty.entries + rhs.classpathOrEmpty.entries) override val buildScriptClasspath get() = lhs.buildScriptClasspath + rhs.buildScriptClasspath override val buildScriptClasspathOrEmpty get() = lhs.buildScriptClasspathOrEmpty + rhs.buildScriptClasspathOrEmpty - override val classpathWithSources get() = lhs.classpathWithSources + rhs.classpathWithSources + override val classpathWithSources get() = ClassPathResult(lhs.classpathWithSources.entries + rhs.classpathWithSources.entries) override val currentBuildFileVersion: Long get() = max(lhs.currentBuildFileVersion, rhs.currentBuildFileVersion) } internal class FirstNonEmptyClassPathResolver(val lhs: ClassPathResolver, val rhs: ClassPathResolver) : ClassPathResolver { override val resolverType: String get() = "(${lhs.resolverType} or ${rhs.resolverType})" - override val classpath get() = lhs.classpath.takeIf { it.isNotEmpty() } ?: rhs.classpath - override val classpathOrEmpty get() = lhs.classpathOrEmpty.takeIf { it.isNotEmpty() } ?: rhs.classpathOrEmpty + override val classpath get() = ClassPathResult(lhs.classpath.entries.takeIf { it.isNotEmpty() } ?: rhs.classpath.entries) + override val classpathOrEmpty get() = ClassPathResult(lhs.classpathOrEmpty.entries.takeIf { it.isNotEmpty() } ?: rhs.classpathOrEmpty.entries) override val buildScriptClasspath get() = lhs.buildScriptClasspath.takeIf { it.isNotEmpty() } ?: rhs.buildScriptClasspath override val buildScriptClasspathOrEmpty get() = lhs.buildScriptClasspathOrEmpty.takeIf { it.isNotEmpty() } ?: rhs.buildScriptClasspathOrEmpty - override val classpathWithSources get() = lhs.classpathWithSources.takeIf { + override val classpathWithSources get() = ClassPathResult(lhs.classpathWithSources.entries.takeIf { it.isNotEmpty() - } ?: rhs.classpathWithSources + } ?: rhs.classpathWithSources.entries) override val currentBuildFileVersion: Long get() = max(lhs.currentBuildFileVersion, rhs.currentBuildFileVersion) } diff --git a/shared/src/main/kotlin/org/javacs/kt/classpath/ClassPathResult.kt b/shared/src/main/kotlin/org/javacs/kt/classpath/ClassPathResult.kt new file mode 100644 index 000000000..082e8eb71 --- /dev/null +++ b/shared/src/main/kotlin/org/javacs/kt/classpath/ClassPathResult.kt @@ -0,0 +1,6 @@ +package org.javacs.kt.classpath + +data class ClassPathResult( + val entries: Set, + val cacheHit: Boolean = false +) diff --git a/shared/src/main/kotlin/org/javacs/kt/classpath/GradleClassPathResolver.kt b/shared/src/main/kotlin/org/javacs/kt/classpath/GradleClassPathResolver.kt index 1e0cbc7b3..e9f6586b5 100644 --- a/shared/src/main/kotlin/org/javacs/kt/classpath/GradleClassPathResolver.kt +++ b/shared/src/main/kotlin/org/javacs/kt/classpath/GradleClassPathResolver.kt @@ -14,13 +14,13 @@ internal class GradleClassPathResolver(private val path: Path, private val inclu override val resolverType: String = "Gradle" private val projectDirectory: Path get() = path.parent - override val classpath: Set get() { + override val classpath: ClassPathResult get() { val scripts = listOf("projectClassPathFinder.gradle") val tasks = listOf("kotlinLSPProjectDeps") - return readDependenciesViaGradleCLI(projectDirectory, scripts, tasks) + return ClassPathResult(readDependenciesViaGradleCLI(projectDirectory, scripts, tasks) .apply { if (isNotEmpty()) LOG.info("Successfully resolved dependencies for '${projectDirectory.fileName}' using Gradle") } - .map { ClassPathEntry(it, null) }.toSet() + .map { ClassPathEntry(it, null) }.toSet()) } override val buildScriptClasspath: Set get() { return if (includeKotlinDSL) { diff --git a/shared/src/main/kotlin/org/javacs/kt/classpath/MavenClassPathResolver.kt b/shared/src/main/kotlin/org/javacs/kt/classpath/MavenClassPathResolver.kt index a984252ab..315c2547a 100644 --- a/shared/src/main/kotlin/org/javacs/kt/classpath/MavenClassPathResolver.kt +++ b/shared/src/main/kotlin/org/javacs/kt/classpath/MavenClassPathResolver.kt @@ -14,7 +14,7 @@ internal class MavenClassPathResolver private constructor(private val pom: Path) override val resolverType: String = "Maven" - override val classpath: Set get() { + override val classpath: ClassPathResult get() { val dependenciesOutput = generateMavenDependencyList(pom) val artifacts = readMavenDependencyList(dependenciesOutput) @@ -27,10 +27,10 @@ internal class MavenClassPathResolver private constructor(private val pom: Path) Files.deleteIfExists(dependenciesOutput) this.artifacts = artifacts - return artifacts.mapNotNull { findMavenArtifact(it, false)?.let { it1 -> ClassPathEntry(it1, null) } }.toSet() + return ClassPathResult(artifacts.mapNotNull { findMavenArtifact(it, false)?.let { it1 -> ClassPathEntry(it1, null) } }.toSet()) } - override val classpathWithSources: Set get() { + override val classpathWithSources: ClassPathResult get() { // Fetch artifacts if not yet present. var artifacts: Set if (this.artifacts != null) { @@ -47,11 +47,11 @@ internal class MavenClassPathResolver private constructor(private val pom: Path) artifacts = readMavenDependencyListWithSources(artifacts, sourcesOutput) Files.deleteIfExists(sourcesOutput) - return artifacts.mapNotNull { + return ClassPathResult(artifacts.mapNotNull { findMavenArtifact(it, false)?.let { it1 -> ClassPathEntry(it1, if (it.source) findMavenArtifact(it, it.source) else null) } - }.toSet() + }.toSet()) } override val currentBuildFileVersion: Long get() = pom.toFile().lastModified() diff --git a/shared/src/main/kotlin/org/javacs/kt/classpath/ShellClassPathResolver.kt b/shared/src/main/kotlin/org/javacs/kt/classpath/ShellClassPathResolver.kt index 65e43be42..b413cb78a 100644 --- a/shared/src/main/kotlin/org/javacs/kt/classpath/ShellClassPathResolver.kt +++ b/shared/src/main/kotlin/org/javacs/kt/classpath/ShellClassPathResolver.kt @@ -14,19 +14,19 @@ internal class ShellClassPathResolver( private val workingDir: Path? = null ) : ClassPathResolver { override val resolverType: String = "Shell" - override val classpath: Set get() { + override val classpath: ClassPathResult get() { val workingDirectory = workingDir?.toFile() ?: script.toAbsolutePath().parent.toFile() val cmd = script.toString() LOG.info("Run {} in {}", cmd, workingDirectory) val process = ProcessBuilder(cmd).directory(workingDirectory).start() - return process.inputStream.bufferedReader().readText() + return ClassPathResult(process.inputStream.bufferedReader().readText() .split(File.pathSeparator) .asSequence() .map { it.trim() } .filter { it.isNotEmpty() } .map { ClassPathEntry(Paths.get(it), null) } - .toSet() + .toSet()) } companion object { diff --git a/shared/src/main/kotlin/org/javacs/kt/classpath/WithStdlibResolver.kt b/shared/src/main/kotlin/org/javacs/kt/classpath/WithStdlibResolver.kt index bbbcccefa..927600df4 100644 --- a/shared/src/main/kotlin/org/javacs/kt/classpath/WithStdlibResolver.kt +++ b/shared/src/main/kotlin/org/javacs/kt/classpath/WithStdlibResolver.kt @@ -5,16 +5,16 @@ import java.nio.file.Path /** A classpath resolver that ensures another resolver contains the stdlib */ internal class WithStdlibResolver(private val wrapped: ClassPathResolver) : ClassPathResolver { override val resolverType: String get() = "Stdlib + ${wrapped.resolverType}" - override val classpath: Set get() = wrapWithStdlibEntries(wrapped.classpath) - override val classpathOrEmpty: Set get() = wrapWithStdlibEntries(wrapped.classpathOrEmpty) + override val classpath: ClassPathResult get() = wrapWithStdlibEntries(wrapped.classpath.entries) + override val classpathOrEmpty: ClassPathResult get() = wrapWithStdlibEntries(wrapped.classpathOrEmpty.entries) override val buildScriptClasspath: Set get() = wrapWithStdlib(wrapped.buildScriptClasspath) override val buildScriptClasspathOrEmpty: Set get() = wrapWithStdlib(wrapped.buildScriptClasspathOrEmpty) - override val classpathWithSources: Set get() = wrapWithStdlibEntries(wrapped.classpathWithSources) + override val classpathWithSources: ClassPathResult get() = wrapWithStdlibEntries(wrapped.classpathWithSources.entries) override val currentBuildFileVersion: Long get() = wrapped.currentBuildFileVersion } -private fun wrapWithStdlibEntries(paths: Set): Set { - return wrapWithStdlib(paths.map { it.compiledJar }.toSet()).map { ClassPathEntry(it, paths.find { it1 -> it1.compiledJar == it }?.sourceJar) }.toSet() +private fun wrapWithStdlibEntries(paths: Set): ClassPathResult { + return ClassPathResult(wrapWithStdlib(paths.map { it.compiledJar }.toSet()).map { ClassPathEntry(it, paths.find { it1 -> it1.compiledJar == it }?.sourceJar) }.toSet()) } private fun wrapWithStdlib(paths: Set): Set {