-
Notifications
You must be signed in to change notification settings - Fork 229
/
Copy pathCompilerClassPath.kt
192 lines (161 loc) · 6.27 KB
/
CompilerClassPath.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
package org.javacs.kt
import org.javacs.kt.classpath.ClassPathEntry
import org.javacs.kt.classpath.defaultClassPathResolver
import org.javacs.kt.compiler.Compiler
import org.javacs.kt.database.DatabaseService
import org.javacs.kt.util.AsyncExecutor
import java.io.Closeable
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
/**
* Manages the class path (compiled JARs, etc), the Java source path
* and the compiler. Note that Kotlin sources are stored in SourcePath.
*/
class CompilerClassPath(
private val config: CompilerConfiguration,
private val scriptsConfig: ScriptsConfiguration,
private val codegenConfig: CodegenConfiguration,
private val databaseService: DatabaseService
) : Closeable {
val workspaceRoots = mutableSetOf<Path>()
private val javaSourcePath = mutableSetOf<Path>()
private val buildScriptClassPath = mutableSetOf<Path>()
val classPath = mutableSetOf<ClassPathEntry>()
val outputDirectory: File = Files.createTempDirectory("klsBuildOutput").toFile()
val javaHome: String? = System.getProperty("java.home", null)
var compiler = Compiler(
javaSourcePath,
classPath.map { it.compiledJar }.toSet(),
buildScriptClassPath,
scriptsConfig,
codegenConfig,
outputDirectory
)
private set
private val async = AsyncExecutor()
init {
compiler.updateConfiguration(config)
}
/** Updates and possibly reinstantiates the compiler using new paths. */
private fun refresh(
updateClassPath: Boolean = true,
updateBuildScriptClassPath: Boolean = true,
updateJavaSourcePath: Boolean = true
): Boolean {
// TODO: Fetch class path and build script class path concurrently (and asynchronously)
val resolver = defaultClassPathResolver(workspaceRoots, databaseService.db)
var refreshCompiler = updateJavaSourcePath
if (updateClassPath) {
val newClassPath = resolver.classpathOrEmpty
if (newClassPath != classPath) {
synchronized(classPath) {
syncPaths(classPath, newClassPath, "class path") { it.compiledJar }
}
refreshCompiler = true
}
async.compute {
val newClassPathWithSources = resolver.classpathWithSources
synchronized(classPath) {
syncPaths(classPath, newClassPathWithSources, "class path with sources") { it.compiledJar }
}
}
}
if (updateBuildScriptClassPath) {
LOG.info("Update build script path")
val newBuildScriptClassPath = resolver.buildScriptClasspathOrEmpty
if (newBuildScriptClassPath != buildScriptClassPath) {
syncPaths(buildScriptClassPath, newBuildScriptClassPath, "build script class path") { it }
refreshCompiler = true
}
}
if (refreshCompiler) {
LOG.info("Reinstantiating compiler")
compiler.close()
compiler = Compiler(
javaSourcePath,
classPath.map { it.compiledJar }.toSet(),
buildScriptClassPath,
scriptsConfig,
codegenConfig,
outputDirectory
)
updateCompilerConfiguration()
}
return refreshCompiler
}
/** Synchronizes the given two path sets and logs the differences. */
private fun <T> syncPaths(dest: MutableSet<T>, new: Set<T>, name: String, toPath: (T) -> Path) {
val added = new - dest
val removed = dest - new
logAdded(added.map(toPath), name)
logRemoved(removed.map(toPath), name)
dest.removeAll(removed)
dest.addAll(added)
}
fun updateCompilerConfiguration() {
compiler.updateConfiguration(config)
}
fun addWorkspaceRoot(root: Path): Boolean {
LOG.info("Searching for dependencies and Java sources in workspace root {}", root)
workspaceRoots.add(root)
javaSourcePath.addAll(findJavaSourceFiles(root))
return refresh()
}
fun removeWorkspaceRoot(root: Path): Boolean {
LOG.info("Removing dependencies and Java source path from workspace root {}", root)
workspaceRoots.remove(root)
javaSourcePath.removeAll(findJavaSourceFiles(root))
return refresh()
}
fun createdOnDisk(file: Path): Boolean {
if (isJavaSource(file)) {
javaSourcePath.add(file)
}
return changedOnDisk(file)
}
fun deletedOnDisk(file: Path): Boolean {
if (isJavaSource(file)) {
javaSourcePath.remove(file)
}
return changedOnDisk(file)
}
fun changedOnDisk(file: Path): Boolean {
val buildScript = isBuildScript(file)
val javaSource = isJavaSource(file)
if (buildScript || javaSource) {
return refresh(updateClassPath = buildScript, updateBuildScriptClassPath = false, updateJavaSourcePath = javaSource)
} else {
return false
}
}
private fun isJavaSource(file: Path): Boolean = file.fileName.toString().endsWith(".java")
private fun isBuildScript(file: Path): Boolean = file.fileName.toString().let { it == "pom.xml" || it == "build.gradle" || it == "build.gradle.kts" }
private fun findJavaSourceFiles(root: Path): Set<Path> {
val sourceMatcher = FileSystems.getDefault().getPathMatcher("glob:*.java")
return SourceExclusions(listOf(root), scriptsConfig)
.walkIncluded()
.filter { sourceMatcher.matches(it.fileName) }
.toSet()
}
override fun close() {
compiler.close()
async.shutdown(true)
outputDirectory.delete()
}
}
private fun logAdded(sources: Collection<Path>, name: String) {
when {
sources.isEmpty() -> return
sources.size > 5 -> LOG.info("Adding {} files to {}", sources.size, name)
else -> LOG.info("Adding {} to {}", sources, name)
}
}
private fun logRemoved(sources: Collection<Path>, name: String) {
when {
sources.isEmpty() -> return
sources.size > 5 -> LOG.info("Removing {} files from {}", sources.size, name)
else -> LOG.info("Removing {} from {}", sources, name)
}
}