Skip to content

Commit 9d30266

Browse files
committed
Use ClassGraph for finding classpaths. That's probably a lot more portable
1 parent 4c62ea0 commit 9d30266

File tree

3 files changed

+72
-74
lines changed

3 files changed

+72
-74
lines changed

build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ dependencies {
5050

5151
compile "me.eugeniomarletti.kotlin.metadata:kotlin-metadata:1.4.0"
5252

53-
testCompile "com.squareup.okio:okio:2.1.0"
53+
testImplementation "com.squareup.okio:okio:2.1.0"
54+
testImplementation 'io.github.classgraph:classgraph:4.6.10'
5455

5556
// the Kotlin compiler should be near the end of the list because its .jar file includes
5657
// an obsolete version of Guava

src/test/kotlin/KotlinCompilation.kt

Lines changed: 58 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
*/
1616
package com.tschuchort.compiletest
1717

18+
import io.github.classgraph.ClassGraph
1819
import okio.Buffer
1920
import okio.buffer
2021
import okio.sink
2122
import kotlin.reflect.KClass
2223
import org.jetbrains.kotlin.cli.common.ExitCode
24+
import org.jetbrains.kotlin.cli.common.arguments.InternalArgument
2325
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
2426
import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
2527
import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
@@ -71,19 +73,13 @@ class KotlinCompilation(
7173
* Setting it to false has no effect when [noStdLib] is true.
7274
*/
7375
val noReflect: Boolean = noStdLib,
74-
/**
75-
* The classloader that should be used for the discovery of
76-
* the host process' classpath
77-
*/
78-
val hostClassLoader: ClassLoader = this::class.java.classLoader,
7976
/**
8077
* Path to the tools.jar file needed for kapt when using a JDK 8.
8178
*
8279
* Note: Using a tools.jar file with a JDK 9 or later leads to an
8380
* internal compiler error!
8481
*/
8582
val toolsJar: File? = findToolsJarInHostClasspath(
86-
hostClassLoader = hostClassLoader,
8783
log = { if(verbose) systemOut.log(it) }
8884
),
8985
/**
@@ -93,7 +89,6 @@ class KotlinCompilation(
9389
* Only needed when [services] is not empty.
9490
*/
9591
val kapt3Jar: File? = findKapt3JarInHostClasspath(
96-
hostClassLoader = hostClassLoader,
9792
log = { if(verbose) systemOut.log(it) }
9893
),
9994
/** Inherit classpath from calling process */
@@ -165,8 +160,11 @@ class KotlinCompilation(
165160
addAll(classpaths.map(File::getAbsolutePath))
166161

167162
if(inheritClassPath) {
168-
val hostClasspaths = getClasspaths(hostClassLoader).map(File::getAbsolutePath)
163+
val hostClasspaths = getClasspaths().map(File::getAbsolutePath)
169164
addAll(hostClasspaths)
165+
166+
if(verbose)
167+
systemOut.log("Inheriting classpaths: " + hostClasspaths.joinToString(File.pathSeparator))
170168
}
171169
}
172170

@@ -199,59 +197,66 @@ class KotlinCompilation(
199197

200198
/** Runs the compilation task */
201199
fun run(): Result {
202-
sources.forEach {
203-
it.writeTo(sourcesDir)
204-
}
200+
// write given sources to working directory
201+
sources.forEach { it.writeTo(sourcesDir) }
205202

206-
val k2jvmArgs = K2JVMCompilerArguments().apply {
207-
freeArgs = sourcesDir.listFiles().map { it.absolutePath } + args
203+
// setup arguments for the compiler call
204+
val k2jvmArgs = K2JVMCompilerArguments().also { k2JvmArgs ->
205+
k2JvmArgs.freeArgs = sourcesDir.listFiles().map { it.absolutePath } + args
208206

209207
// only add kapt stuff if there are services that may use it
210208
if(services.isNotEmpty()) {
211209
val annotationProcArgs = annotationProcessorArgs()
212210

213-
pluginOptions = if(pluginOptions != null)
214-
pluginOptions!!.plus(annotationProcArgs.pluginOptions)
211+
k2JvmArgs.pluginOptions = if(k2JvmArgs.pluginOptions != null)
212+
k2JvmArgs.pluginOptions!!.plus(annotationProcArgs.pluginOptions)
215213
else
216214
annotationProcArgs.pluginOptions
217215

218-
pluginClasspaths = if(pluginClasspaths != null)
219-
pluginClasspaths!!.plus(annotationProcArgs.pluginClassPaths)
216+
k2JvmArgs.pluginClasspaths = if(k2JvmArgs.pluginClasspaths != null)
217+
k2JvmArgs.pluginClasspaths!!.plus(annotationProcArgs.pluginClassPaths)
220218
else
221219
annotationProcArgs.pluginClassPaths
222220
}
223221
else if(verbose) {
224222
systemOut.log("No services were given. Not including kapt in the compiler's plugins.")
225223
}
226224

227-
destination = classesDir.absolutePath
228-
classpath = allClasspaths.joinToString(separator = File.pathSeparator)
225+
k2JvmArgs.destination = classesDir.absolutePath
226+
k2JvmArgs.classpath = allClasspaths.joinToString(separator = File.pathSeparator)
229227

230-
if(this@KotlinCompilation.jdkHome != null) {
231-
jdkHome = this@KotlinCompilation.jdkHome.absolutePath
228+
if(jdkHome != null) {
229+
k2JvmArgs.jdkHome = jdkHome.absolutePath
232230
}
233231
else {
234-
noJdk = true
232+
if(verbose)
233+
systemOut.log("Using option -no-jdk. Kotlinc won't look for a JDK.")
234+
235+
k2JvmArgs.noJdk = true
235236
}
236237

237-
noStdlib = this@KotlinCompilation.noStdLib
238-
noReflect = this@KotlinCompilation.noReflect
238+
k2JvmArgs.noStdlib = noStdLib
239+
k2JvmArgs.noReflect = noReflect
240+
241+
kotlinHome?.let { k2JvmArgs.kotlinHome = it.absolutePath }
239242

240-
this@KotlinCompilation.kotlinHome?.let { kotlinHome = it.absolutePath }
243+
jvmTarget?.let { k2JvmArgs.jvmTarget = it }
241244

242-
this@KotlinCompilation.jvmTarget?.let { jvmTarget = it }
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
243253

244-
verbose = this@KotlinCompilation.verbose
245-
skipRuntimeVersionCheck = this@KotlinCompilation.skipRuntimeVersionCheck
246-
suppressWarnings = this@KotlinCompilation.suppressWarnings
247-
allWarningsAsErrors = this@KotlinCompilation.allWarningsAsErrors
248-
reportOutputFiles = this@KotlinCompilation.reportOutputFiles
249-
reportPerf = this@KotlinCompilation.reportPerformance
250-
reportOutputFiles = this@KotlinCompilation.reportOutputFiles
251-
loadBuiltInsFromDependencies = this@KotlinCompilation.loadBuiltInsFromDependencies
254+
255+
k2JvmArgs.additionalJavaModules=arrayOf()
256+
k2JvmArgs.javaModulePath=""
252257
}
253258

254-
val compilerSystemOutBuffer = Buffer()
259+
val compilerSystemOutBuffer = Buffer() // Buffer for capturing compiler's logging output
255260

256261
val exitCode = K2JVMCompiler().execImpl(
257262
PrintingMessageCollector(
@@ -262,6 +267,7 @@ class KotlinCompilation(
262267
k2jvmArgs
263268
)
264269

270+
// check for known errors that are hard to debug for the user
265271
if (exitCode == ExitCode.INTERNAL_ERROR && compilerSystemOutBuffer.readUtf8()
266272
.contains("No enum constant com.sun.tools.javac.main.Option.BOOT_CLASS_PATH")) {
267273

@@ -296,62 +302,45 @@ class KotlinCompilation(
296302

297303
companion object {
298304
/** Tries to find the kapt 3 jar in the host classpath */
299-
fun findKapt3JarInHostClasspath(hostClassLoader: ClassLoader,
300-
log: ((String) -> Unit)? = null): File? {
301-
302-
val jarFile = getClasspaths(hostClassLoader).firstOrNull { classpath ->
305+
fun findKapt3JarInHostClasspath(log: ((String) -> Unit)? = null): File? {
306+
val jarFile = getClasspaths().firstOrNull { classpath ->
303307
classpath.name.matches(Regex("kotlin-annotation-processing-embeddable.*?\\.jar"))
304308
//TODO("check that jar file actually contains the right classes")
305309
}
306310

307311
if(jarFile == null && log != null)
308312
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")
309315

310316
return jarFile
311317
}
312318

313319
/** Tries to find the tools.jar needed for kapt in the host classpath */
314-
fun findToolsJarInHostClasspath(hostClassLoader: ClassLoader,
315-
log: ((String) -> Unit)? = null): File? {
316-
317-
val jarFile = getClasspaths(hostClassLoader).firstOrNull { classpath ->
320+
fun findToolsJarInHostClasspath(log: ((String) -> Unit)? = null): File? {
321+
val jarFile = getClasspaths().firstOrNull { classpath ->
318322
classpath.name.matches(Regex("tools.jar"))
319323
//TODO("check that jar file actually contains the right classes")
320324
}
321325

322326
if(jarFile == null && log != null)
323327
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")
324330

325331
return jarFile
326332
}
327333

328-
/** Returns the files on the classloader's classpath. */
329-
fun getClasspaths(classLoader: ClassLoader): List<File> {
330-
if (classLoader is URLClassLoader) {
331-
return classLoader.urLs.map { url ->
332-
if (url.protocol != "file")
333-
throw UnsupportedOperationException("unable to handle classpath element $url")
334+
/** Returns the files on the classloader's classpath and modulepath */
335+
fun getClasspaths(): List<File> {
336+
val classGraph = ClassGraph()
337+
.enableSystemJarsAndModules()
338+
.removeTemporaryFilesAfterScan()
334339

335-
// paths may contain percent-encoded characters like %20 for space
336-
File(URLDecoder.decode(url.path, "UTF-8"))
337-
}
338-
}
339-
else {
340-
val jarsOrZips = classLoader.getResources("META-INF").toList().mapNotNull {
341-
if(it.path.matches(Regex("file:/.*?(\\.jar|\\.zip)!/META-INF")))
342-
it.path.removeSurrounding("file:/", "!/META-INF")
343-
else
344-
null
345-
}
346-
347-
val dirs = classLoader.getResources("").toList().map { it.path }
348-
val wildcards = classLoader.getResources("*").toList().map { it.path }
349-
350-
return (jarsOrZips + dirs + wildcards).map {
351-
// paths may contain percent-encoded characters like %20 for space
352-
File(URLDecoder.decode(it, "UTF-8"))
353-
}
354-
}
340+
val classpaths = classGraph.classpathFiles
341+
val modules = classGraph.modules.mapNotNull { it.locationFile }
342+
343+
return (classpaths + modules).distinctBy(File::getAbsolutePath)
355344
}
356345

357346
/** Finds the tools.jar given a path to a JDK 8 or earlier */

src/test/kotlin/SmokeTests.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,31 @@ package com.tschuchort.compiletest
33
import okio.Buffer
44
import org.assertj.core.api.Assertions.assertThat
55
import org.jetbrains.kotlin.cli.common.ExitCode
6+
import org.jetbrains.kotlin.metadata.js.JsProtoBuf
67
import org.junit.Rule
78
import org.junit.Test
89
import org.junit.rules.TemporaryFolder
910
import java.io.File
1011
import java.io.PrintStream
1112
import javax.annotation.processing.Processor
13+
import java.util.logging.Filter
14+
import java.util.logging.LogRecord
1215

1316
class SmokeTests {
1417
@Rule @JvmField val temporaryFolder = TemporaryFolder()
1518

1619
@Test
1720
fun `compilation succeeds`() {
21+
22+
1823
val source = KotlinCompilation.SourceFile("source.kt",
19-
"""import com.tschuchort.compiletest.InspectionRoot
24+
"""//import com.tschuchort.compiletest.InspectionRoot
25+
import javax.lang.model.SourceVersion
26+
import java.io.File
2027
21-
@InspectionRoot
28+
//@InspectionRoot
2229
fun main(args: Array<String>) {
30+
File("")
2331
println("hello")
2432
}
2533
""".trimIndent()
@@ -31,8 +39,8 @@ class SmokeTests {
3139
workingDir = temporaryFolder.root,
3240
sources = listOf(source),
3341
services = listOf(KotlinCompilation.Service(Processor::class, TestProcessor::class)),
34-
jdkHome = getJavaHome(),
35-
hostClassLoader = this::class.java.classLoader,
42+
//jdkHome = File("D:\\Program Files\\Java\\jdk1.8.0_25"),//getJavaHome(),
43+
//kotlinHome = File("C:\\kotlin-compiler"),
3644
//toolsJar = File("D:\\Program Files\\Java\\jdk1.8.0_25\\lib\\tools.jar"),
3745
inheritClassPath = true,
3846
skipRuntimeVersionCheck = true,

0 commit comments

Comments
 (0)