|
| 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