1
1
package kotlinx.benchmark.gradle
2
2
3
+ import com.squareup.kotlinpoet.*
3
4
import kotlinx.benchmark.gradle.internal.KotlinxBenchmarkPluginInternalApi
4
5
import org.gradle.api.*
5
6
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
@@ -31,6 +32,7 @@ fun Project.unpackAndProcessAar(target: KotlinJvmAndroidCompilation) {
31
32
println (" Class annotations for ${target.name} :" )
32
33
33
34
classAnnotationsDescriptors.forEach { descriptor ->
35
+ println (" Package: ${descriptor.packageName} " )
34
36
println (" Class: ${descriptor.name} " )
35
37
println (" Visibility: ${descriptor.visibility} " )
36
38
descriptor.annotations.forEach { annotation ->
@@ -67,4 +69,144 @@ fun Project.unpackAndProcessAar(target: KotlinJvmAndroidCompilation) {
67
69
} else {
68
70
println (" AAR file not found" )
69
71
}
72
+ }
73
+
74
+ @KotlinxBenchmarkPluginInternalApi
75
+ fun Project.generateAndroidExecFile () {
76
+ // TODO: It should be android template project directory
77
+ val androidTestDir = File (" E:\\ Android\\ AndroidProjects\\ kotlin-qualification-task\\ composeApp\\ src\\ androidTest\\ kotlin" )
78
+ if (! androidTestDir.exists()) {
79
+ androidTestDir.mkdirs()
80
+ }
81
+
82
+ // TODO: It should be android template project build.gradle.kts file
83
+ val buildGradleFile = File (" E:\\ Android\\ AndroidProjects\\ kotlin-qualification-task\\ composeApp\\ build.gradle.kts" )
84
+ val dependencyPaths = listOf (
85
+ " ${project.projectDir} \\ build\\ outputs\\ unpacked-aar\\ release\\ classes.jar" .replace(" \\ " , " \\\\ " ) to null ,
86
+ " androidx.benchmark:benchmark-junit4" to " 1.2.4" ,
87
+ " androidx.test.ext:junit-ktx" to " 1.2.1" ,
88
+ " junit:junit" to " 4.13.2"
89
+ )
90
+
91
+ updateAndroidDependencies(buildGradleFile, dependencyPaths)
92
+
93
+ val jarFile = JarFile (File (" ${project.projectDir} \\ build\\ outputs\\ unpacked-aar\\ release\\ classes.jar" ))
94
+ val annotationProcessor = AnnotationProcessor ()
95
+ annotationProcessor.processJarFile(jarFile)
96
+ jarFile.close()
97
+
98
+ val classDescriptors = annotationProcessor.getClassDescriptors()
99
+ val fileSpecBuilder = FileSpec .builder(" generated" , " GeneratedCode" )
100
+ .addImport(" androidx.test.ext.junit.runners" , " AndroidJUnit4" )
101
+ .addImport(" org.junit" , " Test" )
102
+ .addImport(" org.junit.runner" , " RunWith" )
103
+ val typeSpecBuilder = TypeSpec .classBuilder(" GeneratedCode" )
104
+ .addAnnotation(
105
+ AnnotationSpec .builder(ClassName (" org.junit.runner" , " RunWith" ))
106
+ .addMember(" %T::class" , ClassName (" androidx.test.ext.junit.runners" , " AndroidJUnit4" ))
107
+ .build()
108
+ )
109
+
110
+ val uniquePropertyNames = mutableSetOf<String >()
111
+
112
+ classDescriptors.forEach { descriptor ->
113
+ if (descriptor.visibility == Visibility .PUBLIC && ! descriptor.isAbstract) {
114
+ val simpleClassName = descriptor.name
115
+ val fullyQualifiedName = " ${descriptor.packageName} .$simpleClassName "
116
+
117
+ var propertyName = " ${descriptor.packageName.replace(' .' , ' _' )} _$simpleClassName " .decapitalize()
118
+ if (! propertyName.endsWith(" Benchmark" )) {
119
+ propertyName + = " Benchmark"
120
+ }
121
+
122
+ while (! uniquePropertyNames.add(propertyName)) {
123
+ propertyName + = " _"
124
+ }
125
+
126
+ val propertySpec = PropertySpec .builder(propertyName, ClassName .bestGuess(fullyQualifiedName))
127
+ .initializer(" %T()" , ClassName .bestGuess(fullyQualifiedName))
128
+ .build()
129
+ typeSpecBuilder.addProperty(propertySpec)
130
+
131
+ val methodSpecBuilder = FunSpec .builder(" benchmarkExecutor_${descriptor.packageName.replace(' .' , ' _' )} _$simpleClassName " )
132
+ .addAnnotation(ClassName (" org.junit" , " Test" ))
133
+ .addCode(buildBenchmarkTestFunctionCode(propertyName, descriptor.methods))
134
+
135
+ typeSpecBuilder.addFunction(methodSpecBuilder.build())
136
+ }
137
+ }
138
+
139
+ fileSpecBuilder.addType(typeSpecBuilder.build())
140
+ fileSpecBuilder.build().writeTo(androidTestDir)
141
+ }
142
+
143
+ private fun buildBenchmarkTestFunctionCode (propertyName : String , methods : List <MethodAnnotationsDescriptor >): CodeBlock {
144
+ val builder = CodeBlock .builder()
145
+
146
+ val setUpMethod = methods.find { it.name == " setUp" }
147
+ val tearDownMethod = methods.find { it.name == " tearDown" }
148
+ val benchmarkMethods = methods.filter { it.name != " setUp" && it.name != " tearDown" && it.visibility == Visibility .PUBLIC }
149
+
150
+ setUpMethod?.let {
151
+ builder.addStatement(" $propertyName .${it.name} ()" )
152
+ }
153
+
154
+ benchmarkMethods.forEach { method ->
155
+ builder.addStatement(" $propertyName .${method.name} ()" )
156
+ }
157
+
158
+ tearDownMethod?.let {
159
+ builder.addStatement(" $propertyName .${it.name} ()" )
160
+ }
161
+
162
+ return builder.build()
163
+ }
164
+
165
+ private fun updateAndroidDependencies (buildGradleFile : File , dependencies : List <Pair <String , String ?>>) {
166
+ if (buildGradleFile.exists()) {
167
+ val buildGradleContent = buildGradleFile.readText()
168
+
169
+ // Check if the android block exists
170
+ if (buildGradleContent.contains(" android {" )) {
171
+ // Check if the dependencies block inside android contains the dependencyString
172
+ val androidBlockStart = buildGradleContent.indexOf(" android {" )
173
+ val androidBlockEnd = buildGradleContent.lastIndexOf(" }" ) + 1
174
+ val androidBlockContent = buildGradleContent.substring(androidBlockStart, androidBlockEnd)
175
+
176
+ val newDependencies = dependencies.filterNot { (dependency, version) ->
177
+ val dependencyString = version?.let { """ $dependency :$version """ } ? : dependency
178
+ androidBlockContent.contains(dependencyString)
179
+ }
180
+ if (newDependencies.isNotEmpty()) {
181
+ // Add the dependencies inside the android { dependencies { ... } } block
182
+ val updatedAndroidBlockContent = if (androidBlockContent.contains(" dependencies {" )) {
183
+ val dependenciesBlockStart = androidBlockContent.indexOf(" dependencies {" )
184
+ val dependenciesBlockEnd = androidBlockContent.indexOf(" }" , dependenciesBlockStart) + 1
185
+ val dependenciesBlockContent =
186
+ androidBlockContent.substring(dependenciesBlockStart, dependenciesBlockEnd)
187
+
188
+ val newDependenciesString = newDependencies.joinToString(" \n " ) { (dependency, version) ->
189
+ version?.let { """ androidTestImplementation("$dependency :$version ")""" } ? : """ androidTestImplementation(files("$dependency "))"""
190
+ }
191
+ androidBlockContent.replace(
192
+ dependenciesBlockContent,
193
+ dependenciesBlockContent.replace(
194
+ " dependencies {" ,
195
+ " dependencies {\n $newDependenciesString "
196
+ )
197
+ )
198
+ } else {
199
+ val newDependenciesString = newDependencies.joinToString(" \n " ) { (dependency, version) ->
200
+ version?.let { """ androidTestImplementation("$dependency :$version ")""" } ? : """ androidTestImplementation(files("$dependency "))"""
201
+ }
202
+ androidBlockContent.replace(" {" , " {\n dependencies {\n $newDependenciesString \n }\n " )
203
+ }
204
+
205
+ // Replace the old android block with the updated one
206
+ val updatedBuildGradleContent =
207
+ buildGradleContent.replace(androidBlockContent, updatedAndroidBlockContent)
208
+ buildGradleFile.writeText(updatedBuildGradleContent)
209
+ }
210
+ }
211
+ }
70
212
}
0 commit comments