Skip to content

Commit 279d309

Browse files
committed
Use Worker API to generate benchmark code
- Update the Benchmark JS/Wasm/Native source generation tasks to use the Worker API, so that `kotlin-compiler-embeddable` can be a compileOnly dependency, thus resolving https://youtrack.jetbrains.com/issue/KT-66764 - Create `GenerateJsSourceWorker` and `GenerateWasmSourceWorker` for isolating the JS and Wasm generation code. - Updated the Js/Wasm/Native generation tasks to invoke the source generators in a Worker isolated classpath. - Created a new Configuration for declaring `kotlin-compiler-embeddable`, and pass it to the Js/Wasm/Native generation tasks. - Create a BenchmarkDependencies utility for defining all Configurations used in the plugin. (This also lays the groundwork for unifying the JMH version #147 (comment)) - Updated BenchmarksPluginConstants to add `DEFAULT_KOTLIN_COMPILER_VERSION`, as sensible default for kotlin-compiler-embeddable. - Add opt-in annotation RequiresKotlinCompilerEmbeddable to highlight code that requires `kotlin-compiler-embeddable` - Some classes were updated to be `abstract`, to follow Gradle best practices for creating managed objects. This commit also contains formatting changes, as the code style is not consistent.
1 parent 1aefc62 commit 279d309

15 files changed

+509
-145
lines changed

Diff for: plugin/build.gradle

