Skip to content

Commit 8630e10

Browse files
ddolovovSpace Team
authored andcommitted
KLIB compat tests, JS: Reorganize utilities for running a custom compiler
1. Introduce an interface class to represent compiler settings for custom JS and Wasm compilers: `CustomWebCompilerSettings`. 2. Merge `JsKlibTestSettings` and `CustomJsCompilerArtifacts` into a new implementation of `CustomWebCompilerSettings` for JS. 3. Reorganize and rename `CustomJsCompiler` to `CustomWebCompiler` to make it suitable for both JS and Wasm custom compilers. ^KT-78188
1 parent 38a7a56 commit 8630e10

File tree

2 files changed

+114
-35
lines changed

2 files changed

+114
-35
lines changed

js/js.tests/testFixtures/org/jetbrains/kotlin/js/test/ir/AbstractJsCompilerInvocationTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
1010
import org.jetbrains.kotlin.cli.common.arguments.cliArgument
1111
import org.jetbrains.kotlin.cli.js.K2JSCompiler
1212
import org.jetbrains.kotlin.js.test.ir.AbstractJsCompilerInvocationTest.CompilerType
13-
import org.jetbrains.kotlin.js.test.klib.JsKlibTestSettings
13+
import org.jetbrains.kotlin.js.test.klib.customJsCompilerSettings
1414
import org.jetbrains.kotlin.js.testOld.V8JsTestChecker
1515
import org.jetbrains.kotlin.klib.KlibCompilerEdition
1616
import org.jetbrains.kotlin.klib.KlibCompilerInvocationTestUtils
@@ -58,8 +58,8 @@ private class ModuleDetails(val name: ModuleName, val outputDir: File) {
5858
constructor(dependency: Dependency) : this(dependency.moduleName, dependency.libraryFile.parentFile)
5959
}
6060

