/
SpecIrGenerationExtension.kt
138 lines (117 loc) · 6.81 KB
/
SpecIrGenerationExtension.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
package io.kotest.framework.multiplatform.native
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
import org.jetbrains.kotlin.backend.common.serialization.proto.IrExpression
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.toLogger
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.IrSingleStatementBuilder
import org.jetbrains.kotlin.ir.builders.Scope
import org.jetbrains.kotlin.ir.builders.declarations.addGetter
import org.jetbrains.kotlin.ir.builders.declarations.buildField
import org.jetbrains.kotlin.ir.builders.declarations.buildProperty
import org.jetbrains.kotlin.ir.builders.irBlock
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irVararg
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.util.addChild
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.getSimpleFunction
import org.jetbrains.kotlin.ir.util.kotlinFqName
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import java.io.File
import java.util.concurrent.CopyOnWriteArrayList
class SpecIrGenerationExtension(private val messageCollector: MessageCollector) : IrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
moduleFragment.transform(object : IrElementTransformerVoidWithContext() {
val specs = CopyOnWriteArrayList<IrClass>()
override fun visitModuleFragment(declaration: IrModuleFragment): IrModuleFragment {
val fragment = super.visitModuleFragment(declaration)
messageCollector.toLogger().log("Detected ${specs.size} native specs:")
specs.forEach {
messageCollector.toLogger().log(it.kotlinFqName.asString())
}
if (specs.isEmpty()) return fragment
val file: IrFile = declaration.files.first()
val launcherClass = pluginContext.referenceClass(ClassId.fromString(EntryPoint.TestEngineClassName))
?: error("Cannot find ${EntryPoint.TestEngineClassName} class reference")
val launcherConstructor = launcherClass.constructors.first { it.owner.valueParameters.isEmpty() }
val launchFn = launcherClass.getSimpleFunction(EntryPoint.LaunchMethodName)
?: error("Cannot find function ${EntryPoint.LaunchMethodName}")
val withSpecsFn = launcherClass.getSimpleFunction(EntryPoint.WithSpecsMethodName)
?: error("Cannot find function ${EntryPoint.WithSpecsMethodName}")
val withTeamCityListenerMethodNameFn =
launcherClass.getSimpleFunction(EntryPoint.WithTeamCityListenerMethodName)
?: error("Cannot find function ${EntryPoint.WithTeamCityListenerMethodName}")
val eagerAnnotationName = ClassId.fromString("kotlin.native.EagerInitialization")
val eagerAnnotation = pluginContext.referenceClass(eagerAnnotationName)
?: error("Cannot find eager initialisation annotation class $eagerAnnotationName")
val eagerAnnotationConstructor = eagerAnnotation.constructors.single()
val launcher = pluginContext.irFactory.buildProperty {
name = Name.identifier(EntryPoint.LauncherValName)
}.apply {
parent = file
annotations += IrSingleStatementBuilder(pluginContext, Scope(this.symbol), UNDEFINED_OFFSET, UNDEFINED_OFFSET).build { irCall(eagerAnnotationConstructor) }
backingField = pluginContext.irFactory.buildField {
type = pluginContext.irBuiltIns.unitType
isFinal = true
isExternal = false
isStatic = true // top level vals must be static
name = Name.identifier(EntryPoint.LauncherValName)
}.also { field ->
field.correspondingPropertySymbol = this@apply.symbol
field.initializer = pluginContext.irFactory.createExpressionBody(
startOffset,
endOffset,
DeclarationIrBuilder(pluginContext, field.symbol).irBlock {
+irCall(launchFn).also { launch ->
launch.dispatchReceiver = irCall(withTeamCityListenerMethodNameFn).also { withTeamCity ->
withTeamCity.dispatchReceiver = irCall(withSpecsFn).also { withSpecs ->
withSpecs.dispatchReceiver = irCall(launcherConstructor)
withSpecs.putValueArgument(
0,
irVararg(
pluginContext.irBuiltIns.stringType,
specs.map { irCall(it.constructors.first()) }
)
)
}
}
}
}
)
}
addGetter {
returnType = pluginContext.irBuiltIns.unitType
}.also { func ->
func.body = DeclarationIrBuilder(pluginContext, func.symbol).irBlockBody {
}
}
}
file.addChild(launcher)
return fragment
}
override fun visitFileNew(declaration: IrFile): IrFile {
super.visitFileNew(declaration)
val specs = declaration.specs()
messageCollector.toLogger().log("${declaration.name} contains ${specs.size} spec(s): ${specs.joinToString(", ") { it.kotlinFqName.asString() }}")
this.specs.addAll(specs)
return declaration
}
}, null)
}
}
// These extension properties are available in org.jetbrains.kotlin.ir.declarations, but were moved from one file to
// another in Kotlin 1.7. This breaks backwards compatibility with earlier versions of Kotlin.
// So instead of using the provided implementations, we've copied them here, so we can work with both Kotlin 1.7+ and earlier
// versions without issue.
// See https://github.com/kotest/kotest/issues/3060 and https://youtrack.jetbrains.com/issue/KT-52888 for more information.
private val IrFile.path: String get() = fileEntry.name
private val IrFile.name: String get() = File(path).name