Skip to content

Commit 0e66ab4

Browse files
committed
Build variant checks + README
1 parent 0efd089 commit 0e66ab4

File tree

3 files changed

+133
-38
lines changed

3 files changed

+133
-38
lines changed

README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Android-Root-Coverage-Plugin
2+
**A Gradle plugin for easy generation of combined code coverage reports for Android projects with multiple modules.**
3+
Android Projects that make use of the Gradle build system by default come with easy methods to generate code coverage reports. Unfortently by default code coverage is generated seperatly per module, this means each modules takes into account it's own sources and tests, which is in terms of domain seperation fine. Howerver it is very common to find multi-module Android project where only one module has instrumented tests, or full-fledged UI tests using Espresso. This plugin comes in handy for those projects. It generates code coverage reports using Jacoco taking into account all the modules and tests at once.
4+
5+
- Supports both Android app and library modules (`com.android.application` & `com.android.library`).
6+
- Supports different build variants per module within the same report.
7+
- Supports custom filters.
8+
9+
# Download
10+
The plugin is not yet available on Maven Central, so currently it is needed to add the following repository to your top-level gradle file:
11+
12+
**Step 1:**
13+
```
14+
// Step 3: Apply the plugin to the top-level gradle file
15+
apply plugin: 'org.neotech.plugin.rootcoverage'
16+
17+
buildscript {
18+
repositories {
19+
// Step 1: add the repository
20+
// This plugin is not yet available on Maven Central, so currently it is needed to add the following repository:
21+
maven {
22+
url "http://dl.bintray.com/rolf-smit/maven"
23+
}
24+
}
25+
dependencies {
26+
// Step 2: add the dependency
27+
classpath 'org.neotech.plugin:android-root-coverage-plugin:0.0.1-dev'
28+
}
29+
}
30+
```
31+
32+
# How to use
33+
Currently only modules with the plugin type `com.android.application` or `com.android.library` are taken into account when generating the root code coverage report, besides this any module that does not have `testCoverageEnabled true` for the default build variant (`debug`) will be skipped::
34+
35+
You can add a module by enableing `testCoverageEnabled`:
36+
```
37+
android {
38+
buildTypes {
39+
debug {
40+
testCoverageEnabled true
41+
}
42+
}
43+
}
44+
```
45+
46+
The Android-Root-Coverage-Plugin generates a special Gradle task `:rootCodeCoverageReport` that when executed generates a Jacoco code coverage report. You can either run this task directly from Android Studio using the Gradle Tool Window (see: https://www.jetbrains.com/help/idea/jetgradle-tool-window.html) or from the terminal.
47+
48+
- **Gradle Tool Window:** You can find the task under: `Tasks > reporting > rootCodeCoverageReport`, double click to execute it.
49+
- **Terminal:** Execute the task using `gradlew rootCodeCoverageReport`.
50+
51+
# Configuration
52+
By default the plugin generates code coverage reports using the build variant `debug` for every module. However in some cases different build variants per module might be required, especially if there is no `debug` build variant available. In those cases you can configure custom build variants for specific modules:
53+
54+
```
55+
rootCoverage {
56+
// The default build variant for every module
57+
buildVariant "debug"
58+
// Overrides the default build variant for specific modules.
59+
buildVariantOverrides ":moduleA" : "debugFlavourA", ":moduleB": "debugFlavourA"
60+
// Class exclude patterns
61+
excludes ["**/library.a/**"]
62+
}
63+
```
64+
65+
66+
# Development
67+
Want to contribute? Great! Currently this plugin is in need of extensive testing. Besides this there is also a small whish list:
68+
69+
- Support for Java modules
70+
- Make use of the JacocoMerge task? To merge the `exec` en `ec` files?
71+
- Support for configuring the output type: html, xml etc. (Just like Jacoco)
72+
- Actual Plugin unit-tests (instead of a test project)
73+
74+
# Author note
75+
Many thanks to [Hans van Dam](https://github.com/hansvdam) for helping with testing and the inital idea.

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,6 @@ jacoco {
5757

5858
rootCoverage {
5959
buildVariant "debug"
60+
buildVariantOverrides ":library_a" : "debug", ":library_b": "debug"
6061
//excludes ["**/library.a/**"]
6162
}

plugin/src/main/kotlin/org/neotech/plugin/rootcoverage/RootCoveragePlugin.kt

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package org.neotech.plugin.rootcoverage
22

33
import com.android.build.gradle.AppExtension
4+
import com.android.build.gradle.BaseExtension
45
import com.android.build.gradle.LibraryExtension
56
import com.android.build.gradle.api.BaseVariant
67
import com.android.build.gradle.api.SourceKind
8+
import org.gradle.api.DomainObjectSet
79
import org.gradle.api.GradleException
810
import org.gradle.api.Plugin
911
import org.gradle.api.Project
12+
import org.gradle.api.internal.DefaultDomainObjectSet
1013
import org.gradle.testing.jacoco.plugins.JacocoPlugin
1114
import org.gradle.testing.jacoco.tasks.JacocoReport
1215

@@ -61,6 +64,17 @@ class RootCoveragePlugin: Plugin<Project> {
6164
return rootProjectExtension.buildVariantOverrides[project.path] ?: rootProjectExtension.buildVariant
6265
}
6366

67+
/**
68+
* Throws a GradleException if the given buildVariant is not found in the set. This method only
69+
* works correctly if used after the Gradle evaluation phase! Use it for example in Task.doFirst
70+
* or Task.doLast.
71+
*/
72+
private fun <T: BaseVariant> assertVariantExists(set: DomainObjectSet<T>, buildVariant: String, project: Project) {
73+
set.find {
74+
it.name.capitalize() == buildVariant.capitalize()
75+
} ?: throw GradleException("Build variant `$buildVariant` required for module `${project.name}` does not exist! Make sure to use a proper build variant configuration using rootCoverage.buildVariant and rootCoverage.buildVariantOverrides!")
76+
}
77+
6478
private fun createCoverageTaskForRoot(project: Project) {
6579
// Aggregates jacoco results from the app sub-project and bankingright sub-project and generates a report.
6680
// The report can be found at the root of the project in /build/reports/jacoco, so don't look in
@@ -74,6 +88,21 @@ class RootCoveragePlugin: Plugin<Project> {
7488
task.reports.csv.isEnabled = false
7589
task.reports.html.destination = project.file("${project.buildDir}/reports/jacoco")
7690

91+
// Add some run-time checks.
92+
task.doFirst {
93+
it.project.allprojects.forEach { subProject ->
94+
val extension = subProject.extensions.findByName("android")
95+
if(extension != null) {
96+
val buildVariant = getBuildVariantFor(subProject)
97+
when(extension) {
98+
is LibraryExtension -> assertVariantExists(extension.libraryVariants, buildVariant, subProject)
99+
is AppExtension -> assertVariantExists(extension.applicationVariants, buildVariant, subProject)
100+
}
101+
}
102+
}
103+
}
104+
105+
// Configure the root task with sub-tasks for the sub-projects.
77106
project.subprojects.forEach {
78107
it.afterEvaluate { subProject ->
79108
createCoverageTaskForSubProject(subProject, task)
@@ -94,43 +123,41 @@ class RootCoveragePlugin: Plugin<Project> {
94123
return
95124
}
96125

97-
/* if (subProject.plugins.withType(JacocoPlugin::class.java).isEmpty()) {
98-
subProject.logger.warn("Jacoco plugin not applied to project: '${subProject.name}'! RootCoveragePlugin automatically applied it but you should do this manually: ${subProject.buildFile}")
99-
subProject.plugins.apply(JacocoPlugin::class.java)
100-
}*/
101-
102-
// If the sub-project does not apply the jacoco plugin, skip that project.
103-
//if (subProject.plugins.withType(JacocoPlugin::class.java).isNotEmpty()) {
104-
105-
// Get the exact required build variant for the current subproject.
106-
val buildVariant = getBuildVariantFor(subProject)
107-
when(extension) {
108-
is LibraryExtension -> {
109-
extension.libraryVariants.all { variant ->
110-
if (variant.buildType.isTestCoverageEnabled && variant.name.capitalize() == buildVariant.capitalize()) {
111-
if (subProject.plugins.withType(JacocoPlugin::class.java).isEmpty()) {
112-
subProject.logger.warn("Jacoco plugin not applied to project: '${subProject.name}'! RootCoveragePlugin automatically applied it but you should do this manually: ${subProject.buildFile}")
113-
subProject.plugins.apply(JacocoPlugin::class.java)
114-
}
115-
val subTask = createTask(subProject, variant)
116-
addSubTaskDependencyToRootTask(task, subTask)
126+
// Get the exact required build variant for the current sub-project.
127+
val buildVariant = getBuildVariantFor(subProject)
128+
when(extension) {
129+
is LibraryExtension -> {
130+
131+
//assertVariantExists(extension.libraryVariants, buildVariant, subProject)
132+
133+
extension.libraryVariants.all { variant ->
134+
135+
if (variant.buildType.isTestCoverageEnabled && variant.name.capitalize() == buildVariant.capitalize()) {
136+
if (subProject.plugins.withType(JacocoPlugin::class.java).isEmpty()) {
137+
subProject.logger.warn("Jacoco plugin not applied to project: '${subProject.name}'! RootCoveragePlugin automatically applied it but you should do this manually: ${subProject.buildFile}")
138+
subProject.plugins.apply(JacocoPlugin::class.java)
117139
}
140+
val subTask = createTask(subProject, variant)
141+
addSubTaskDependencyToRootTask(task, subTask)
118142
}
119143
}
120-
is AppExtension -> {
121-
extension.applicationVariants.all { variant ->
122-
if (variant.buildType.isTestCoverageEnabled && variant.name.capitalize() == buildVariant.capitalize()) {
123-
if (subProject.plugins.withType(JacocoPlugin::class.java).isEmpty()) {
124-
subProject.logger.warn("Jacoco plugin not applied to project: '${subProject.name}'! RootCoveragePlugin automatically applied it but you should do this manually: ${subProject.buildFile}")
125-
subProject.plugins.apply(JacocoPlugin::class.java)
126-
}
127-
val subTask = createTask(subProject, variant)
128-
addSubTaskDependencyToRootTask(task, subTask)
144+
}
145+
is AppExtension -> {
146+
147+
//assertVariantExists(extension.libraryVariants, buildVariant, subProject)
148+
149+
extension.applicationVariants.all { variant ->
150+
if (variant.buildType.isTestCoverageEnabled && variant.name.capitalize() == buildVariant.capitalize()) {
151+
if (subProject.plugins.withType(JacocoPlugin::class.java).isEmpty()) {
152+
subProject.logger.warn("Jacoco plugin not applied to project: '${subProject.name}'! RootCoveragePlugin automatically applied it but you should do this manually: ${subProject.buildFile}")
153+
subProject.plugins.apply(JacocoPlugin::class.java)
129154
}
155+
val subTask = createTask(subProject, variant)
156+
addSubTaskDependencyToRootTask(task, subTask)
130157
}
131158
}
132159
}
133-
//}
160+
}
134161
}
135162

136163
private fun createTask(project: Project, variant: BaseVariant): RootCoverageModuleTask {
@@ -143,11 +170,6 @@ class RootCoveragePlugin: Plugin<Project> {
143170
task.description = "Generate unified Jacoco code codecoverage report"
144171
task.dependsOn("test${name}UnitTest", "connected${name}AndroidTest")
145172

146-
//task.reports.html.isEnabled = false
147-
//task.reports.xml.isEnabled = false
148-
//task.reports.csv.isEnabled = false
149-
//task.reports.html.destination = project.file("${project.buildDir}/reports/jacoco")
150-
151173
// Collect the class files based on the Java Compiler output
152174
val javaClassTrees = variant.javaCompiler.outputs.files.map { file ->
153175
project.fileTree(file, excludes = getFileFilter()).excludeNonClassFiles()
@@ -165,9 +187,6 @@ class RootCoveragePlugin: Plugin<Project> {
165187
task.classDirectories = project.files(javaClassTrees, kotlinClassTree)
166188
task.executionData = project.fileTree(project.buildDir, includes = listOf("jacoco/test${name}UnitTest.exec", "outputs/code-coverage/connected/*coverage.ec"))
167189

168-
//task.onlyIf { true }
169-
//println("Source files: ${task.classDirectories.files}")
170-
171190
return task
172191
}
173192

0 commit comments

Comments
 (0)