Skip to content

Commit 88477fd

Browse files
committed
Make path to kotlin stdlibs configurable
1 parent 9d30266 commit 88477fd

File tree

2 files changed

+169
-130
lines changed

2 files changed

+169
-130
lines changed

src/test/kotlin/KotlinCompilation.kt

Lines changed: 166 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -21,88 +21,110 @@ import okio.buffer
2121
import okio.sink
2222
import kotlin.reflect.KClass
2323
import org.jetbrains.kotlin.cli.common.ExitCode
24-
import org.jetbrains.kotlin.cli.common.arguments.InternalArgument
2524
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
2625
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
2726
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
2827
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
2928
import org.jetbrains.kotlin.config.Services
3029
import java.io.*
31-
import java.net.URLClassLoader
32-
import java.net.URLDecoder
3330
import java.util.zip.ZipEntry
3431
import java.util.zip.ZipOutputStream
3532
import javax.lang.model.SourceVersion
3633

3734
@Suppress("unused", "MemberVisibilityCanBePrivate")
38-
class KotlinCompilation(
39-
/** Working directory for the compilation */
35+
open class KotlinCompilation(
36+
/** Working directory for the compilation */
4037
val workingDir: File,
41-
/** Arbitrary arguments to be passed to kotlinc */
42-
val args: List<String> = emptyList(),
43-
/** Arbitrary arguments to be passed to kapt */
38+
/** Free arguments to be passed to kotlinc */
39+
val freeArgs: List<String> = emptyList(),
40+
/** Arbitrary arguments to be passed to kapt */
4441
val kaptArgs: Map<String, String> = emptyMap(),
45-
/**
42+
/**
4643
* Paths to directories or .jar files that contain classes
4744
* to be made available in the compilation (i.e. added to
4845
* the classpath)
4946
*/
5047
classpaths: List<File> = emptyList(),
51-
/** Source files to be compiled */
48+
/** Source files to be compiled */
5249
val sources: List<SourceFile> = emptyList(),
53-
/** Services to be passed to kapt */
50+
/** Services to be passed to kapt */
5451
val services: List<Service<*, *>> = emptyList(),
55-
/**
52+
/**
5653
* Path to the JDK to be used
5754
*
5855
* null if no JDK is to be used (option -no-jdk)
5956
* */
6057
val jdkHome: File? = null,
61-
/** Search path for Kotlin runtime libraries */
62-
val kotlinHome: File? = null,
63-
/**
64-
* Don't look for kotlin-stdlib.jar, kotlin-script-runtime.jar
65-
* and kotlin-reflect.jar in the [kotlinHome] directory. They
66-
* need to be provided by [classpaths] or will be unavailable.
67-
* */
68-
val noStdLib: Boolean = kotlinHome == null,
69-
/**
70-
* Don't look for kotlin-reflect.jar in the [kotlinHome] directory.
71-
* It has to be provided by [classpaths] or will be unavailable.
72-
*
73-
* Setting it to false has no effect when [noStdLib] is true.
58+
/**
59+
* Path to the kotlin-stdlib.jar
60+
* If none is given, it will be searched for in the host
61+
* process' classpaths
62+
*/
63+
val kotlinStdLibJar: File? = findKtStdLib(
64+
log = { if(verbose) systemOut.log(it) }
65+
),
66+
/**
67+
* Path to the kotlin-stdlib-jdk*.jar
68+
* If none is given, it will be searched for in the host
69+
* process' classpaths
70+
*/
71+
val kotlinStdLibJdkJar: File? = findKtStdLibJdk(
72+
log = { if(verbose) systemOut.log(it) }
73+
),
74+
/**
75+
* Path to the kotlin-reflect.jar
76+
* If none is given, it will be searched for in the host
77+
* process' classpaths
78+
*/
79+
val kotlinReflectJar: File? = findKtReflect(
80+
log = { if(verbose) systemOut.log(it) }
81+
),
82+
/**
83+
* Path to the kotlin-script-runtime.jar
84+
* If none is given, it will be searched for in the host
85+
* process' classpaths
86+
*/
87+
val kotlinScriptRuntimeJar: File? = findKtScriptRt(
88+
log = { if(verbose) systemOut.log(it) }
89+
),
90+
/**
91+
* Path to the kotlin-stdlib-common.jar
92+
* If none is given, it will be searched for in the host
93+
* process' classpaths
7494
*/
75-
val noReflect: Boolean = noStdLib,
76-
/**
95+
val kotlinStdLibCommonJar: File? = findKtStdLibCommon(
96+
log = { if(verbose) systemOut.log(it) }
97+
),
98+
/**
7799
* Path to the tools.jar file needed for kapt when using a JDK 8.
78100
*
79101
* Note: Using a tools.jar file with a JDK 9 or later leads to an
80102
* internal compiler error!
81103
*/
82-
val toolsJar: File? = findToolsJarInHostClasspath(
104+
val toolsJar: File? = findToolsInHostClasspath(
83105
log = { if(verbose) systemOut.log(it) }
84106
),
85-
/**
107+
/**
86108
* Path to the kotlin-annotation-processing-embeddable*.jar that
87109
* contains kapt3.
88110
*
89111
* Only needed when [services] is not empty.
90112
*/
91-
val kapt3Jar: File? = findKapt3JarInHostClasspath(
113+
val kapt3Jar: File? = findKapt3(
92114
log = { if(verbose) systemOut.log(it) }
93115
),
94-
/** Inherit classpath from calling process */
116+
/** Inherit classpath from calling process */
95117
val inheritClassPath: Boolean = false,
96-
val jvmTarget: String? = null,
97-
val correctErrorTypes: Boolean = true,
98-
val skipRuntimeVersionCheck: Boolean = false,
99-
val verbose: Boolean = false,
100-
val suppressWarnings: Boolean = false,
101-
val allWarningsAsErrors: Boolean = false,
102-
val reportOutputFiles: Boolean = false,
103-
val reportPerformance: Boolean = false,
104-
val loadBuiltInsFromDependencies: Boolean = false,
105-
/**
118+
val jvmTarget: String? = null,
119+
val correctErrorTypes: Boolean = true,
120+
val skipRuntimeVersionCheck: Boolean = false,
121+
val verbose: Boolean = false,
122+
val suppressWarnings: Boolean = false,
123+
val allWarningsAsErrors: Boolean = false,
124+
val reportOutputFiles: Boolean = false,
125+
val reportPerformance: Boolean = false,
126+
val loadBuiltInsFromDependencies: Boolean = false,
127+
/**
106128
* Helpful information (if [verbose] = true) and the compiler
107129
* system output will be written to this stream
108130
*/
@@ -159,14 +181,17 @@ class KotlinCompilation(
159181
val allClasspaths: List<String> = mutableListOf<String>().apply {
160182
addAll(classpaths.map(File::getAbsolutePath))
161183

184+
addAll(listOfNotNull(kotlinStdLibJar, kotlinReflectJar, kotlinScriptRuntimeJar)
185+
.map(File::getAbsolutePath))
186+
162187
if(inheritClassPath) {
163-
val hostClasspaths = getClasspaths().map(File::getAbsolutePath)
188+
val hostClasspaths = getHostClasspaths().map(File::getAbsolutePath)
164189
addAll(hostClasspaths)
165190

166191
if(verbose)
167192
systemOut.log("Inheriting classpaths: " + hostClasspaths.joinToString(File.pathSeparator))
168193
}
169-
}
194+
}.distinct()
170195

171196
/** Returns arguments necessary to enable and configure kapt3. */
172197
private fun annotationProcessorArgs() = object {
@@ -195,66 +220,63 @@ class KotlinCompilation(
195220
)
196221
}
197222

198-
/** Runs the compilation task */
199-
fun run(): Result {
200-
// write given sources to working directory
201-
sources.forEach { it.writeTo(sourcesDir) }
202-
203-
// setup arguments for the compiler call
204-
val k2jvmArgs = K2JVMCompilerArguments().also { k2JvmArgs ->
205-
k2JvmArgs.freeArgs = sourcesDir.listFiles().map { it.absolutePath } + args
206-
207-
// only add kapt stuff if there are services that may use it
208-
if(services.isNotEmpty()) {
209-
val annotationProcArgs = annotationProcessorArgs()
210-
211-
k2JvmArgs.pluginOptions = if(k2JvmArgs.pluginOptions != null)
212-
k2JvmArgs.pluginOptions!!.plus(annotationProcArgs.pluginOptions)
213-
else
214-
annotationProcArgs.pluginOptions
223+
// setup arguments for the compiler call
224+
protected open fun parseK2JVMArgs() = K2JVMCompilerArguments().also { k2JvmArgs ->
225+
k2JvmArgs.freeArgs = sourcesDir.listFiles().map { it.absolutePath } + freeArgs
215226

216-
k2JvmArgs.pluginClasspaths = if(k2JvmArgs.pluginClasspaths != null)
217-
k2JvmArgs.pluginClasspaths!!.plus(annotationProcArgs.pluginClassPaths)
218-
else
219-
annotationProcArgs.pluginClassPaths
220-
}
221-
else if(verbose) {
222-
systemOut.log("No services were given. Not including kapt in the compiler's plugins.")
223-
}
224-
225-
k2JvmArgs.destination = classesDir.absolutePath
226-
k2JvmArgs.classpath = allClasspaths.joinToString(separator = File.pathSeparator)
227-
228-
if(jdkHome != null) {
229-
k2JvmArgs.jdkHome = jdkHome.absolutePath
230-
}
231-
else {
232-
if(verbose)
233-
systemOut.log("Using option -no-jdk. Kotlinc won't look for a JDK.")
227+
// only add kapt stuff if there are services that may use it
228+
if(services.isNotEmpty()) {
229+
val annotationProcArgs = annotationProcessorArgs()
234230

235-
k2JvmArgs.noJdk = true
236-
}
231+
k2JvmArgs.pluginOptions = if(k2JvmArgs.pluginOptions != null)
232+
k2JvmArgs.pluginOptions!!.plus(annotationProcArgs.pluginOptions)
233+
else
234+
annotationProcArgs.pluginOptions
237235

238-
k2JvmArgs.noStdlib = noStdLib
239-
k2JvmArgs.noReflect = noReflect
236+
k2JvmArgs.pluginClasspaths = if(k2JvmArgs.pluginClasspaths != null)
237+
k2JvmArgs.pluginClasspaths!!.plus(annotationProcArgs.pluginClassPaths)
238+
else
239+
annotationProcArgs.pluginClassPaths
240+
}
241+
else if(verbose) {
242+
systemOut.log("No services were given. Not including kapt in the compiler's plugins.")
243+
}
240244

241-
kotlinHome?.let { k2JvmArgs.kotlinHome = it.absolutePath }
245+
k2JvmArgs.destination = classesDir.absolutePath
246+
k2JvmArgs.classpath = allClasspaths.joinToString(separator = File.pathSeparator)
242247

243-
jvmTarget?.let { k2JvmArgs.jvmTarget = it }
248+
if(jdkHome != null) {
249+
k2JvmArgs.jdkHome = jdkHome.absolutePath
250+
}
251+
else {
252+
if(verbose)
253+
systemOut.log("Using option -no-jdk. Kotlinc won't look for a JDK.")
244254

245-
k2JvmArgs.verbose = verbose
246-
k2JvmArgs.skipRuntimeVersionCheck = skipRuntimeVersionCheck
247-
k2JvmArgs.suppressWarnings = suppressWarnings
248-
k2JvmArgs.allWarningsAsErrors = allWarningsAsErrors
249-
k2JvmArgs.reportOutputFiles = reportOutputFiles
250-
k2JvmArgs.reportPerf = reportPerformance
251-
k2JvmArgs.reportOutputFiles = reportOutputFiles
252-
k2JvmArgs.loadBuiltInsFromDependencies = loadBuiltInsFromDependencies
255+
k2JvmArgs.noJdk = true
256+
}
253257

258+
// the compiler should never look for stdlib or reflect in the
259+
// kotlinHome directory (which is null anyway). We will put them
260+
// in the classpath manually if they're needed
261+
k2JvmArgs.noStdlib = true
262+
k2JvmArgs.noReflect = true
263+
264+
jvmTarget?.let { k2JvmArgs.jvmTarget = it }
265+
266+
k2JvmArgs.verbose = verbose
267+
k2JvmArgs.skipRuntimeVersionCheck = skipRuntimeVersionCheck
268+
k2JvmArgs.suppressWarnings = suppressWarnings
269+
k2JvmArgs.allWarningsAsErrors = allWarningsAsErrors
270+
k2JvmArgs.reportOutputFiles = reportOutputFiles
271+
k2JvmArgs.reportPerf = reportPerformance
272+
k2JvmArgs.reportOutputFiles = reportOutputFiles
273+
k2JvmArgs.loadBuiltInsFromDependencies = loadBuiltInsFromDependencies
274+
}
254275

255-
k2JvmArgs.additionalJavaModules=arrayOf()
256-
k2JvmArgs.javaModulePath=""
257-
}
276+
/** Runs the compilation task */
277+
fun run(): Result {
278+
// write given sources to working directory
279+
sources.forEach { it.writeTo(sourcesDir) }
258280

259281
val compilerSystemOutBuffer = Buffer() // Buffer for capturing compiler's logging output
260282

@@ -264,7 +286,7 @@ class KotlinCompilation(
264286
TeeOutputStream(systemOut, compilerSystemOutBuffer.outputStream())),
265287
MessageRenderer.WITHOUT_PATHS, true),
266288
Services.EMPTY,
267-
k2jvmArgs
289+
parseK2JVMArgs()
268290
)
269291

270292
// check for known errors that are hard to debug for the user
@@ -285,7 +307,7 @@ class KotlinCompilation(
285307

286308

287309
/**
288-
* Base64 encodes a mapping of annotation processor args for kapt, as specified by
310+
* Base64 encodes a mapping of annotation processor freeArgs for kapt, as specified by
289311
* https://kotlinlang.org/docs/reference/kapt.html#apjavac-options-encoding
290312
*/
291313
private fun encodeOptionsForKapt(options: Map<String, String>): String {
@@ -301,38 +323,58 @@ class KotlinCompilation(
301323
}
302324

303325
companion object {
304-
/** Tries to find the kapt 3 jar in the host classpath */
305-
fun findKapt3JarInHostClasspath(log: ((String) -> Unit)? = null): File? {
306-
val jarFile = getClasspaths().firstOrNull { classpath ->
307-
classpath.name.matches(Regex("kotlin-annotation-processing-embeddable.*?\\.jar"))
308-
//TODO("check that jar file actually contains the right classes")
309-
}
310-
311-
if(jarFile == null && log != null)
312-
log("Searched classpath for kotlin-annotation-processing-embeddable*.jar but didn't find anything.")
313-
else if(log != null)
314-
log("Searched classpath for kapt3 jar and found: $jarFile")
315-
316-
return jarFile
317-
}
326+
/** Tries to find a file matching the given [regex] in the host process' classpath */
327+
fun findInHostClasspath(shortName: String, regex: Regex, log: ((String) -> Unit)? = null): File? {
328+
val jarFile = getHostClasspaths().firstOrNull { classpath ->
329+
classpath.name.matches(regex)
330+
//TODO("check that jar file actually contains the right classes")
331+
}
318332

319-
/** Tries to find the tools.jar needed for kapt in the host classpath */
320-
fun findToolsJarInHostClasspath(log: ((String) -> Unit)? = null): File? {
321-
val jarFile = getClasspaths().firstOrNull { classpath ->
322-
classpath.name.matches(Regex("tools.jar"))
323-
//TODO("check that jar file actually contains the right classes")
324-
}
333+
if(jarFile == null && log != null)
334+
log("Searched classpath for $shortName but didn't find anything.")
335+
else if(log != null)
336+
log("Searched classpath for $shortName and found: $jarFile")
325337

326-
if(jarFile == null && log != null)
327-
log("Searched classpath for tools.jar but didn't find anything.")
328-
else if(log != null)
329-
log("Searched classpath for tools.jar and found: $jarFile")
338+
return jarFile
339+
}
330340

331-
return jarFile
332-
}
341+
/** Tries to find the kotlin-stdlib.jar in the host process' classpath */
342+
fun findKtStdLib(log: ((String) -> Unit)? = null)
343+
= findInHostClasspath("kotlin-stdlib.jar",
344+
Regex("kotlin-stdlib(-[0-9]+\\.[0-9]+\\.[0-9]+)\\.jar"), log)
345+
346+
/** Tries to find the kotlin-stdlib-jdk*.jar in the host process' classpath */
347+
fun findKtStdLibJdk(log: ((String) -> Unit)? = null)
348+
= findInHostClasspath("kotlin-stdlib-jdk*.jar",
349+
Regex("kotlin-stdlib-jdk[0-9]+(-[0-9]+\\.[0-9]+\\.[0-9]+)\\.jar"), log)
350+
351+
/** Tries to find the kotlin-stdlib-common.jar in the host process' classpath */
352+
fun findKtStdLibCommon(log: ((String) -> Unit)? = null)
353+
= findInHostClasspath("kotlin-stdlib-common.jar",
354+
Regex("kotlin-stdlib-common(-[0-9]+\\.[0-9]+\\.[0-9]+)\\.jar"), log)
355+
356+
/** Tries to find the kotlin-reflect.jar in the host process' classpath */
357+
fun findKtReflect(log: ((String) -> Unit)? = null)
358+
= findInHostClasspath("kotlin-reflect.jar",
359+
Regex("kotlin-reflect(-[0-9]+\\.[0-9]+\\.[0-9]+)\\.jar"), log)
360+
361+
/** Tries to find the kotlin-script-runtime.jar in the host process' classpath */
362+
fun findKtScriptRt(log: ((String) -> Unit)? = null)
363+
= findInHostClasspath("kotlin-script-runtime.jar",
364+
Regex("kotlin-script-runtime(-[0-9]+\\.[0-9]+\\.[0-9]+)\\.jar"), log)
365+
366+
/** Tries to find the kapt 3 jar in the host process' classpath */
367+
fun findKapt3(log: ((String) -> Unit)? = null)
368+
= findInHostClasspath("kotlin-annotation-processing(-embeddable).jar",
369+
Regex("kotlin-annotation-processing(-embeddable)?(-[0-9]+\\.[0-9]+\\.[0-9]+)?\\.jar"), log)
370+
371+
372+
/** Tries to find the tools.jar needed for kapt in the host process' classpath */
373+
fun findToolsInHostClasspath(log: ((String) -> Unit)? = null)
374+
= findInHostClasspath("tools.jar", Regex("tools.jar"), log)
333375

334376
/** Returns the files on the classloader's classpath and modulepath */
335-
fun getClasspaths(): List<File> {
377+
fun getHostClasspaths(): List<File> {
336378
val classGraph = ClassGraph()
337379
.enableSystemJarsAndModules()
338380
.removeTemporaryFilesAfterScan()

0 commit comments

Comments
 (0)