11package io.sentry.android.gradle.snapshot.metadata
22
3+ import com.android.build.gradle.BaseExtension
34import groovy.json.JsonOutput
5+ import java.util.zip.ZipInputStream
46import org.gradle.api.DefaultTask
57import org.gradle.api.Project
6- import org.gradle.api.file.DirectoryProperty
8+ import org.gradle.api.artifacts.component.ProjectComponentIdentifier
9+ import org.gradle.api.attributes.Attribute
10+ import org.gradle.api.file.ConfigurableFileCollection
711import org.gradle.api.file.RegularFileProperty
812import org.gradle.api.provider.Property
913import org.gradle.api.tasks.CacheableTask
1014import org.gradle.api.tasks.Input
11- import org.gradle.api.tasks.InputDirectory
15+ import org.gradle.api.tasks.InputFiles
1216import org.gradle.api.tasks.OutputFile
1317import org.gradle.api.tasks.PathSensitive
1418import org.gradle.api.tasks.PathSensitivity
@@ -24,37 +28,29 @@ abstract class ExportPreviewMetadataTask : DefaultTask() {
2428
2529 @get:Input abstract val includePrivatePreviews: Property <Boolean >
2630
27- @get:InputDirectory
31+ @get:InputFiles
2832 @get:PathSensitive(PathSensitivity .RELATIVE )
29- abstract val mergedClassesDir : DirectoryProperty
33+ abstract val inputClasspath : ConfigurableFileCollection
3034
3135 @get:OutputFile abstract val outputFile: RegularFileProperty
3236
3337 @TaskAction
3438 fun export () {
3539 val scanner = PreviewMethodScanner (includePrivatePreviews.get())
36- val rootDir = mergedClassesDir.get().asFile
3740
3841 // First pass: discover custom preview annotations.
3942 // Two iterations handle nested custom annotations where A references B but B was
4043 // processed after A in the first iteration.
4144 val customAnnotations = mutableMapOf<String , CustomPreviewAnnotation >()
4245 repeat(2 ) {
43- rootDir
44- .walk()
45- .filter { it.isFile && it.name.endsWith(" .class" ) }
46- .forEach { file -> scanner.findCustomAnnotations(file.readBytes(), customAnnotations) }
46+ forEachClassEntry { _, bytes -> scanner.findCustomAnnotations(bytes, customAnnotations) }
4747 }
4848
4949 // Second pass: find preview methods
5050 val previews = mutableListOf<PreviewMetadata >()
51- rootDir
52- .walk()
53- .filter { it.isFile && it.name.endsWith(" .class" ) }
54- .forEach { file ->
55- val relativePath = file.relativeTo(rootDir).path
56- scanClassFile(file.readBytes(), relativePath, scanner, customAnnotations, previews)
57- }
51+ forEachClassEntry { relativePath, bytes ->
52+ scanClassFile(bytes, relativePath, scanner, customAnnotations, previews)
53+ }
5854
5955 val export = PreviewMetadataExport (previews = previews)
6056 val json = JsonOutput .prettyPrint(JsonOutput .toJson(export.toMap()))
@@ -66,6 +62,29 @@ abstract class ExportPreviewMetadataTask : DefaultTask() {
6662 logger.lifecycle(" Exported ${previews.size} preview(s) to ${outFile.absolutePath} " )
6763 }
6864
65+ private fun forEachClassEntry (action : (relativePath: String , bytes: ByteArray ) -> Unit ) {
66+ inputClasspath.files.forEach { file ->
67+ if (file.isDirectory) {
68+ file
69+ .walkTopDown()
70+ .filter { it.isFile && it.extension == " class" }
71+ .forEach { classFile ->
72+ val relativePath = classFile.relativeTo(file).path
73+ action(relativePath, classFile.readBytes())
74+ }
75+ } else if (file.isFile && (file.name.endsWith(" .jar" ) || file.name.endsWith(" .zip" ))) {
76+ ZipInputStream (file.inputStream().buffered()).use { zis ->
77+ generateSequence { zis.nextEntry }
78+ .filter { ! it.isDirectory && it.name.endsWith(" .class" ) }
79+ .forEach { entry ->
80+ action(entry.name, zis.readBytes())
81+ zis.closeEntry()
82+ }
83+ }
84+ }
85+ }
86+ }
87+
6988 private fun scanClassFile (
7089 bytes : ByteArray ,
7190 relativePath : String ,
@@ -131,24 +150,43 @@ abstract class ExportPreviewMetadataTask : DefaultTask() {
131150 }
132151
133152 companion object {
153+ private const val ARTIFACT_TYPE = " artifactType"
134154
135155 fun register (
136156 project : Project ,
137157 extension : SentrySnapshotMetadataExtension ,
138- mergeTask : TaskProvider < MergeClassesTask > ,
158+ android : BaseExtension ,
139159 ): TaskProvider <ExportPreviewMetadataTask > {
140160 return project.tasks.register(
141161 " exportPreviewMetadata" ,
142162 ExportPreviewMetadataTask ::class .java,
143163 ) { task ->
144164 task.includePrivatePreviews.set(extension.includePrivatePreviews)
145- task.mergedClassesDir.set(mergeTask.flatMap { it.outputDir })
165+
166+ // Local compiled classes
167+ // TODO Debug is hard coded here. we should allow different variants
168+ task.inputClasspath.from(project.tasks.named(" compileDebugKotlin" ).map { it.outputs.files })
169+
170+ // Dependency project classes
171+ val debugClasspath = project.configurations.getByName(" debugRuntimeClasspath" )
172+ task.inputClasspath.from(
173+ debugClasspath.incoming
174+ .artifactView { view ->
175+ view.componentFilter { id -> id is ProjectComponentIdentifier }
176+ view.attributes { attrs ->
177+ attrs.attribute(Attribute .of(ARTIFACT_TYPE , String ::class .java), " android-classes" )
178+ }
179+ }
180+ .files
181+ )
146182
147183 task.outputFile.set(
148184 project.layout.buildDirectory.file(
149185 " sentry-snapshots/preview-metadata/preview-metadata.json"
150186 )
151187 )
188+
189+ task.dependsOn(" compileDebugKotlin" )
152190 }
153191 }
154192 }
0 commit comments