JacocoPlugin.groovy
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2018-2019 Andres Almiray.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     http://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package org.kordamp.gradle.plugin.jacoco
019 
020 import groovy.transform.CompileDynamic
021 import groovy.transform.CompileStatic
022 import org.gradle.BuildAdapter
023 import org.gradle.api.Action
024 import org.gradle.api.Project
025 import org.gradle.api.Task
026 import org.gradle.api.file.FileCollection
027 import org.gradle.api.invocation.Gradle
028 import org.gradle.api.plugins.JavaBasePlugin
029 import org.gradle.api.tasks.TaskProvider
030 import org.gradle.api.tasks.testing.Test
031 import org.gradle.testing.jacoco.tasks.JacocoMerge
032 import org.gradle.testing.jacoco.tasks.JacocoReport
033 import org.gradle.testing.jacoco.tasks.JacocoReportsContainer
034 import org.kordamp.gradle.PluginUtils
035 import org.kordamp.gradle.StringUtils
036 import org.kordamp.gradle.plugin.AbstractKordampPlugin
037 import org.kordamp.gradle.plugin.base.BasePlugin
038 import org.kordamp.gradle.plugin.base.ProjectConfigurationExtension
039 import org.kordamp.gradle.plugin.base.plugins.Jacoco
040 
041 import static org.kordamp.gradle.PluginUtils.resolveEffectiveConfig
042 import static org.kordamp.gradle.plugin.base.BasePlugin.isRootProject
043 
044 /**
045  *
046  @author Andres Almiray
047  @since 0.2.0
048  */
049 @CompileStatic
050 class JacocoPlugin extends AbstractKordampPlugin {
051     Project project
052 
053     void apply(Project project) {
054         this.project = project
055 
056         if (isRootProject(project)) {
057             project.childProjects.values().each {
058                 configureProject(it)
059             }
060         }
061         configureProject(project)
062     }
063 
064     static void applyIfMissing(Project project) {
065         if (!project.plugins.findPlugin(JacocoPlugin)) {
066             project.plugins.apply(JacocoPlugin)
067         }
068     }
069 
070     private void configureProject(Project project) {
071         if (hasBeenVisited(project)) {
072             return
073         }
074         setVisited(project, true)
075 
076         BasePlugin.applyIfMissing(project)
077         project.plugins.apply(org.gradle.testing.jacoco.plugins.JacocoPlugin)
078 
079         project.afterEvaluate {
080             ProjectConfigurationExtension effectiveConfig = resolveEffectiveConfig(project)
081             setEnabled(effectiveConfig.jacoco.enabled)
082 
083             if (!enabled) {
084                 return
085             }
086 
087             project.plugins.withType(JavaBasePlugin) {
088                 project.tasks.withType(Test) { Test testTask ->
089                     if (!testTask.enabled) {
090                         return
091                     }
092                     JacocoReport reportTask = configureJacocoReportTask(project, testTask)
093                     if (reportTask.enabled) {
094                         effectiveConfig.jacoco.testTasks() << testTask
095                         effectiveConfig.jacoco.reportTasks() << reportTask
096                         effectiveConfig.jacoco.projects() << project
097                     }
098                 }
099             }
100         }
101 
102         if (isRootProject(project&& !project.childProjects.isEmpty()) {
103             TaskProvider<JacocoMerge> jacocoRootMerge = project.tasks.register('jacocoRootMerge', JacocoMerge,
104                 new Action<JacocoMerge>() {
105                     @Override
106                     void execute(JacocoMerge t) {
107                         t.enabled = false
108                         t.group = 'Reporting'
109                         t.description = 'Aggregate Jacoco coverage reports.'
110                     }
111                 })
112 
113             TaskProvider<JacocoReport> jacocoRootReport = project.tasks.register('jacocoRootReport', JacocoReport,
114                 new Action<JacocoReport>() {
115                     @Override
116                     void execute(JacocoReport t) {
117                         t.enabled = false
118                         t.dependsOn jacocoRootMerge
119                         t.group = 'Reporting'
120                         t.description = 'Generate aggregate Jacoco coverage report.'
121 
122                         t.reports(new Action<JacocoReportsContainer>() {
123                             @Override
124                             void execute(JacocoReportsContainer reports) {
125                                reports.html.enabled = true
126                                reports.xml.enabled = true
127                             }
128                         })
129                     }
130                 })
131 
132             project.gradle.addBuildListener(new BuildAdapter() {
133                 @Override
134                 void projectsEvaluated(Gradle gradle) {
135                     applyJacocoMerge(project, jacocoRootMerge, jacocoRootReport)
136                 }
137             })
138         }
139     }
140 
141     static String resolveJacocoReportTaskName(String name) {
142         return 'jacoco' + StringUtils.capitalize(name'Report'
143     }
144 
145     @CompileDynamic
146     private JacocoReport configureJacocoReportTask(Project project, Test testTask) {
147         ProjectConfigurationExtension effectiveConfig = resolveEffectiveConfig(project)
148 
149         String taskName = resolveJacocoReportTaskName(testTask.name)
150 
151         Task jacocoReportTask = project.tasks.findByName(taskName)
152 
153         if (!jacocoReportTask) {
154             jacocoReportTask = project.tasks.create(taskName, JacocoReport) {
155                 dependsOn testTask
156                 group 'Verification'
157                 description "Generates code coverage report for the ${testTask.name} task."
158 
159                 jacocoClasspath = project.configurations.jacocoAnt
160 
161                 additionalSourceDirs.from project.files(resolveSourceDirs(effectiveConfig, project))
162                 sourceDirectories.from project.files(resolveSourceDirs(effectiveConfig, project))
163                 classDirectories.from project.files(resolveClassDirs(effectiveConfig, project))
164                 executionData testTask
165 
166                 reports {
167                     html.destination = project.file("${project.reporting.baseDir.path}/jacoco/${testTask.name}/html")
168                     xml.destination = project.file("${project.reporting.baseDir.path}/jacoco/${testTask.name}/jacocoTestReport.xml")
169                 }
170             }
171         }
172 
173         jacocoReportTask.configure {
174             enabled = effectiveConfig.jacoco.enabled
175             reports {
176                 xml.enabled = true
177                 csv.enabled = false
178                 html.enabled = true
179             }
180         }
181 
182         jacocoReportTask
183     }
184 
185     private FileCollection resolveSourceDirs(ProjectConfigurationExtension effectiveConfig, Project project) {
186         project.files(PluginUtils.resolveMainSourceDirs(project))
187             .from(effectiveConfig.jacoco.additionalSourceDirs.files.flatten().unique())
188     }
189 
190     @CompileDynamic
191     private FileCollection resolveClassDirs(ProjectConfigurationExtension effectiveConfig, Project project) {
192         project.files(PluginUtils.resolveSourceSets(project).main.output.classesDirs*.path.flatten().unique())
193             .from(effectiveConfig.jacoco.additionalClassDirs.files.flatten().unique())
194     }
195 
196     private FileCollection resolveSourceDirs(ProjectConfigurationExtension effectiveConfig, Collection<Project> projects) {
197         project.files(projects.collect resolveSourceDirs(effectiveConfig, it) }.flatten().unique())
198     }
199 
200     private FileCollection resolveClassDirs(ProjectConfigurationExtension effectiveConfig, Collection<Project> projects) {
201         project.files(projects.collect resolveClassDirs(effectiveConfig, it) }.flatten().unique())
202     }
203 
204     private void applyJacocoMerge(Project project, TaskProvider<JacocoMerge> jacocoRootMerge, TaskProvider<JacocoReport> jacocoRootReport) {
205         ProjectConfigurationExtension effectiveConfig = resolveEffectiveConfig(project)
206         if (!effectiveConfig.jacoco.enabled) {
207             return
208         }
209 
210         Set<Project> projects = new LinkedHashSet<>(effectiveConfig.jacoco.projects())
211         Set<Test> testTasks = new LinkedHashSet<>(effectiveConfig.jacoco.testTasks())
212         Set<JacocoReport> reportTasks = new LinkedHashSet<>(effectiveConfig.jacoco.reportTasks())
213 
214         project.childProjects.values().each {
215             Jacoco e = resolveEffectiveConfig(it).jacoco
216             if (e.enabled) {
217                 projects.addAll(e.projects())
218                 testTasks.addAll(e.testTasks())
219                 reportTasks.addAll(e.reportTasks())
220             }
221         }
222 
223         jacocoRootMerge.configure(new Action<JacocoMerge>() {
224             @Override
225             @CompileDynamic
226             void execute(JacocoMerge t) {
227                 t.dependsOn testTasks + reportTasks
228                 t.enabled = effectiveConfig.jacoco.enabled
229                 t.executionData = project.files(reportTasks.executionData.files.flatten())
230                 t.destinationFile = effectiveConfig.jacoco.mergeExecFile
231             }
232         })
233 
234         jacocoRootReport.configure(new Action<JacocoReport>() {
235             @Override
236             void execute(JacocoReport t) {
237                 t.enabled = effectiveConfig.jacoco.enabled
238 
239                 t.additionalSourceDirs.from project.files(resolveSourceDirs(effectiveConfig, projects))
240                 t.sourceDirectories.from project.files(resolveSourceDirs(effectiveConfig, projects))
241                 t.classDirectories.from project.files(resolveClassDirs(effectiveConfig, projects))
242                 t.executionData project.files(jacocoRootMerge.get().destinationFile)
243 
244                 t.reports(new Action<JacocoReportsContainer>() {
245                     @Override
246                     void execute(JacocoReportsContainer reports) {
247                         reports.html.setDestination(effectiveConfig.jacoco.mergeReportHtmlFile)
248                         reports.xml.setDestination(effectiveConfig.jacoco.mergeReportXmlFile)
249                     }
250                 })
251             }
252         })
253     }
254 }