+4
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,17 @@ def generatePluginConstants = tasks.register("generatePluginConstants") {
118118
Provider<String> minSupportedGradleVersion = libs.versions.minSupportedGradle
119119
inputs.property("minSupportedGradleVersion", minSupportedGradleVersion)
120120

121+
Provider<String> kotlinCompilerVersion = libs.versions.kotlin
122+
inputs.property("kotlinCompilerVersion", kotlinCompilerVersion)
123+
121124
doLast {
122125
constantsKtFile.write(
123126
"""|package kotlinx.benchmark.gradle.internal
124127
|
125128
|internal object BenchmarksPluginConstants {
126129
| const val BENCHMARK_PLUGIN_VERSION = "${benchmarkPluginVersion.get()}"
127130
| const val MIN_SUPPORTED_GRADLE_VERSION = "${minSupportedGradleVersion.get()}"
131+
| const val DEFAULT_KOTLIN_COMPILER_VERSION = "${kotlinCompilerVersion.get()}"
128132
|}
129133
|""".stripMargin()
130134
)

Diff for: plugin/main/src/kotlinx/benchmark/gradle/AnnotationsValidator.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package kotlinx.benchmark.gradle
22

33
import kotlinx.benchmark.gradle.SuiteSourceGenerator.Companion.paramAnnotationFQN
4+
import kotlinx.benchmark.gradle.internal.generator.RequiresKotlinCompilerEmbeddable
45
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
56
import org.jetbrains.kotlin.builtins.UnsignedTypes
67
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
@@ -10,6 +11,7 @@ import org.jetbrains.kotlin.js.descriptorUtils.getKotlinTypeFqName
1011
import org.jetbrains.kotlin.name.FqName
1112
import org.jetbrains.kotlin.resolve.annotations.argumentValue
1213

14+
@RequiresKotlinCompilerEmbeddable
1315
internal fun validateBenchmarkFunctions(functions: List<FunctionDescriptor>) {
1416
functions.forEach { function ->
1517
if (function.visibility != DescriptorVisibilities.PUBLIC) {
@@ -30,6 +32,7 @@ internal fun validateBenchmarkFunctions(functions: List<FunctionDescriptor>) {
3032
}
3133
}
3234

35+
@RequiresKotlinCompilerEmbeddable
3336
internal fun validateSetupFunctions(functions: List<FunctionDescriptor>) {
3437
functions.forEach { function ->
3538
if (function.visibility != DescriptorVisibilities.PUBLIC) {
@@ -44,6 +47,7 @@ internal fun validateSetupFunctions(functions: List<FunctionDescriptor>) {
4447
}
4548
}
4649

50+
@RequiresKotlinCompilerEmbeddable
4751
internal fun validateTeardownFunctions(functions: List<FunctionDescriptor>) {
4852
functions.forEach { function ->
4953
if (function.visibility != DescriptorVisibilities.PUBLIC) {
@@ -58,6 +62,7 @@ internal fun validateTeardownFunctions(functions: List<FunctionDescriptor>) {
5862
}
5963
}
6064

65+
@RequiresKotlinCompilerEmbeddable
6166
internal fun validateParameterProperties(properties: List<PropertyDescriptor>) {
6267
properties.forEach { property ->
6368
if (!property.isVar) {
@@ -81,4 +86,4 @@ internal fun validateParameterProperties(properties: List<PropertyDescriptor>) {
8186
error("@Param annotation should have at least one argument. The annotation on property `${property.name}` has no arguments.")
8287
}
8388
}
84-
}
89+
}

Diff for: plugin/main/src/kotlinx/benchmark/gradle/BenchmarksExtension.kt

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import groovy.lang.Closure
44
import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi
55
import org.gradle.api.*
66
import org.gradle.api.plugins.*
7+
import org.gradle.api.provider.*
78
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
89
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
910
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
@@ -16,7 +17,7 @@ fun Project.benchmark(configure: Action<BenchmarksExtension>) {
1617
extensions.configure(BenchmarksExtension::class.java, configure)
1718
}
1819

19-
open class BenchmarksExtension
20+
abstract class BenchmarksExtension
2021
@KotlinxBenchmarkPluginInternalApi
2122
constructor(
2223
val project: Project
@@ -28,6 +29,8 @@ constructor(
2829

2930
val version = BenchmarksPlugin.PLUGIN_VERSION
3031

32+
abstract val kotlinCompilerVersion: Property<String>
33+
3134
fun configurations(configureClosure: Closure<NamedDomainObjectContainer<BenchmarkConfiguration>>): NamedDomainObjectContainer<BenchmarkConfiguration> {
3235
return configurations.configure(configureClosure)
3336
}
@@ -46,7 +49,8 @@ constructor(
4649

4750
val targets: NamedDomainObjectContainer<BenchmarkTarget> = run {
4851
project.container(BenchmarkTarget::class.java) { name ->
49-
val multiplatformClass = tryGetClass<KotlinMultiplatformExtension>("org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension")
52+
val multiplatformClass =
53+
tryGetClass<KotlinMultiplatformExtension>("org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension")
5054
val multiplatform = multiplatformClass?.let { project.extensions.findByType(it) }
5155
val javaExtension = project.extensions.findByType(JavaPluginExtension::class.java)
5256

Diff for: plugin/main/src/kotlinx/benchmark/gradle/BenchmarksPlugin.kt

+37-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package kotlinx.benchmark.gradle
22

3+
import kotlinx.benchmark.gradle.internal.BenchmarkDependencies
34
import kotlinx.benchmark.gradle.internal.BenchmarksPluginConstants
5+
import kotlinx.benchmark.gradle.internal.BenchmarksPluginConstants.DEFAULT_KOTLIN_COMPILER_VERSION
46
import kotlinx.benchmark.gradle.internal.BenchmarksPluginConstants.MIN_SUPPORTED_GRADLE_VERSION
57
import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi
68
import org.gradle.api.*
9+
import org.gradle.api.provider.*
710
import org.gradle.util.GradleVersion
11+
import javax.inject.Inject
812

913
@Suppress("unused")
1014
abstract class BenchmarksPlugin
1115
@KotlinxBenchmarkPluginInternalApi
12-
constructor() : Plugin<Project> {
16+
@Inject
17+
constructor(
18+
private val providers: ProviderFactory,
19+
) : Plugin<Project> {
1320

1421
companion object {
1522
const val PLUGIN_ID = "org.jetbrains.kotlinx.benchmark"
@@ -50,7 +57,7 @@ constructor() : Plugin<Project> {
5057

5158
override fun apply(project: Project) = project.run {
5259
// DO NOT use properties of an extension immediately, it will not contain any user-specified data
53-
val extension = extensions.create(BENCHMARK_EXTENSION_NAME, BenchmarksExtension::class.java, project)
60+
val extension = createBenchmarksExtension(project)
5461

5562
if (GradleVersion.current() < GradleVersion.version(MIN_SUPPORTED_GRADLE_VERSION)) {
5663
logger.error("JetBrains Gradle Benchmarks plugin requires Gradle version $MIN_SUPPORTED_GRADLE_VERSION or higher")
@@ -62,19 +69,24 @@ constructor() : Plugin<Project> {
6269
if (!getKotlinVersion(kotlinPlugin.pluginVersion).isAtLeast(1, 9, 20)) {
6370
logger.error("JetBrains Gradle Benchmarks plugin requires Kotlin version 1.9.20 or higher")
6471
}
72+
extension.kotlinCompilerVersion.set(kotlinPlugin.pluginVersion)
6573
}
6674

67-
// Create empty task that will depend on all benchmark building tasks to build all benchmarks in a project
75+
// Create a lifecycle task that will depend on all benchmark building tasks to build all benchmarks in a project
6876
val assembleBenchmarks = task<DefaultTask>(ASSEMBLE_BENCHMARKS_TASKNAME) {
6977
group = BENCHMARKS_TASK_GROUP
7078
description = "Generate and build all benchmarks in a project"
7179
}
7280

81+
val benchmarkDependencies = BenchmarkDependencies(project, extension)
82+
83+
configureBenchmarkTaskConventions(project, benchmarkDependencies)
84+
7385
// TODO: Design configuration avoidance
7486
// I currently don't know how to do it correctly yet, so materialize all tasks after project evaluation.
7587
afterEvaluate {
7688
extension.configurations.forEach {
77-
// Create empty task that will depend on all benchmark execution tasks to run all benchmarks in a project
89+
// Create a lifecycle task that will depend on all benchmark execution tasks to run all benchmarks in a project
7890
task<DefaultTask>(it.prefixName(RUN_BENCHMARKS_TASKNAME)) {
7991
group = BENCHMARKS_TASK_GROUP
8092
description = "Execute all benchmarks in a project"
@@ -92,6 +104,12 @@ constructor() : Plugin<Project> {
92104
}
93105
}
94106

107+
private fun createBenchmarksExtension(project: Project): BenchmarksExtension {
108+
return project.extensions.create(BENCHMARK_EXTENSION_NAME, BenchmarksExtension::class.java, project).apply {
109+
kotlinCompilerVersion.convention(DEFAULT_KOTLIN_COMPILER_VERSION)
110+
}
111+
}
112+
95113
private fun Project.processConfigurations(extension: BenchmarksExtension) {
96114
// Calling `all` on NDOC causes all items to materialize and be configured
97115
extension.targets.all { config ->
@@ -104,6 +122,21 @@ constructor() : Plugin<Project> {
104122
}
105123
}
106124
}
125+
126+
private fun configureBenchmarkTaskConventions(
127+
project: Project,
128+
benchmarkDependencies: BenchmarkDependencies,
129+
) {
130+
project.tasks.withType(NativeSourceGeneratorTask::class.java).configureEach {
131+
it.runtimeClasspath.from(benchmarkDependencies.benchmarkGeneratorResolver)
132+
}
133+
project.tasks.withType(WasmSourceGeneratorTask::class.java).configureEach {
134+
it.runtimeClasspath.from(benchmarkDependencies.benchmarkGeneratorResolver)
135+
}
136+
project.tasks.withType(JsSourceGeneratorTask::class.java).configureEach {
137+
it.runtimeClasspath.from(benchmarkDependencies.benchmarkGeneratorResolver)
138+
}
139+
}
107140
}
108141

109142
private fun getKotlinVersion(kotlinVersion: String): KotlinVersion {

Diff for: plugin/main/src/kotlinx/benchmark/gradle/JmhBytecodeGeneratorWorker.kt

+36-22
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,43 @@
1616
package kotlinx.benchmark.gradle
1717

1818
import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi
19-
import org.gradle.api.file.DirectoryProperty
20-
import org.gradle.api.file.ConfigurableFileCollection
19+
import org.gradle.api.file.*
20+
import org.gradle.api.logging.*
2121
import org.gradle.workers.WorkAction
2222
import org.gradle.workers.WorkParameters
23-
import org.openjdk.jmh.annotations.*
24-
import org.openjdk.jmh.generators.core.*
25-
import org.openjdk.jmh.generators.reflection.*
26-
import org.openjdk.jmh.util.*
27-
import java.io.*
28-
import java.net.*
29-
import java.util.*
23+
import org.openjdk.jmh.annotations.Benchmark
24+
import org.openjdk.jmh.generators.core.BenchmarkGenerator
25+
import org.openjdk.jmh.generators.core.FileSystemDestination
26+
import org.openjdk.jmh.generators.reflection.RFGeneratorSource
27+
import org.openjdk.jmh.util.FileUtils
28+
import java.io.File
29+
import java.net.URL
30+
import java.net.URLClassLoader
3031

3132
@KotlinxBenchmarkPluginInternalApi
33+
// TODO Make visibility of JmhBytecodeGeneratorWorker `internal` in version 1.0.
34+
// Move to package kotlinx.benchmark.gradle.internal.generator.workers, alongside the other workers.
3235
abstract class JmhBytecodeGeneratorWorker : WorkAction<JmhBytecodeGeneratorWorkParameters> {
3336

34-
@KotlinxBenchmarkPluginInternalApi
35-
companion object {
37+
// TODO in version 1.0 replace JmhBytecodeGeneratorWorkParameters with this interface:
38+
//internal interface Parameters : WorkParameters {
39+
// val inputClasses: ConfigurableFileCollection
40+
// val inputClasspath: ConfigurableFileCollection
41+
// val outputSourceDirectory: DirectoryProperty
42+
// val outputResourceDirectory: DirectoryProperty
43+
//}
44+
45+
internal companion object {
3646
private const val classSuffix = ".class"
47+
private val logger = Logging.getLogger(JmhBytecodeGeneratorWorker::class.java)
3748
}
3849

3950
private val outputSourceDirectory: File get() = parameters.outputSourceDirectory.get().asFile
4051
private val outputResourceDirectory: File get() = parameters.outputResourceDirectory.get().asFile
4152

4253
override fun execute() {
43-
cleanup(outputSourceDirectory)
44-
cleanup(outputResourceDirectory)
54+
outputSourceDirectory.deleteRecursively()
55+
outputResourceDirectory.deleteRecursively()
4556

4657
val urls = (parameters.inputClasses + parameters.inputClasspath).map { it.toURI().toURL() }.toTypedArray()
4758

@@ -58,13 +69,13 @@ abstract class JmhBytecodeGeneratorWorker : WorkAction<JmhBytecodeGeneratorWorkP
5869
// inside JMH bytecode gen. This hack seem to work, but we need to understand
5970
val introspectionClassLoader = URLClassLoader(urls, benchmarkAnnotation.classLoader)
6071

61-
/*
62-
println("Original_Parent_ParentCL: ${originalClassLoader.parent.parent}")
63-
println("Original_ParentCL: ${originalClassLoader.parent}")
64-
println("OriginalCL: $originalClassLoader")
65-
println("IntrospectCL: $introspectionClassLoader")
66-
println("BenchmarkCL: ${benchmarkAnnotation.classLoader}")
67-
*/
72+
/*
73+
println("Original_Parent_ParentCL: ${originalClassLoader.parent.parent}")
74+
println("Original_ParentCL: ${originalClassLoader.parent}")
75+
println("OriginalCL: $originalClassLoader")
76+
println("IntrospectCL: $introspectionClassLoader")
77+
println("BenchmarkCL: ${benchmarkAnnotation.classLoader}")
78+
*/
6879

6980
try {
7081
currentThread.contextClassLoader = introspectionClassLoader
@@ -97,7 +108,7 @@ abstract class JmhBytecodeGeneratorWorker : WorkAction<JmhBytecodeGeneratorWorkP
97108
}
98109
}
99110

100-
println("Writing out Java source to $outputSourceDirectory and resources to $outputResourceDirectory")
111+
logger.lifecycle("Writing out Java source to $outputSourceDirectory and resources to $outputResourceDirectory")
101112
val gen = BenchmarkGenerator()
102113
gen.generate(source, destination)
103114
gen.complete(source, destination)
@@ -109,12 +120,15 @@ abstract class JmhBytecodeGeneratorWorker : WorkAction<JmhBytecodeGeneratorWorkP
109120
errCount++
110121
sb.append(" - ").append(e.toString()).append("\n")
111122
}
112-
throw RuntimeException("Generation of JMH bytecode failed with " + errCount + " errors:\n" + sb)
123+
throw RuntimeException("Generation of JMH bytecode failed with $errCount errors:\n$sb")
113124
}
114125
}
115126
}
116127

117128
@KotlinxBenchmarkPluginInternalApi
129+
// TODO In version 1.0:
130+
// - Make internal
131+
// - Move to a nested interface inside of JmhBytecodeGeneratorWorker (like the other workers)
118132
interface JmhBytecodeGeneratorWorkParameters : WorkParameters {
119133
val inputClasses: ConfigurableFileCollection
120134
val inputClasspath: ConfigurableFileCollection
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
package kotlinx.benchmark.gradle
22

33
import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi
4-
import org.gradle.api.DefaultTask
5-
import org.gradle.api.file.FileCollection
4+
import kotlinx.benchmark.gradle.internal.generator.RequiresKotlinCompilerEmbeddable
5+
import kotlinx.benchmark.gradle.internal.generator.workers.GenerateJsSourceWorker
6+
import org.gradle.api.*
7+
import org.gradle.api.file.*
68
import org.gradle.api.tasks.*
79
import org.gradle.workers.WorkerExecutor
8-
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
9-
import org.jetbrains.kotlin.storage.LockBasedStorageManager
10-
import org.jetbrains.kotlin.storage.StorageManager
1110
import java.io.File
1211
import javax.inject.Inject
1312

1413
@CacheableTask
15-
open class JsSourceGeneratorTask
14+
abstract class JsSourceGeneratorTask
1615
@KotlinxBenchmarkPluginInternalApi
1716
@Inject
1817
constructor(
@@ -37,34 +36,28 @@ constructor(
3736
@OutputDirectory
3837
lateinit var outputSourcesDir: File
3938

39+
@get:Classpath
40+
abstract val runtimeClasspath: ConfigurableFileCollection
41+
4042
@TaskAction
4143
fun generate() {
42-
cleanup(outputSourcesDir)
43-
cleanup(outputResourcesDir)
44-
45-
inputClassesDirs.files.forEach { lib: File ->
46-
generateSources(lib)
44+
val workQueue = workerExecutor.classLoaderIsolation {
45+
it.classpath.from(runtimeClasspath)
4746
}
48-
}
4947

50-
private fun generateSources(lib: File) {
51-
val modules = loadIr(lib, LockBasedStorageManager("Inspect"))
52-
modules.forEach { module ->
53-
val generator = SuiteSourceGenerator(
54-
title,
55-
module,
56-
outputSourcesDir,
57-
if (useBenchmarkJs) Platform.JsBenchmarkJs else Platform.JsBuiltIn
58-
)
59-
generator.generate()
48+
@OptIn(RequiresKotlinCompilerEmbeddable::class)
49+
workQueue.submit(GenerateJsSourceWorker::class.java) {
50+
it.title.set(title)
51+
it.inputClasses.from(inputClassesDirs)
52+
it.inputDependencies.from(inputDependencies)
53+
it.outputSourcesDir.set(outputSourcesDir)
54+
it.outputResourcesDir.set(outputResourcesDir)
55+
it.useBenchmarkJs.set(useBenchmarkJs)
6056
}
61-
}
6257

63-
private fun loadIr(lib: File, storageManager: StorageManager): List<ModuleDescriptor> {
64-
// skip processing of empty dirs (fails if not to do it)
65-
if (lib.listFiles() == null) return emptyList()
66-
val dependencies = inputDependencies.files.filterNot { it.extension == "js" }.toSet()
67-
val module = KlibResolver.JS.createModuleDescriptor(lib, dependencies, storageManager)
68-
return listOf(module)
58+
workQueue.await() // I'm not sure if waiting is necessary,
59+
// but I suspect that the task dependencies aren't configured correctly,
60+
// so: better-safe-than-sorry.
61+
// Try removing await() when Benchmarks follows Gradle best practices.
6962
}
7063
}

0 commit comments

Comments
 (0)