blob: bebfe04ed3e3e94e9ca06cf1dfd1ce427c359697 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import groovy.text.markup.MarkupTemplateEngine
import groovy.text.markup.TemplateConfiguration
import java.lang.reflect.Modifier
buildscript {
// this block should not be necessary, but for some reason it fails without!
repositories {
jcenter()
}
dependencies {
classpath 'me.champeau.gradle:japicmp-gradle-plugin:0.1.1'
}
}
task checkBinaryCompatibility {
description = 'Generates binary compatibility reports'
}
check.dependsOn(checkBinaryCompatibility)
def excludeModules = ['performance', 'tests-vm8']
//def excludeModules = []
Set projectsToCheck = allprojects.findAll{ !(it.name in excludeModules) }
if (JavaVersion.current().isJava7Compatible()) {
allprojects {
if (project in projectsToCheck) {
apply plugin: 'me.champeau.gradle.japicmp'
}
}
def referenceMinorVersion = '2.4.15'
def prettyPrint = { classOrMethod ->
"${Modifier.toString(classOrMethod.get()?.modifiers)} ${classOrMethod.get()?.longName}"
}
def reportGenerator = { model ->
outputProcessor {
def skipClass = { c ->
c.fullyQualifiedName =~ /\$[0-9]+$/ || // skip AIC
c.fullyQualifiedName.startsWith('org.codehaus.groovy.runtime.dgm$') ||
c.fullyQualifiedName.contains('_closure')
}
def skipMethod = { c, m -> skipClass(c) || m.name =~ /access\$[0-9]+/ }
def violations = [:].withDefault {
// key = class name
// value = map of violations
[:].withDefault { [] }
}
removedConstructor { c, m ->
if (!skipMethod(c, m)) {
def level = Modifier.isPrivate(m.oldConstructor.get()?.modifiers) ? 'info' : 'error'
violations[c.fullyQualifiedName][level] << "Constructor ${prettyPrint(m.oldConstructor)} has been removed"
}
}
removedMethod { c, m ->
if (!skipMethod(c, m)) {
def level = m.name.startsWith('super$') ? 'warning' : 'error'
violations[c.fullyQualifiedName][level] << "Method ${m.name} has been removed"
}
}
removedClass { c ->
if (!skipClass(c)) {
violations[c.fullyQualifiedName].error << "Class has been removed"
}
}
modifiedMethod { c, m ->
if (!skipMethod(c, m)) {
violations[c.fullyQualifiedName].warning << """<p>Method ${m.name} has been modified</p>
<p>From <pre>${prettyPrint(m.oldMethod)}</pre> to <pre>${prettyPrint(m.newMethod)}</pre></p>"""
}
}
modifiedConstructor { c, m ->
if (!skipMethod(c, m)) {
violations[c.fullyQualifiedName].warning << """<p>Constructor ${m.name} has been modified</p>
<p>From <pre>${prettyPrint(m.oldConstructor)}</pre> to <pre>${prettyPrint(m.newConstructor)}</pre></p>"""
}
}
modifiedClass { c ->
if (!skipClass(c)) {
def level = c.binaryCompatible?'info':'error'
def message = "Class ${c.fullyQualifiedName} has been modified"
violations[c.fullyQualifiedName][level] << message
}
}
newClass { c ->
if (!skipClass(c)) {
violations[c.fullyQualifiedName].info << "Class has been added"
}
}
newMethod { c, m ->
if (!skipMethod(c, m)) {
violations[c.fullyQualifiedName].info << """<p>Method ${m.name} has been added</p>
<p>Signature: <pre>${prettyPrint(m.newMethod)}</pre></p>"""
}
}
after {
model.violations = violations
}
}
}
// using a global engine for all tasks in order to increase performance
def configDir = file("$rootProject.projectDir/config/binarycompatibility")
def templateFile = 'binarycompat-report.groovy'
def templateConfiguration = new TemplateConfiguration()
templateConfiguration.with {
autoIndent = true
autoNewLine = true
}
def engine = new MarkupTemplateEngine(this.class.classLoader, configDir, templateConfiguration)
task japicmpAll(type: me.champeau.gradle.ArtifactJapicmpTask) {
dependsOn jarAll
baseline = "org.codehaus.groovy:groovy-all:${referenceMinorVersion}@jar"
to = jarAll.archivePath
accessModifier = 'protected'
onlyModified = true
failOnModification = false
txtOutputFile = file("$buildDir/reports/japi.txt")
doFirst {
classpath = projectsToCheck.configurations.japicmp.files.flatten()
}
def htmlReportFile = file("${buildDir}/reports/binary-compat-${project.name}-all.html")
inputs.file file("$configDir/$templateFile")
inputs.file templateFile
outputs.file htmlReportFile
def model = [title : "Binary compatibility report for ${project.name}",
project : project,
baseline: baseline,
archive : to.name]
outputProcessor(reportGenerator.curry(model))
doLast {
htmlReportFile.withWriter('utf-8') { wrt ->
engine.createTemplateByPath(templateFile).make(model).writeTo(wrt)
}
}
}
allprojects {
if (project in projectsToCheck) {
dependencies {
japicmp files(rootProject.jar.archivePath)
}
task japicmp(type: me.champeau.gradle.ArtifactJapicmpTask) {
dependsOn replaceJarWithJarJar
baseline = "org.codehaus.groovy:${project.name}:${referenceMinorVersion}@jar"
to = jar.archivePath
accessModifier = 'protected'
onlyModified = true
failOnModification = false
txtOutputFile = file("$buildDir/reports/japi.txt")
def htmlReportFile = file("${buildDir}/reports/binary-compat-${project.name}.html")
inputs.file file("$configDir/$templateFile")
inputs.file templateFile
outputs.file htmlReportFile
def model = [title : "Binary compatibility report for ${project.name}",
project : project,
baseline: baseline,
archive : to.name]
outputProcessor(reportGenerator.curry(model))
doLast {
htmlReportFile.withWriter('utf-8') { wrt ->
engine.createTemplateByPath(templateFile).make(model).writeTo(wrt)
}
}
}
}
}
allprojects {
tasks.withType(me.champeau.gradle.ArtifactJapicmpTask) { task ->
checkBinaryCompatibility.dependsOn(task)
}
}
}