ClirrPlugin.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  *     https://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.clirr
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.artifacts.Configuration
027 import org.gradle.api.file.FileCollection
028 import org.gradle.api.invocation.Gradle
029 import org.gradle.api.plugins.ReportingBasePlugin
030 import org.gradle.api.tasks.TaskProvider
031 import org.kordamp.gradle.PluginUtils
032 import org.kordamp.gradle.plugin.AbstractKordampPlugin
033 import org.kordamp.gradle.plugin.base.BasePlugin
034 import org.kordamp.gradle.plugin.base.ProjectConfigurationExtension
035 import org.kordamp.gradle.plugin.base.plugins.Clirr
036 import org.kordamp.gradle.plugin.clirr.tasks.AggregateClirrReportTask
037 import org.kordamp.gradle.plugin.clirr.tasks.ClirrTask
038 
039 import static org.kordamp.gradle.PluginUtils.resolveEffectiveConfig
040 import static org.kordamp.gradle.plugin.base.BasePlugin.isRootProject
041 
042 /**
043  *
044  @author Andres Almiray
045  @since 0.12.0
046  */
047 @CompileStatic
048 class ClirrPlugin extends AbstractKordampPlugin {
049     Project project
050 
051     void apply(Project project) {
052         this.project = project
053 
054         if (isRootProject(project)) {
055             project.childProjects.values().each {
056                 configureProject(it)
057             }
058         }
059         configureProject(project)
060     }
061 
062     static void applyIfMissing(Project project) {
063         if (!project.plugins.findPlugin(ClirrPlugin)) {
064             project.pluginManager.apply(ClirrPlugin)
065         }
066     }
067 
068     private void configureProject(Project project) {
069         if (hasBeenVisited(project)) {
070             return
071         }
072         setVisited(project, true)
073 
074         BasePlugin.applyIfMissing(project)
075         project.pluginManager.apply(ReportingBasePlugin)
076 
077         project.afterEvaluate {
078             ProjectConfigurationExtension effectiveConfig = resolveEffectiveConfig(project)
079             setEnabled(effectiveConfig.clirr.enabled)
080 
081             if (!enabled) {
082                 return
083             }
084 
085             TaskProvider<ClirrTask> clirrTask = configureClirrTask(project)
086             if (clirrTask) {
087                 effectiveConfig.clirr.projects() << project
088                 effectiveConfig.clirr.clirrTasks() << clirrTask
089             }
090         }
091 
092         if (isRootProject(project&& !project.childProjects.isEmpty()) {
093             TaskProvider<AggregateClirrReportTask> aggregateClirrReportTask = project.tasks.register('aggregateClirr', AggregateClirrReportTask,
094                 new Action<AggregateClirrReportTask>() {
095                     @Override
096                     @CompileDynamic
097                     void execute(AggregateClirrReportTask t) {
098                         t.enabled = false
099                         t.group = 'Verification'
100                         t.description = 'Aggregates binary compatibility reports.'
101                         t.reportFile = project.file("${project.reporting.baseDir.path}/clirr/aggregate-compatibility-report.html")
102                     }
103                 })
104 
105             project.gradle.addBuildListener(new BuildAdapter() {
106                 @Override
107                 void projectsEvaluated(Gradle gradle) {
108                     configureAggregateClirrTask(project, aggregateClirrReportTask)
109                 }
110             })
111         }
112     }
113 
114     private TaskProvider<ClirrTask> configureClirrTask(Project project) {
115         ProjectConfigurationExtension effectiveConfig = resolveEffectiveConfig(project)
116 
117         FileCollection newfiles = null
118         if (PluginUtils.isAndroidProject(project)) {
119             newfiles = project.tasks.findByName('assemble')?.outputs?.files
120         else {
121             newfiles = project.tasks.findByName('jar')?.outputs?.files
122         }
123 
124         if (!newfiles || !effectiveConfig.clirr.enabled) {
125             return null
126         }
127 
128         TaskProvider<ClirrTask> clirrTask = project.tasks.register('clirr', ClirrTask,
129             new Action<ClirrTask>() {
130                 @Override
131                 @CompileDynamic
132                 void execute(ClirrTask t) {
133                     t.group = 'Verification'
134                     t.description = 'Determines the binary compatibility of the current codebase against a previous release.'
135                     t.newFiles = newfiles
136                     t.newClasspath = project.configurations['compile'] // TODO: implementation
137                     t.xmlReport = project.file("${project.reporting.baseDir.path}/clirr/compatibility-report.xml")
138                     t.htmlReport = project.file("${project.reporting.baseDir.path}/clirr/compatibility-report.html")
139                     t.enabled = effectiveConfig.clirr.enabled
140                 }
141             })
142 
143         String baseline = effectiveConfig.clirr.baseline
144         if (!baseline) {
145             // attempt resolving baseline using current version
146             Version current = Version.of(String.valueOf(project.version))
147             if (current == Version.ZERO) {
148                 // can't run clirr
149                 project.logger.info("{}: version '{}' could not be parsed as semver", project.name, project.version)
150                 project.logger.info('{}: please set clirr.baseline explicitly or disable clirr', project.name)
151                 return null
152             }
153 
154             Versions versions = Versions.of(current, effectiveConfig.clirr.semver)
155             if (versions.previous == Version.ZERO) {
156                 project.logger.info("{}: could not determine previous version for '{}' [semver compatibility={}]", project.name, current, effectiveConfig.clirr.semver)
157                 project.logger.info('{}: please set clirr.baseline explicitly or disable clirr', project.name)
158                 return null
159             }
160 
161             baseline = [project.group ?: '', project.name, versions.previous].join(':')
162         }
163 
164         project.logger.info('{}: baseline has been set to {}', project.name, baseline)
165 
166         // temporary change the group of the current project  otherwise
167         // the latest version will always override the baseline
168         String projectGroup = project.group
169         try {
170             project.group = projectGroup + '.clirr'
171             Configuration detached = project.configurations.detachedConfiguration(
172                 project.dependencies.create(baseline)
173             )
174             detached.transitive = true
175             detached.resolve()
176             clirrTask.configure(new Action<ClirrTask>() {
177                 @Override
178                 void execute(ClirrTask t) {
179                     t.baseFiles = detached
180                 }
181             })
182         finally {
183             project.group = projectGroup
184         }
185 
186         clirrTask
187     }
188 
189     @CompileDynamic
190     private void configureAggregateClirrTask(Project project, TaskProvider<AggregateClirrReportTask> aggregateClirrReportTask) {
191         ProjectConfigurationExtension effectiveConfig = resolveEffectiveConfig(project)
192         if (!effectiveConfig.clirr.enabled) {
193             return
194         }
195 
196         Set<TaskProvider<? extends Task>> clirrTasks = new LinkedHashSet<>(effectiveConfig.clirr.clirrTasks())
197 
198         project.childProjects.values().each {
199             Clirr e = resolveEffectiveConfig(it).clirr
200             if (e.enabled) {
201                 clirrTasks.addAll(e.clirrTasks())
202             }
203         }
204 
205         aggregateClirrReportTask.configure(new Action<AggregateClirrReportTask>() {
206             @Override
207             void execute(AggregateClirrReportTask t) {
208                 t.dependsOn clirrTasks
209                 t.enabled = effectiveConfig.clirr.enabled
210                 t.reports = project.files(clirrTasks*.get()*.xmlReport)
211             }
212         })
213     }
214 }