Skip to content

Commit 98d5e13

Browse files
committed
Improve code generation for android target
1 parent 8f34da2 commit 98d5e13

File tree

5 files changed

+203
-196
lines changed

5 files changed

+203
-196
lines changed

examples/kotlin-multiplatform/src/androidMain/kotlin/AndroidTestBenchmark.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ class AndroidTestBenchmark {
1212
data = 3.0
1313
}
1414

15+
@TearDown
16+
fun teardown() {
17+
// println("Teardown!")
18+
}
19+
1520
@Benchmark
1621
fun sqrtBenchmark(): Double {
1722
return sqrt(data)
@@ -21,6 +26,4 @@ class AndroidTestBenchmark {
2126
fun cosBenchmark(): Double {
2227
return cos(data)
2328
}
24-
}
25-
26-
29+
}

plugin/main/src/kotlinx/benchmark/gradle/AndroidMultiplatformTasks.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ fun Project.processAndroidCompilation(target: KotlinJvmAndroidCompilation) {
1717
it.description = "Processes the Android compilation '${target.name}' for benchmarks"
1818
it.dependsOn("bundle${target.name.capitalize(Locale.getDefault())}Aar")
1919
it.doLast {
20-
unpackAndProcessAar(target)
21-
//generateAndroidExecFile()
20+
unpackAndProcessAar(target) { classDescriptors ->
21+
generateBenchmarkSourceFiles(classDescriptors)
22+
}
2223
detectAndroidDevice()
2324
}
2425
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package kotlinx.benchmark.gradle
2+
3+
import com.squareup.kotlinpoet.*
4+
import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi
5+
import org.gradle.api.*
6+
import java.io.File
7+
8+
@KotlinxBenchmarkPluginInternalApi
9+
fun Project.generateBenchmarkSourceFiles(
10+
classDescriptors: List<ClassAnnotationsDescriptor>,
11+
) {
12+
13+
val targetPath = "E:/Android/AndroidProjects/kotlin-qualification-task/composeApp"
14+
val androidTestDir = File(targetPath).resolve("src/androidTest/kotlin")
15+
if (!androidTestDir.exists()) {
16+
androidTestDir.mkdirs()
17+
}
18+
19+
val buildGradleFile = File(targetPath).resolve("build.gradle.kts")
20+
val dependencyPaths = listOf(
21+
"${project.projectDir}/build/outputs/unpacked-aar/release/classes.jar".replace("\\", "/") to null,
22+
"androidx.benchmark:benchmark-junit4" to "1.2.4",
23+
"androidx.test.ext:junit-ktx" to "1.2.1",
24+
"junit:junit" to "4.13.2"
25+
)
26+
27+
updateAndroidDependencies(buildGradleFile, dependencyPaths)
28+
29+
classDescriptors.forEach { descriptor ->
30+
if (descriptor.visibility == Visibility.PUBLIC && !descriptor.isAbstract) {
31+
generateDescriptorFile(descriptor, androidTestDir)
32+
}
33+
}
34+
}
35+
36+
private fun generateDescriptorFile(descriptor: ClassAnnotationsDescriptor, androidTestDir: File) {
37+
val descriptorName = "${descriptor.name}_Descriptor"
38+
val packageName = descriptor.packageName
39+
val fileSpecBuilder = FileSpec.builder(packageName, descriptorName)
40+
.addImport("androidx.test.ext.junit.runners", "AndroidJUnit4")
41+
.addImport("org.junit", "Test")
42+
.addImport("org.junit.runner", "RunWith")
43+
.addImport("androidx.benchmark.junit4", "BenchmarkRule")
44+
45+
val typeSpecBuilder = TypeSpec.classBuilder(descriptorName)
46+
.addAnnotation(
47+
AnnotationSpec.builder(ClassName("org.junit.runner", "RunWith"))
48+
.addMember("%T::class", ClassName("androidx.test.ext.junit.runners", "AndroidJUnit4"))
49+
.build()
50+
)
51+
.addProperty(
52+
PropertySpec.builder("benchmarkRule", ClassName("androidx.benchmark.junit4", "BenchmarkRule"))
53+
.addAnnotation(
54+
AnnotationSpec.builder(ClassName("org.junit", "Rule"))
55+
.useSiteTarget(AnnotationSpec.UseSiteTarget.GET)
56+
.build()
57+
)
58+
.initializer("BenchmarkRule()")
59+
.build()
60+
)
61+
62+
addBenchmarkMethods(typeSpecBuilder, descriptor)
63+
64+
fileSpecBuilder.addType(typeSpecBuilder.build())
65+
fileSpecBuilder.build().writeTo(androidTestDir)
66+
}
67+
68+
private fun addBenchmarkMethods(typeSpecBuilder: TypeSpec.Builder, descriptor: ClassAnnotationsDescriptor) {
69+
val className = "${descriptor.packageName}.${descriptor.name}"
70+
val propertyName = descriptor.name.decapitalize()
71+
72+
typeSpecBuilder.addProperty(
73+
PropertySpec.builder(propertyName, ClassName.bestGuess(className))
74+
.initializer("%T()", ClassName.bestGuess(className))
75+
.addModifiers(KModifier.PRIVATE)
76+
.build()
77+
)
78+
79+
// TODO: Handle methods with parameters (Blackhole)
80+
descriptor.methods
81+
.filter { it.visibility == Visibility.PUBLIC && it.parameters.isEmpty() }
82+
.filterNot { method ->
83+
method.annotations.any { annotation -> annotation.name == "kotlinx.benchmark.Param" }
84+
}
85+
.forEach { method ->
86+
val methodSpecBuilder = FunSpec.builder("benchmark_${descriptor.name}_${method.name}")
87+
.addAnnotation(ClassName("org.junit", "Test"))
88+
.addStatement("$propertyName.${method.name}()")
89+
typeSpecBuilder.addFunction(methodSpecBuilder.build())
90+
}
91+
}
92+
93+
private fun updateAndroidDependencies(buildGradleFile: File, dependencies: List<Pair<String, String?>>) {
94+
if (buildGradleFile.exists()) {
95+
val buildGradleContent = buildGradleFile.readText()
96+
97+
if (buildGradleContent.contains("android {")) {
98+
val androidBlockStart = buildGradleContent.indexOf("android {")
99+
val androidBlockEnd = buildGradleContent.lastIndexOf("}") + 1
100+
val androidBlockContent = buildGradleContent.substring(androidBlockStart, androidBlockEnd)
101+
102+
val newDependencies = dependencies.filterNot { (dependency, version) ->
103+
val dependencyString = version?.let { """$dependency:$version""" } ?: dependency
104+
androidBlockContent.contains(dependencyString)
105+
}
106+
if (newDependencies.isNotEmpty()) {
107+
val updatedAndroidBlockContent = if (androidBlockContent.contains("dependencies {")) {
108+
val dependenciesBlockStart = androidBlockContent.indexOf("dependencies {")
109+
val dependenciesBlockEnd = androidBlockContent.indexOf("}", dependenciesBlockStart) + 1
110+
val dependenciesBlockContent = androidBlockContent.substring(dependenciesBlockStart, dependenciesBlockEnd)
111+
112+
val newDependenciesString = newDependencies.joinToString("\n ") { (dependency, version) ->
113+
version?.let { """androidTestImplementation("$dependency:$version")""" } ?: """androidTestImplementation(files("$dependency"))"""
114+
}
115+
androidBlockContent.replace(
116+
dependenciesBlockContent,
117+
dependenciesBlockContent.replace(
118+
"dependencies {",
119+
"dependencies {\n $newDependenciesString"
120+
)
121+
)
122+
} else {
123+
val newDependenciesString = newDependencies.joinToString("\n ") { (dependency, version) ->
124+
version?.let { """androidTestImplementation("$dependency:$version")""" } ?: """androidTestImplementation(files("$dependency"))"""
125+
}
126+
androidBlockContent.replace("{", "{\n dependencies {\n $newDependenciesString\n }\n")
127+
}
128+
129+
val updatedBuildGradleContent = buildGradleContent.replace(androidBlockContent, updatedAndroidBlockContent)
130+
buildGradleFile.writeText(updatedBuildGradleContent)
131+
}
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)