61-
private fun customCompilerCall(): (PrintStream, Array<String>) -> ExitCode = { printStream: PrintStream, allCompilerArgs: Array<String> ->
62-
JsKlibTestSettings.customJsCompiler.callCompiler(args = allCompilerArgs, output = printStream)
61+
private fun customCompilerCall(): (PrintStream, Array<String>) -> ExitCode = { printStream: PrintStream, args: Array<String> ->
62+
customJsCompilerSettings.customCompiler.callCompiler(printStream, args)
6363
}
6464

6565
private fun currentCompilerCall() = { printStream: PrintStream, args: Array<String> ->
@@ -195,7 +195,7 @@ internal class JsCompilerInvocationTestArtifactBuilder(
195195
)
196196

197197
private fun Set<Dependency>.replaceStdLib(): Set<Dependency> = mapToSetOrEmpty {
198-
if (it.moduleName == "stdlib") Dependency("stdlib", JsKlibTestSettings.customJsCompilerArtifacts.jsStdLib) else it
198+
if (it.moduleName == "stdlib") Dependency("stdlib", customJsCompilerSettings.stdlib) else it
199199
}.toSet()
200200

201201
private fun Dependencies.toCompilerArgs(): List<String> = buildList {

js/js.tests/testFixtures/org/jetbrains/kotlin/js/test/klib/CustomWebCompilerUtils.kt

Lines changed: 110 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,81 @@
66
package org.jetbrains.kotlin.js.test.klib
77

88
import org.jetbrains.kotlin.cli.common.ExitCode
9+
import org.jetbrains.kotlin.config.LanguageVersion
910
import java.io.File
1011
import java.io.PrintStream
1112
import java.lang.ref.SoftReference
1213
import java.net.URLClassLoader
14+
import kotlin.test.fail
1315

14-
internal object JsKlibTestSettings {
15-
val customJsCompilerArtifacts: CustomJsCompilerArtifacts by lazy {
16-
val artifactsDir: String? = System.getProperty("kotlin.internal.js.test.compat.customCompilerArtifactsDir")
17-
requireNotNull(artifactsDir) { "Custom compiler location is not specified" }
18-
19-
val version: String? = System.getProperty("kotlin.internal.js.test.compat.customCompilerVersion")
20-
requireNotNull(version) { "Custom compiler version is not specified" }
21-
22-
CustomJsCompilerArtifacts(artifactsDir = File(artifactsDir), version)
23-
}
16+
/**
17+
* An accessor to "custom" (alternative) Kotlin/JS compiler and the relevant artifacts (stdlib, kotlin-test)
18+
* which are used in KLIB backward/forward compatibility tests.
19+
*/
20+
val customJsCompilerSettings: CustomWebCompilerSettings by lazy {
21+
createCustomWebCompilerSettings(
22+
artifactsDirPropertyName = "kotlin.internal.js.test.compat.customCompilerArtifactsDir",
23+
versionPropertyName = "kotlin.internal.js.test.compat.customCompilerVersion",
24+
stdlibArtifactName = "kotlin-stdlib-js",
25+
)
26+
}
2427

25-
val customJsCompiler by lazy {
26-
CustomJsCompiler(customJsCompilerArtifacts)
27-
}
28+
/**
29+
* A "custom" (alternative) Kotlin/JS or Kotlin/Wasm compiler and the relevant artifacts (stdlib, kotlin-test)
30+
* which are used in KLIB backward/forward compatibility tests.
31+
*/
32+
interface CustomWebCompilerSettings {
33+
val version: String
34+
val stdlib: File
35+
val customCompiler: CustomWebCompiler
2836
}
2937

30-
internal class CustomJsCompilerArtifacts(artifactsDir: File, val version: String) {
31-
val compilerEmbeddable: File = artifactsDir.resolve("kotlin-compiler-embeddable-$version.jar")
38+
val CustomWebCompilerSettings.defaultLanguageVersion: LanguageVersion
39+
get() = LanguageVersion.fromFullVersionString(version)
40+
?: fail("Cannot deduce the default LV from the compiler version: $version")
41+
42+
private fun createCustomWebCompilerSettings(
43+
artifactsDirPropertyName: String,
44+
versionPropertyName: String,
45+
stdlibArtifactName: String,
46+
): CustomWebCompilerSettings = object : CustomWebCompilerSettings {
47+
private val artifacts: CustomWebCompilerArtifacts by lazy {
48+
CustomWebCompilerArtifacts.create(artifactsDirPropertyName, versionPropertyName)
49+
}
3250

33-
val baseStdLib: File = artifactsDir.resolve("kotlin-stdlib-$version.jar")
51+
override val version: String get() = artifacts.version
52+
override val stdlib: File by lazy { artifacts.resolve(stdlibArtifactName, "klib") }
3453

35-
val jsStdLib: File = artifactsDir.resolve("kotlin-stdlib-js-$version.klib")
54+
override val customCompiler: CustomWebCompiler by lazy {
55+
CustomWebCompiler(
56+
listOfNotNull(
57+
artifacts.resolve("kotlin-compiler-embeddable", "jar"),
58+
artifacts.resolveOptionalTrove4j(),
59+
artifacts.resolve("kotlin-stdlib", "jar"),
60+
)
61+
)
62+
}
3663
}
3764

38-
internal class CustomJsCompiler(private val customWebCompilerArtifacts: CustomJsCompilerArtifacts) {
65+
/**
66+
* An entry point to call a custom Kotlin/JS or Kotlin/Wasm compiler inside an isolated class loader.
67+
*
68+
* Note: The class loader is cached to be easily reused in all later calls without reloading the class path.
69+
* Yet it is cached as a [SoftReference] to allow GC in the case of a need.
70+
*/
71+
class CustomWebCompiler(private val compilerClassPath: List<File>) {
3972
private var isolatedClassLoaderSoftRef: SoftReference<URLClassLoader>? = null
4073

41-
private fun getIsolatedClassLoader(): URLClassLoader = isolatedClassLoaderSoftRef?.get()
42-
?: synchronized(this) {
43-
isolatedClassLoaderSoftRef?.get()
44-
?: createIsolatedClassLoader(customWebCompilerArtifacts).also { isolatedClassLoaderSoftRef = SoftReference(it) }
74+
private fun getIsolatedClassLoader(): URLClassLoader = isolatedClassLoaderSoftRef?.get() ?: synchronized(this) {
75+
isolatedClassLoaderSoftRef?.get() ?: run {
76+
val isolatedClassLoader = URLClassLoader(compilerClassPath.map { it.toURI().toURL() }.toTypedArray(), null)
77+
isolatedClassLoader.setDefaultAssertionStatus(true)
78+
isolatedClassLoaderSoftRef = SoftReference(isolatedClassLoader)
79+
isolatedClassLoader
4580
}
81+
}
4682

47-
fun callCompiler(args: Array<String>, output: PrintStream): ExitCode {
83+
fun callCompiler(output: PrintStream, args: Array<String>): ExitCode {
4884
val isolatedClassLoader = getIsolatedClassLoader()
4985

5086
val compilerClass = Class.forName("org.jetbrains.kotlin.cli.js.K2JSCompiler", true, isolatedClassLoader)
@@ -61,13 +97,56 @@ internal class CustomJsCompiler(private val customWebCompilerArtifacts: CustomJs
6197
}
6298
}
6399

64-
private fun createIsolatedClassLoader(customWebCompilerArtifacts: CustomJsCompilerArtifacts): URLClassLoader {
65-
val compilerClassPath = setOf(
66-
customWebCompilerArtifacts.compilerEmbeddable,
67-
customWebCompilerArtifacts.baseStdLib,
68-
)
69-
.map { it.toURI().toURL() }
70-
.toTypedArray()
100+
private sealed interface CustomWebCompilerArtifacts {
101+
val version: String
102+
103+
/**
104+
* Resolves the mandatory artifact '$baseName-$version.$extension', where $extension is one of the passed [extensions].
105+
*/
106+
fun resolve(baseName: String, vararg extensions: String): File
71107

72-
return URLClassLoader(compilerClassPath, null).apply { setDefaultAssertionStatus(true) }
108+
/**
109+
* This artifact was removed in Kotlin 2.2.0-Beta1.
110+
* But it is still available in older compiler versions, where we need to load it.
111+
*/
112+
fun resolveOptionalTrove4j(): File?
113+
114+
private class Resolvable(override val version: String, private val artifactsDir: File) : CustomWebCompilerArtifacts {
115+
override fun resolve(baseName: String, vararg extensions: String): File {
116+
val candidates: List<File> = extensions.map { extension -> artifactsDir.resolve("$baseName-$version.$extension") }
117+
return candidates.firstOrNull { it.exists() } ?: fail("Artifact $baseName is not found. Candidates tested: $candidates")
118+
}
119+
120+
override fun resolveOptionalTrove4j(): File? {
121+
return artifactsDir.listFiles()?.firstOrNull { file ->
122+
file.isFile && file.name.run { startsWith("trove4j") && endsWith("jar") }
123+
}
124+
}
125+
}
126+
127+
private class Unresolvable(val reason: String) : CustomWebCompilerArtifacts {
128+
override val version get() = fail(reason)
129+
override fun resolve(baseName: String, vararg extensions: String) = fail(reason)
130+
override fun resolveOptionalTrove4j() = fail(reason)
131+
}
132+
133+
companion object {
134+
fun create(artifactsDirPropertyName: String, versionPropertyName: String): CustomWebCompilerArtifacts {
135+
val version: String = readProperty(versionPropertyName)
136+
?: return propertyNotFound(versionPropertyName)
137+
138+
val artifactsDir: File = readProperty(artifactsDirPropertyName)?.let(::File)
139+
?: return propertyNotFound(artifactsDirPropertyName)
140+
141+
return Resolvable(version, artifactsDir)
142+
}
143+
144+
private fun readProperty(propertyName: String): String? =
145+
System.getProperty(propertyName)?.trim(Char::isWhitespace)?.takeIf(String::isNotEmpty)
146+
147+
private fun propertyNotFound(propertyName: String): Unresolvable = Unresolvable(
148+
"The Gradle property \"$propertyName\" is not specified. " +
149+
"Please check the README.md in the root of the `klib-compatibility` project."
150+
)
151+
}
73152
}

0 commit comments

Comments
 (0)