6
6
package org.jetbrains.kotlin.js.test.klib
7
7
8
8
import org.jetbrains.kotlin.cli.common.ExitCode
9
+ import org.jetbrains.kotlin.config.LanguageVersion
9
10
import java.io.File
10
11
import java.io.PrintStream
11
12
import java.lang.ref.SoftReference
12
13
import java.net.URLClassLoader
14
+ import kotlin.test.fail
13
15
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
+ }
24
27
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
28
36
}
29
37
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
+ }
32
50
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" ) }
34
53
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
+ }
36
63
}
37
64
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 >) {
39
72
private var isolatedClassLoaderSoftRef: SoftReference <URLClassLoader >? = null
40
73
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
45
80
}
81
+ }
46
82
47
- fun callCompiler (args : Array <String >, output : PrintStream ): ExitCode {
83
+ fun callCompiler (output : PrintStream , args : Array <String >): ExitCode {
48
84
val isolatedClassLoader = getIsolatedClassLoader()
49
85
50
86
val compilerClass = Class .forName(" org.jetbrains.kotlin.cli.js.K2JSCompiler" , true , isolatedClassLoader)
@@ -61,13 +97,56 @@ internal class CustomJsCompiler(private val customWebCompilerArtifacts: CustomJs
61
97
}
62
98
}
63
99
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
71
107
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
+ }
73
152
}
0 commit comments