diff --git a/build.gradle b/build.gradle index 1b798a012..bccb44c47 100644 --- a/build.gradle +++ b/build.gradle @@ -66,6 +66,7 @@ allprojects { } apply plugin: 'java-library' +apply plugin: 'com.github.johnrengelman.shadow' /* ******************** dependencies ******************** */ @@ -128,63 +129,70 @@ dependencies { /* ******************** jars ******************** */ -apply plugin: 'biz.aQute.bnd.builder' - -jar { - bnd('Automatic-Module-Name': project.moduleName, - 'Bundle-Name': project.name, - 'Bundle-SymbolicName': project.moduleName, - 'Bundle-Description': project.description, - 'Bundle-Vendor': 'HiveMQ and the HiveMQ Community', - 'Bundle-License': project.licenseShortName + ';description="' + project.licenseReadableName + '";link="' + project.licenseUrl + '"', - 'Bundle-DocURL': project.docUrl, - 'Bundle-SCM': 'url="' + project.githubUrl + '";connection="' + project.scmConnection + '";developerConnection="' + project.scmDeveloperConnection + '"', - 'Export-Package': 'com.hivemq.client.annotations.*, com.hivemq.client.mqtt.*, com.hivemq.client.rx.*, com.hivemq.client.util.*', - '-consumer-policy': '${range;[==,=+)}', - '-removeheaders': 'Private-Package') -} +allprojects { + plugins.withType(JavaLibraryPlugin) { + + project.apply plugin: 'biz.aQute.bnd.builder' + + jar { + bnd( + 'Automatic-Module-Name': project.moduleName, + 'Bundle-Name': project.name, + 'Bundle-SymbolicName': project.moduleName, + 'Bundle-Description': project.description, + 'Bundle-Vendor': 'HiveMQ and the HiveMQ Community', + 'Bundle-License': project.licenseShortName + ';description="' + project.licenseReadableName + '";link="' + project.licenseUrl + '"', + 'Bundle-DocURL': project.docUrl, + 'Bundle-SCM': 'url="' + project.githubUrl + '";connection="' + project.scmConnection + '";developerConnection="' + project.scmDeveloperConnection + '"', + 'Export-Package': 'com.hivemq.client.annotations.*, com.hivemq.client.mqtt.*, com.hivemq.client.rx.*, com.hivemq.client.util.*', + '-consumer-policy': '${range;[==,=+)}', + '-removeheaders': 'Private-Package') + } -apply plugin: 'com.github.johnrengelman.shadow' + task javadocJar(type: Jar) { + group 'documentation' + description 'Assembles a jar archive containing the javadoc.' -shadowJar { - appendix project.shadedAppendix - classifier null + from javadoc + classifier 'javadoc' + } - // api: not shaded and relocated, added as dependencies in pom - dependencies { - it.exclude(it.dependency('io.reactivex.rxjava2:rxjava')) - it.exclude(it.dependency('org.reactivestreams:reactive-streams')) - it.exclude(it.dependency('org.slf4j:slf4j-api')) - } + task sourcesJar(type: Jar) { + group 'build' + description 'Assembles a jar archive containing the main sources.' - def shadePrefix = project.group.toString() + '.shaded.' - def shadeFilePrefix = shadePrefix.replace('.', '_') - relocate 'io.netty', shadePrefix + 'io.netty' - relocate 'META-INF/native/libnetty', 'META-INF/native/lib' + shadeFilePrefix + 'netty' - exclude 'META-INF/io.netty.versions.properties' - relocate 'org.jctools', shadePrefix + 'org.jctools' - relocate 'org.jetbrains', shadePrefix + 'org.jetbrains' - relocate 'dagger', shadePrefix + 'dagger' - relocate 'javax.inject', shadePrefix + 'javax.inject' - - minimize() + from sourceSets.main.allSource + classifier 'sources' + } + } } allprojects { - task javadocJar(type: Jar) { - group 'documentation' - description 'Assembles a jar archive containing the javadoc.' + plugins.withId('com.github.johnrengelman.shadow') { - from javadoc - classifier 'javadoc' - } + shadowJar { + appendix project.shadedAppendix + classifier null - task sourcesJar(type: Jar) { - group 'build' - description 'Assembles a jar archive containing the main sources.' + // api: not shaded and relocated, added as dependencies in pom + dependencies { + it.exclude(it.dependency('io.reactivex.rxjava2:rxjava')) + it.exclude(it.dependency('org.reactivestreams:reactive-streams')) + it.exclude(it.dependency('org.slf4j:slf4j-api')) + } - from sourceSets.main.allSource - classifier 'sources' + def shadePrefix = project.group.toString() + '.shaded.' + def shadeFilePrefix = shadePrefix.replace('.', '_') + relocate 'io.netty', shadePrefix + 'io.netty' + relocate 'META-INF/native/libnetty', 'META-INF/native/lib' + shadeFilePrefix + 'netty' + exclude 'META-INF/io.netty.versions.properties' + relocate 'org.jctools', shadePrefix + 'org.jctools' + relocate 'org.jetbrains', shadePrefix + 'org.jetbrains' + relocate 'dagger', shadePrefix + 'dagger' + relocate 'javax.inject', shadePrefix + 'javax.inject' + + minimize() + } } } @@ -193,93 +201,104 @@ allprojects { apply from: "${project.rootDir}/gradle/publishing.gradle" -apply plugin: 'maven-publish' - -void addPom(MavenPublication publication) { - publication.pom { - name = project.readableName - description = project.description - url = project.githubUrl - licenses { - license { - name = project.licenseReadableName - url = project.licenseUrl - } - } - developers { - developer { - id = 'SG' - name = 'Silvio Giebl' - email = 'silvio.giebl@hivemq.com' +allprojects { + plugins.withType(JavaLibraryPlugin) { + + project.apply plugin: 'maven-publish' + + publishing { + publications { + withType(MavenPublication) { + pom { + name = project.readableName + description = project.description + url = project.githubUrl + licenses { + license { + name = project.licenseReadableName + url = project.licenseUrl + } + } + developers { + developer { + id = 'SG' + name = 'Silvio Giebl' + email = 'silvio.giebl@hivemq.com' + } + } + scm { + connection = project.scmConnection + developerConnection = project.scmDeveloperConnection + url = project.githubUrl + } + issueManagement { + system = 'github' + url = project.issuesUrl + } + } + artifact javadocJar + artifact sourcesJar + } + normal(MavenPublication) { + from components.java + } } } - scm { - connection = project.scmConnection - developerConnection = project.scmDeveloperConnection - url = project.githubUrl - } - issueManagement { - system = 'github' - url = project.issuesUrl - } - } -} -publishing { - publications { - normal(MavenPublication) { - from components.java - artifact javadocJar - artifact sourcesJar - addPom(it) - } - shaded(MavenPublication) { - artifactId project.name + '-' + project.shadedAppendix - artifact shadowJar - artifact javadocJar - artifact sourcesJar - addPom(it) - pom.withXml { xml -> - def dependenciesNode = xml.asNode().appendNode('dependencies') - - project.configurations.api.allDependencies.each { - def dependencyNode = dependenciesNode.appendNode('dependency') - dependencyNode.appendNode('groupId', it.group) - dependencyNode.appendNode('artifactId', it.name) - dependencyNode.appendNode('version', it.version) - dependencyNode.appendNode('scope', 'compile') + project.apply plugin: 'com.jfrog.bintray' + + afterEvaluate { + bintray { + user = project.bintray_username + key = project.bintray_apiKey + publications = publishing.publications.withType(MavenPublication).stream().collect { it.name } + publish = true + pkg { + userOrg = 'hivemq' + repo = 'HiveMQ' + name = 'hivemq-mqtt-client' + desc = project.description + websiteUrl = project.githubUrl + issueTrackerUrl = project.issuesUrl + vcsUrl = project.githubUrl + '.git' + licenses = [project.licenseShortName] + labels = ['mqtt', 'mqtt-client', 'iot', 'internet-of-things', 'rxjava2', 'reactive-streams', 'backpressure'] + version { + released = new Date() + gpg { + sign = true + } + /*mavenCentralSync { + user = '' + password = '' + }*/ + } } } } } } -apply plugin: 'com.jfrog.bintray' - -bintray { - user = project.bintray_username - key = project.bintray_apiKey - publications = ['normal', 'shaded'] - publish = true - pkg { - userOrg = 'hivemq' - repo = 'HiveMQ' - name = 'hivemq-mqtt-client' - desc = project.description - websiteUrl = project.githubUrl - issueTrackerUrl = project.issuesUrl - vcsUrl = project.githubUrl + '.git' - licenses = [project.licenseShortName] - labels = ['mqtt', 'mqtt-client', 'iot', 'internet-of-things', 'rxjava2', 'reactive-streams', 'backpressure'] - version { - released = new Date() - gpg { - sign = true +allprojects { + plugins.withId('com.github.johnrengelman.shadow') { + publishing { + publications { + shaded(MavenPublication) { + artifactId project.name + '-' + project.shadedAppendix + artifact shadowJar + pom.withXml { xml -> + def dependenciesNode = xml.asNode().appendNode('dependencies') + + project.configurations.api.allDependencies.each { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + dependencyNode.appendNode('scope', 'compile') + } + } + } } - /*mavenCentralSync { - user = '' - password = '' - }*/ } } } @@ -314,4 +333,8 @@ allprojects { } } -apply from: "${project.rootDir}/gradle/japicc.gradle" +allprojects { + plugins.withType(JavaLibraryPlugin) { + project.apply from: "${project.rootDir}/gradle/japicc.gradle" + } +} diff --git a/gradle/japicc.gradle b/gradle/japicc.gradle index f4d4d71e5..936b33ff2 100644 --- a/gradle/japicc.gradle +++ b/gradle/japicc.gradle @@ -1,192 +1,195 @@ task japicc { group 'verification' - description 'Checks for binary and source incompatibility.' - def japiccVersion = '2.4' - def workingDir = new File(project.buildDir, 'japicc') - def executable = new File(workingDir, 'japi-compliance-checker-' + japiccVersion + '/japi-compliance-checker.pl') - - def lastSemVer = project.prevVersion == null ? lastSemVer() : project.prevVersion - def shadedName = project.name + '-' + project.shadedAppendix - def lastJar = new File(workingDir, project.name + '-' + lastSemVer + '.jar') - def lastShadedJar = new File(workingDir, shadedName + '-' + lastSemVer + '.jar') + ext { + japiccVersion = '2.4' + workingDir = new File(project.buildDir, 'japicc') + executable = new File(workingDir, 'japi-compliance-checker-' + japiccVersion + '/japi-compliance-checker.pl') + } +} +project.tasks.check.dependsOn(japicc) - def nonImplFile = new File(workingDir, 'non-impl') +if (rootProject.tasks.findByName('japiccDownload') == null) { + rootProject.tasks.create('japiccDownload') { + group 'japicc' + description 'Download Java API Compliance Checker' - def reportDir = new File(workingDir, 'compat_reports') - def versions = lastSemVer + '_to_' + project.version - def report = new File(new File(new File(reportDir, project.name), versions), 'compat_report.html') - def shadedReport = new File(new File(new File(reportDir, shadedName), versions), 'compat_report.html') + outputs.files japicc.executable - inputs.files jar, shadowJar - outputs.files report, shadedReport + File archive = new File(japicc.workingDir, 'japi-compliance-checker-' + japicc.japiccVersion + '.zip') - doFirst { - description 'Check if last semantic version is available' - println(description) + doLast { + archive.parentFile.mkdirs() + if (!archive.exists()) { + new URL('https://github.com/lvc/japi-compliance-checker/archive/' + japicc.japiccVersion + '.zip') + .withInputStream { i -> archive.withOutputStream { it << i } } + } + } - if (project.version == lastSemVer) { - throw new StopExecutionException('No last semantic version available') + doLast { + copy { + from zipTree(archive) + into japicc.workingDir + } } } +} - doLast { +if (rootProject.tasks.findByName('japiccNonImpl') == null) { + rootProject.tasks.create('japiccNonImpl') { + group 'japicc' description 'List non impl interfaces' - println(description) - - nonImplFile.delete() - nonImplFile.createNewFile() - - sourceSets.main.java.visit { FileTreeElement f -> - if (f.file.isFile()) { - def packageName = f.relativePath.parent.pathString.replace('/', '.').replace('\\', '.') - - def content = f.file.getText("UTF-8") - content = content.replaceAll('//.*\n', ' ') // remove line comments - content = content.replaceAll('\n', ' ') // remove new lines - content = content.replaceAll('/\\*.*?\\*/', ' ') // remove multi line comments - content = content.replaceAll(' +', ' ') // remove unnecessary spaces - - def index = 0 - def classNames = [] - while (true) { - def start = content.indexOf(' interface ', index) - if (start == -1) break - - def sub = content.substring(0, start) - def level = sub.count('{') - sub.count('}') - while (level < classNames.size()) { - classNames.remove(classNames.size() - 1) - } - start += ' interface '.length() - def end = content.indexOf('{', start) - if (end == -1) break - - def interfaceDef = content.substring(start, end) - def className = interfaceDef.split('[ <{]', 2)[0] - classNames.add(className) - - def annotationIndex = content.indexOf('@DoNotImplement', index) - if (annotationIndex == -1) break - - if (annotationIndex < start) { - def qualifiedName = packageName + "." + classNames.join('.') - - def rest = interfaceDef.substring(className.length()).trim() - if (rest.startsWith('<')) { - rest = rest.replaceAll('extends [^ <,]+', '') // remove all extends ... - rest = rest.replaceAll('@.*? ', '') // remove all annotations - def generics = '<' - def nesting = 0 - for (def c : rest.chars) { - if (c == '<') { - nesting++ - } else if (c == '>') { - nesting-- - } else if (nesting == 1) { - generics += c - } else if (nesting == 0) { - break + ext { + nonImplFile = new File(japicc.workingDir, 'non-impl') + } + + inputs.files sourceSets.main.java + outputs.files nonImplFile + + doLast { + nonImplFile.delete() + nonImplFile.parentFile.mkdirs() + nonImplFile.createNewFile() + + sourceSets.main.java.visit { FileTreeElement f -> + if (f.file.isFile()) { + def packageName = f.relativePath.parent.pathString.replace('/', '.').replace('\\', '.') + + def content = f.file.getText("UTF-8") + content = content.replaceAll('//.*\n', ' ') // remove line comments + content = content.replaceAll('\n', ' ') // remove new lines + content = content.replaceAll('/\\*.*?\\*/', ' ') // remove multi line comments + content = content.replaceAll(' +', ' ') // remove unnecessary spaces + + def index = 0 + def classNames = [] + while (true) { + def start = content.indexOf(' interface ', index) + if (start == -1) break + + def sub = content.substring(0, start) + def level = sub.count('{') - sub.count('}') + while (level < classNames.size()) { + classNames.remove(classNames.size() - 1) + } + + start += ' interface '.length() + def end = content.indexOf('{', start) + if (end == -1) break + + def interfaceDef = content.substring(start, end) + def className = interfaceDef.split('[ <{]', 2)[0] + classNames.add(className) + + def annotationIndex = content.indexOf('@DoNotImplement', index) + if (annotationIndex == -1) break + + if (annotationIndex < start) { + def qualifiedName = packageName + "." + classNames.join('.') + + def rest = interfaceDef.substring(className.length()).trim() + if (rest.startsWith('<')) { + rest = rest.replaceAll('extends [^ <,]+', '') // remove all extends ... + rest = rest.replaceAll('@.*? ', '') // remove all annotations + def generics = '<' + def nesting = 0 + for (def c : rest.chars) { + if (c == '<') { + nesting++ + } else if (c == '>') { + nesting-- + } else if (nesting == 1) { + generics += c + } else if (nesting == 0) { + break + } } + generics += '>' + generics = generics.replace(' ', '') + qualifiedName += generics } - generics += '>' - generics = generics.replace(' ', '') - qualifiedName += generics + + nonImplFile.append(qualifiedName + '\n') } - nonImplFile.append(qualifiedName + '\n') + index = end + 1 } - - index = end + 1 } } } } +} - doLast { - description 'Download Java API Compliance Checker' - println(description) +def addCheck(Jar jarTask) { + String archiveBaseName = jarTask.archiveBaseName.get() + String archiveAppendix = jarTask.archiveAppendix.getOrElse('') + String taskName = 'japiccCheck' + archiveAppendix.capitalize() - def archive = new File(workingDir, 'japi-compliance-checker-' + japiccVersion + '.zip') - archive.parentFile.mkdirs() - if (!archive.exists()) { - new URL('https://github.com/lvc/japi-compliance-checker/archive/' + japiccVersion + '.zip') - .withInputStream { i -> archive.withOutputStream { it << i } } + def task = project.tasks.create(taskName) { - copy { - from zipTree(archive) - into workingDir - } - } - } + group 'japicc' + description 'Checks for binary and source incompatibility.' + dependsOn rootProject.tasks.japiccDownload, rootProject.tasks.japiccNonImpl - doLast { - description 'Download last version' - println(description) - - lastJar.parentFile.mkdirs() - if (!lastJar.exists()) { - String path = project.group.replace('.', '/') - path += '/' + project.name + '/' + lastSemVer + '/' - path += project.name + '-' + lastSemVer + '.jar' - new URL('http://central.maven.org/maven2/' + path) - .withInputStream { i -> lastJar.withOutputStream { it << i } } + File taskDir = new File(japicc.workingDir, taskName) + String archiveName = archiveBaseName + if (archiveAppendix == '-') { + archiveName += '-' + archiveAppendix } - } - doLast { - description 'Download last shaded version' - println(description) - - lastShadedJar.parentFile.mkdirs() - if (!lastShadedJar.exists()) { - String path = project.group.replace('.', '/') - path += '/' + shadedName + '/' + lastSemVer + '/' - path += shadedName + '-' + lastSemVer + '.jar' - new URL('http://central.maven.org/maven2/' + path) - .withInputStream { i -> lastShadedJar.withOutputStream { it << i } } - } - } + String lastSemVer = project.prevVersion == null ? lastSemVer() : project.prevVersion + String lastJarName = archiveName + '-' + lastSemVer + '.jar' + File lastJar = new File(taskDir, lastJarName) - doLast { - description 'Check binary and source compatibility for last version' - println(description) + File reportDir = new File(taskDir, 'compat_reports') + File report = new File(new File(reportDir, lastSemVer + '_to_' + project.version), 'compat_report.html') - def command = ['perl', executable.getPath(), '-lib', project.name, - '-skip-internal-packages', 'com.hivemq.client.internal', - '-non-impl', nonImplFile.getPath(), - '-check-annotations', '-s', - lastJar.getPath(), jar.archiveFile.get().getAsFile().getPath()] + inputs.files jarTask + outputs.files report - def process = new ProcessBuilder(command).directory(workingDir).start() - def returnCode = process.waitFor() - if (returnCode != 0) { - throw new GradleException('Binary or source incompatibilities, code ' + returnCode) + doFirst { + description 'Check if last semantic version is available' + println(description) + + if (project.version == lastSemVer) { + throw new StopExecutionException('No last semantic version available') + } } - } - doLast { - description 'Check binary and source compatibility for last shaded version' - println(description) - - def command = ['perl', executable.getPath(), '-lib', shadedName, - '-skip-internal-packages', 'com.hivemq.client.internal', - '-skip-internal-packages', 'com.hivemq.shaded', - '-non-impl', nonImplFile.getPath(), - '-check-annotations', '-s', - lastShadedJar.getPath(), shadowJar.archiveFile.get().getAsFile().getPath()] - - def process = new ProcessBuilder(command).directory(workingDir).start() - def returnCode = process.waitFor() - if (returnCode != 0) { - throw new GradleException('Binary or source incompatibilities in shaded, code ' + returnCode) + doLast { + description 'Download last version' + println(description) + + lastJar.parentFile.mkdirs() + if (!lastJar.exists()) { + String path = project.group.replace('.', '/') + '/' + archiveName + '/' + lastSemVer + '/' + lastJarName + new URL(project.repositories.mavenCentral().url.toString() + path) + .withInputStream { i -> lastJar.withOutputStream { it << i } } + } + } + + doLast { + description 'Check binary and source compatibility for last version' + println(description) + + def command = ['perl', japicc.executable.getPath(), '-lib', archiveName, + '-skip-internal-packages', 'com.hivemq.client.internal', + '-skip-internal-packages', 'com.hivemq.shaded', + '-non-impl', japiccNonImpl.nonImplFile.getPath(), + '-check-annotations', '-s', + lastJar.getPath(), jarTask.archiveFile.get().getAsFile().getPath()] + + Process process = new ProcessBuilder(command).directory(taskDir).start() + int returnCode = process.waitFor() + if (returnCode != 0) { + throw new GradleException('Binary or source incompatibilities, code ' + returnCode) + } } } + project.tasks.japicc.dependsOn(task) } -tasks.check.dependsOn(japicc) - String lastSemVer() { String version = project.version def split = version.split('-')[0].split('\\.') @@ -200,3 +203,8 @@ String lastSemVer() { } return major + '.' + minor + '.' + patch } + +addCheck(project.tasks.jar) +if (project.plugins.hasPlugin('com.github.johnrengelman.shadow')) { + addCheck(project.tasks.shadowJar) +}