Skip to content

Commit 126caad

Browse files
committed
fix: init script timing and manifest interpolation bugs
- Use allprojects+afterEvaluate instead of projectsEvaluated (too late for source sets) - Use configureEach for lazily-registered compile tasks - Fix manifest template to use double-quoted string for ${applicationId} - Remove redundant BuildConfig guard from CoverageHelper - Set jacocoCli transitive=false - Remove ANDROID_COVERAGE_GUIDE.md from repo
1 parent 4b99f17 commit 126caad

3 files changed

Lines changed: 65 additions & 228 deletions

File tree

ANDROID_COVERAGE_GUIDE.md

Lines changed: 0 additions & 161 deletions
This file was deleted.

packages/coverage-android/android/src/main/kotlin/com/harness/coverage/CoverageHelper.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ object CoverageHelper {
1414
private var cachedAgent: Any? = null
1515

1616
fun setup(context: Context) {
17-
if (!BuildConfig.COVERAGE_ENABLED) return
18-
1917
val agent = try {
2018
Class.forName("org.jacoco.agent.rt.RT")
2119
.getMethod("getAgent")

packages/coverage-android/scripts/harness-coverage-init.gradle

Lines changed: 65 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,27 @@ logger.lifecycle("[HarnessCoverage] Will instrument modules: ${targetModules.joi
4949
def scriptDir = buildscript.sourceFile?.parentFile
5050
def packageDir = scriptDir?.parentFile
5151

52-
gradle.projectsEvaluated {
53-
def jacocoVersion = '0.8.12'
52+
def jacocoVersion = '0.8.12'
5453

55-
rootProject.allprojects { project ->
56-
if (!targetModules.contains(project.path)) return
54+
// Use allprojects + afterEvaluate so that source sets, dependencies, and
55+
// BuildConfig fields are registered BEFORE the compile tasks finalize their
56+
// inputs. gradle.projectsEvaluated is too late for source-set changes.
57+
gradle.allprojects { project ->
58+
if (!targetModules.contains(project.path)) return
5759

60+
project.afterEvaluate {
5861
logger.lifecycle("[HarnessCoverage] Configuring ${project.path}")
5962

63+
def androidExt = project.extensions.findByName('android')
64+
if (!androidExt) {
65+
logger.warn("[HarnessCoverage] ${project.path} has no android extension, skipping")
66+
return
67+
}
68+
6069
// --- Dependencies ---
6170
project.configurations.maybeCreate('jacocoAnt')
62-
project.configurations.maybeCreate('jacocoCli')
71+
def jacocoCliConf = project.configurations.maybeCreate('jacocoCli')
72+
jacocoCliConf.transitive = false
6373

6474
project.dependencies {
6575
implementation "org.jacoco:org.jacoco.agent:${jacocoVersion}:runtime"
@@ -68,15 +78,13 @@ gradle.projectsEvaluated {
6878
}
6979

7080
// --- BuildConfig field ---
71-
def androidExt = project.extensions.findByName('android')
72-
if (androidExt) {
73-
androidExt.defaultConfig {
74-
buildConfigField "boolean", "COVERAGE_ENABLED", "true"
75-
}
81+
def namespace = androidExt.namespace
82+
androidExt.defaultConfig {
83+
buildConfigField "boolean", "COVERAGE_ENABLED", "true"
7684
}
7785

7886
// --- Inject coverage runtime sources ---
79-
if (androidExt && packageDir) {
87+
if (packageDir) {
8088
def coverageSrcDir = new File(packageDir, 'android/src/main/kotlin')
8189
def coverageResources = new File(packageDir, 'android/src/main/resources')
8290

@@ -88,86 +96,78 @@ gradle.projectsEvaluated {
8896
}
8997

9098
// Generate a debug-overlay manifest that merges the ContentProvider
91-
// into the app's manifest (avoids replacing the main manifest)
9299
def generatedManifestDir = project.file("${project.buildDir}/generated/harness-coverage")
93100
generatedManifestDir.mkdirs()
94101
def generatedManifest = new File(generatedManifestDir, 'AndroidManifest.xml')
95-
generatedManifest.text = '''\
102+
generatedManifest.text = """\
96103
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
97104
<application>
98105
<provider
99106
android:name="com.harness.coverage.CoverageInitProvider"
100-
android:authorities="${applicationId}.harness_coverage"
107+
android:authorities="\${applicationId}.harness_coverage"
101108
android:exported="false"
102109
android:initOrder="999" />
103110
</application>
104111
</manifest>
105-
'''
112+
"""
106113
androidExt.sourceSets.debug.manifest.srcFile generatedManifest
107114
}
108115

109116
// --- Offline instrumentation + stash build artifacts ---
110-
project.afterEvaluate {
111-
def coverageDir = project.file("${project.buildDir}/harness-coverage")
117+
def coverageDir = project.file("${project.buildDir}/harness-coverage")
112118

113-
// Reusable closure that saves originals, instruments, and stashes jacococli
114-
def instrumentClasses = { File classesDir, String label ->
115-
if (!classesDir.exists()) return
119+
def instrumentClasses = { File classesDir, String label ->
120+
if (!classesDir.exists()) return
116121

117-
coverageDir.mkdirs()
122+
coverageDir.mkdirs()
118123

119-
// Save original classes for report generation
120-
def origDir = new File(coverageDir, "original-classes-${label}")
121-
if (origDir.exists()) origDir.deleteDir()
122-
ant.copy(todir: origDir) {
123-
fileset(dir: classesDir)
124-
}
125-
126-
// Stash jacococli.jar for use at report time
127-
def destJar = new File(coverageDir, 'jacococli.jar')
128-
if (!destJar.exists()) {
129-
def cliJar = project.configurations.jacocoCli.singleFile
130-
ant.copy(file: cliJar, tofile: destJar)
131-
logger.lifecycle("[HarnessCoverage] Stashed ${destJar}")
132-
}
124+
// Save original classes for report generation
125+
def origDir = new File(coverageDir, "original-classes-${label}")
126+
if (origDir.exists()) origDir.deleteDir()
127+
ant.copy(todir: origDir) {
128+
fileset(dir: classesDir)
129+
}
133130

134-
// Instrument to temp dir, then replace
135-
def instrumentedDir = project.file("${project.buildDir}/tmp/jacoco-instrumented-${label}")
136-
if (instrumentedDir.exists()) instrumentedDir.deleteDir()
137-
instrumentedDir.mkdirs()
138-
139-
ant.taskdef(
140-
name: 'instrument',
141-
classname: 'org.jacoco.ant.InstrumentTask',
142-
classpath: project.configurations.jacocoAnt.asPath
143-
)
144-
ant.instrument(destdir: instrumentedDir) {
145-
fileset(dir: classesDir, includes: '**/*.class')
146-
}
131+
// Stash jacococli.jar for use at report time
132+
def destJar = new File(coverageDir, 'jacococli.jar')
133+
if (!destJar.exists()) {
134+
def cliJar = project.configurations.jacocoCli.singleFile
135+
ant.copy(file: cliJar, tofile: destJar)
136+
logger.lifecycle("[HarnessCoverage] Stashed ${destJar}")
137+
}
147138

148-
ant.copy(todir: classesDir, overwrite: true) {
149-
fileset(dir: instrumentedDir)
150-
}
139+
// Instrument to temp dir, then replace originals
140+
def instrumentedDir = project.file("${project.buildDir}/tmp/jacoco-instrumented-${label}")
141+
if (instrumentedDir.exists()) instrumentedDir.deleteDir()
142+
instrumentedDir.mkdirs()
143+
144+
ant.taskdef(
145+
name: 'instrument',
146+
classname: 'org.jacoco.ant.InstrumentTask',
147+
classpath: project.configurations.jacocoAnt.asPath
148+
)
149+
ant.instrument(destdir: instrumentedDir) {
150+
fileset(dir: classesDir, includes: '**/*.class')
151+
}
151152

152-
logger.lifecycle("[HarnessCoverage] Instrumented ${label} classes in ${classesDir}")
153+
ant.copy(todir: classesDir, overwrite: true) {
154+
fileset(dir: instrumentedDir)
153155
}
154156

155-
// Kotlin classes
156-
def kotlinTask = project.tasks.findByName('compileDebugKotlin')
157-
if (kotlinTask) {
157+
logger.lifecycle("[HarnessCoverage] Instrumented ${label} classes in ${classesDir}")
158+
}
159+
160+
// Hook into compile tasks. Use configureEach with name matching so it
161+
// works with lazily-registered tasks (AGP + RN Gradle plugin register
162+
// compile tasks after project evaluation, during task graph resolution).
163+
project.tasks.configureEach { task ->
164+
if (task.name == 'compileDebugKotlin') {
158165
def kotlinClasses = project.file("${project.buildDir}/tmp/kotlin-classes/debug")
159-
kotlinTask.doLast { instrumentClasses(kotlinClasses, 'kotlin') }
166+
task.doLast { instrumentClasses(kotlinClasses, 'kotlin') }
160167
}
161-
162-
// Java classes
163-
def javaTask = project.tasks.findByName('compileDebugJavaWithJavac')
164-
if (javaTask) {
168+
if (task.name == 'compileDebugJavaWithJavac') {
165169
def javaClasses = project.file("${project.buildDir}/intermediates/javac/debug/classes")
166-
javaTask.doLast { instrumentClasses(javaClasses, 'java') }
167-
}
168-
169-
if (!kotlinTask && !javaTask) {
170-
logger.warn("[HarnessCoverage] No compile tasks found in ${project.path}, skipping instrumentation")
170+
task.doLast { instrumentClasses(javaClasses, 'java') }
171171
}
172172
}
173173
}

0 commit comments

Comments
 (0)