buildscript { repositories { jcenter() maven { url 'https://plugins.gradle.org/m2/' } maven { url 'https://dl.bintray.com/content/aalmiray/kordamp' } } dependencies { classpath 'org.kordamp:markdown-gradle-plugin:1.0.0' classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.2' } } plugins { id 'ru.vyarus.animalsniffer' version '1.5.0' id 'net.ltgt.errorprone' version '1.3.0' // Use e.g. "gradle taskTree" to show its dependency tree. id 'com.dorongold.task-tree' version '1.5' id 'com.github.kt3k.coveralls' version '2.10.2' } apply plugin: 'org.kordamp.gradle.markdown' ext { java11Projects = [ ':smack-integration-test', ':smack-omemo-signal-integration-test', ':smack-repl', ':smack-websocket-java11', ].collect { project(it) } java11Projects += getRootProject() java8Projects = allprojects - java11Projects } configure (java8Projects) { ext { javaCompatilibity = JavaVersion.VERSION_1_8 } } configure (java11Projects) { ext { javaCompatilibity = JavaVersion.VERSION_11 } } allprojects { apply plugin: 'java-library' apply plugin: 'java-test-fixtures' apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'jacoco' apply plugin: 'net.ltgt.errorprone' version readVersionFile() ext { isSnapshot = version.endsWith('-SNAPSHOT') gitCommit = getGitCommit() javadocAllDir = new File(buildDir, 'javadoc') documentationDir = new File(buildDir, 'documentation') releasedocsDir = new File(buildDir, 'releasedocs') rootConfigDir = new File(rootDir, 'config') sonatypeCredentialsAvailable = project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePassword') isReleaseVersion = !isSnapshot isContinuousIntegrationEnvironment = Boolean.parseBoolean(System.getenv('CI')) signingRequired = !(isSnapshot || isContinuousIntegrationEnvironment) sonatypeSnapshotUrl = 'https://oss.sonatype.org/content/repositories/snapshots' sonatypeStagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2' // Returns only the date in yyyy-MM-dd format, as otherwise, with // hh:mm:ss information, the manifest files would change with every // build, causing unnecessary rebuilds. builtDate = (new java.text.SimpleDateFormat("yyyy-MM-dd")).format(new Date()) oneLineDesc = 'An Open Source XMPP (Jabber) client library' integrationTestProjects = [ ':smack-integration-test', ':smack-omemo-signal-integration-test', ].collect{ project(it) } javadocAllProjects = subprojects - integrationTestProjects // A dirty hack used for Gradle's jacoco plugin, since is not // hable to handle the case when a (sub)project has no unit // tests. :-( projectsWithoutUnitTests = [ ':smack-android', ':smack-android-extensions', ':smack-bosh', ':smack-debug', ':smack-debug-slf4j', ':smack-java8', ':smack-jingle-old', ':smack-resolver-dnsjava', ':smack-resolver-javax', ':smack-resolver-minidns', ':smack-omemo-signal-integration-test', ].collect{ project(it) } projectsWithUnitTests = subprojects - projectsWithoutUnitTests androidProjects = [ ':smack-tcp', ':smack-bosh', ':smack-core', ':smack-im', ':smack-resolver-minidns', ':smack-sasl-provided', ':smack-extensions', ':smack-experimental', ':smack-omemo', ':smack-omemo-signal', ':smack-openpgp', ':smack-xmlparser', ':smack-xmlparser-xpp3', ].collect{ project(it) } androidBootClasspathProjects = [ ':smack-android', ':smack-android-extensions', ].collect{ project(it) } androidOptionalProjects = [ ':smack-tcp', ':smack-extensions', ':smack-experimental', ':smack-bosh', ':smack-omemo', ':smack-omemo-signal', ].collect{ project(it) } gplLicensedProjects = [ ':smack-omemo-signal', ':smack-omemo-signal-integration-test', ':smack-repl' ].collect{ project(it) } // Lazily evaluate the Android bootClasspath and offline // Javadoc using a closure, so that targets which do not // require it are still able to succeed without an Android // SDK. androidBootClasspath = { getAndroidRuntimeJar() } androidJavadocOffline = { getAndroidJavadocOffline() } junit4Projects = [ ':smack-core', ':smack-im', ':smack-omemo', ':smack-omemo-signal', ':smack-openpgp', ].collect { project(it) } // When using dynamic versions for those, do *not* use [1.0, // 2.0), since this will also pull in 2.0-alpha1. Instead use // [1.0, 1.0.99]. // See also: // - https://issues.apache.org/jira/browse/MNG-6232 // - https://issues.igniterealtime.org/browse/SMACK-858 jxmppVersion = '[1.0.0, 1.0.999]' miniDnsVersion = '[1.0.0, 1.0.999]' smackMinAndroidSdk = 19 junitVersion = '5.7.1' commonsIoVersion = '2.6' bouncyCastleVersion = '1.68' guavaVersion = '30.1-jre' mockitoVersion = '3.7.7' orgReflectionsVersion = '0.9.11' if (project.hasProperty("useSonatype")) { useSonatype = project.getProperty("useSonatype").toBoolean() } else { // Default to true useSonatype = true } javaMajor = javaCompatilibity.getMajorVersion() } group = 'org.igniterealtime.smack' sourceCompatibility = javaCompatilibity targetCompatibility = sourceCompatibility test { useJUnitPlatform() maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 // Enable full stacktraces of failed tests. Especially handy // for environments like Travis. testLogging { events "failed" exceptionFormat "full" } } ext.sharedManifest = manifest { attributes('Implementation-Version': version, 'Implementation-GitRevision': ext.gitCommit, 'Built-Date': ext.builtDate, 'Built-JDK': System.getProperty('java.version'), 'Built-Gradle': gradle.gradleVersion, 'Built-By': System.getProperty('user.name') ) } eclipse { classpath { downloadJavadoc = true } } repositories { mavenLocal() mavenCentral() // Add OSS Sonatype Snapshot repository maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } } tasks.withType(JavaCompile) { // Some systems may not have set their platform default // converter to 'utf8', but we use unicode in our source // files. Therefore ensure that javac uses unicode options.encoding = 'UTF-8' options.compilerArgs = [ '-Xlint:all', // Set '-options' because a non-java7 javac will emit a // warning if source/traget is set to 1.7 and // bootclasspath is *not* set. // TODO implement a sound heuristic to determine a java7 // rt.jar on the build host. And if none is found, // fallback to using a environment variable, // e.g. JAVA7_HOME. See SMACK-651. '-Xlint:-options', '-Werror', ] options.errorprone { error( "UnusedVariable", "UnusedMethod", "MethodCanBeStatic", ) errorproneArgs = [ // Disable errorprone checks '-Xep:TypeParameterUnusedInFormals:OFF', // Disable errorpone StringSplitter check, as it // recommends using Splitter from Guava, which we don't // have (nor want to use in Smack). '-Xep:StringSplitter:OFF', '-Xep:JdkObsolete:OFF', // Disabled because sinttest re-uses BeforeClass from junit. // TODO: change sinttest so that it has it's own // BeforeClass and re-enable this check. '-Xep:JUnit4ClassAnnotationNonStatic:OFF', // Disabled but should be re-enabled at some point //'-Xep:InconsistentCapitalization:OFF', '-Xep:MixedMutabilityReturnType:OFF', // TODO: Re-enable once Smack's minimum Android SDK level is 26 or higher. '-Xep:JavaUtilDate:OFF', ] } } tasks.withType(ScalaCompile) { scalaCompileOptions.additionalParameters = [ '-Xfatal-warnings', '-feature', ] } jacoco { toolVersion = "0.8.6" } jacocoTestReport { dependsOn test getSourceDirectories().setFrom(project.files(sourceSets.main.allSource.srcDirs)) getClassDirectories().setFrom(project.files(sourceSets.main.output)) reports { xml.enabled true } } if (JavaVersion.current().isJava8Compatible()) { tasks.withType(Javadoc) { // The '-quiet' as second argument is actually a hack, // since the one paramater addStringOption doesn't seem to // work, we extra add '-quiet', which is added anyway by // gradle. // TODO: This enables all doclint check but // 'missing'. Re-enable 'missing' once every public API in // Smack has a javadoc comment. options.addStringOption('Xdoclint:accessibility,html,reference,syntax', '-quiet') // Treat warnings as errors. // See also https://bugs.openjdk.java.net/browse/JDK-8200363 options.addStringOption('Xwerror', '-quiet') } } if (JavaVersion.current().isJava9Compatible()) { tasks.withType(Javadoc) { options.addStringOption('-release', javaMajor) // The -no-modules-directories javadoc option was removed in Java 13 // See https://bugs.openjdk.java.net/browse/JDK-8215582 if (JavaVersion.current() < JavaVersion.VERSION_13) { // Fix for javadoc search. If not set, the search result would direct to // javadoc/undefined/org/jivesoftware/smack/altconnections/HttpLookupMethod.html // instead of // javadoc/org/jivesoftware/smack/altconnections/HttpLookupMethod.html // https://stackoverflow.com/a/53732633/194894 options.addBooleanOption("-no-module-directories", true) } } tasks.withType(JavaCompile) { options.compilerArgs.addAll([ '--release', javaMajor, ]) } } tasks.withType(Javadoc) { options.charSet = "UTF-8" options.encoding = 'UTF-8' } dependencies { testFixturesApi "org.junit.jupiter:junit-jupiter-api:$junitVersion" testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion" // The smack-extensions subproject uses mockito in its fest // fixtures, and we want to have mockito also available in // test, so we use API here. testFixturesApi "org.mockito:mockito-core:${mockitoVersion}" // To mock final classes testImplementation "org.mockito:mockito-inline:${mockitoVersion}" testImplementation 'com.jamesmurty.utils:java-xmlbuilder:1.2' errorprone 'com.google.errorprone:error_prone_core:2.5.1' errorproneJavac('com.google.errorprone:javac:9+181-r4173-1') } // Make all project's 'test' target depend on javadoc, so that // javadoc is also linted. test { dependsOn javadoc } } configure (junit4Projects) { dependencies { testImplementation "org.junit.vintage:junit-vintage-engine:$junitVersion" } } // We need to evaluate the child projects first because // - javadocAll needs the smack-core child to have already resolved // the jXMPP/MiniDNS dependencies, so that we can the resovled // version to link to those project's javadoc. // - We use the child's project description as description for the // Maven POM. evaluationDependsOnChildren() task javadocAll(type: Javadoc) { source javadocAllProjects.collect {project -> project.sourceSets.main.allJava.findAll { // Filter out symbolic links to avoid // "warning: a package-info.java file has already been seen for package" // javadoc warnings. !java.nio.file.Files.isSymbolicLink(it.toPath()) } } destinationDir = javadocAllDir // Might need a classpath classpath = files(subprojects.collect {project -> project.sourceSets.main.compileClasspath}) classpath += files(androidBootClasspath) def staticJxmppVersion = getResolvedVersion('org.jxmpp:jxmpp-core') def staticMiniDnsVersion = getResolvedVersion('org.minidns:minidns-core') options { linkSource = true use = true links = [ "https://docs.oracle.com/en/java/javase/${javaMajor}/docs/api/", "https://jxmpp.org/releases/${staticJxmppVersion}/javadoc/", "https://minidns.org/releases/${staticMiniDnsVersion}/javadoc/", ] as String[] overview = "$projectDir/resources/javadoc-overview.html" } // Finally copy the javadoc doc-files from the subprojects, which // are potentially generated, to the javadocAll directory. Note // that we use a copy *method* and not a *task* because the inputs // of copy tasks is determined within the configuration phase. And // since some of the inputs are generated, they will not get // picked up if we used a copy method. See also // https://stackoverflow.com/a/40518516/194894 doLast { copy { javadocAllProjects.each { from ("${it.projectDir}/src/javadoc") { include '**/doc-files/*.*' } } into javadocAllDir } } } import org.apache.tools.ant.filters.ReplaceTokens task prepareReleasedocs(type: Copy) { from 'resources/releasedocs' into releasedocsDir filter(ReplaceTokens, tokens: [version: version, releasedate: builtDate, targetCompatibility: targetCompatibility.toString()]) } markdownToHtml { sourceDir = new File(projectDir, "/documentation") outputDir documentationDir configuration = [tables: true, fencedCodeBlocks: true] } task distributionZip(type: Zip, dependsOn: [javadocAll, prepareReleasedocs, markdownToHtml]) { classifier builtDate into ('javadoc') { from(javadocAllDir) } into ('releasedocs') { from(releasedocsDir) } into ('releasedocs/documentation') { from(documentationDir) } } task maybeCheckForSnapshotDependencies { // Don't check for Snapshot dependencies if this is a snapshot. onlyIf { isReleaseVersion } // Run in the execution phase, not in configuration phase, as the // 'each' forces the runtime configuration to be resovled, which // causes "Cannot change dependencies of configuration after it // has been included in dependency resolution." errors. // See https://discuss.gradle.org/t/23153 doLast { allprojects { project -> project.configurations.runtime.each { if (it.toString().contains("-SNAPSHOT")) throw new Exception("Release build contains snapshot dependencies: " + it) } } } } test { dependsOn maybeCheckForSnapshotDependencies } jar { // Root project should not create empty jar artifact enabled = false } // Disable upload archives for the root project uploadArchives.enabled = false description = """\ Smack ${version} ${oneLineDesc}.""" subprojects { apply plugin: 'maven-publish' apply plugin: 'signing' apply plugin: 'checkstyle' apply plugin: 'org.kordamp.gradle.clirr' checkstyle { toolVersion = '8.27' } task sourcesJar(type: Jar, dependsOn: classes) { classifier = 'sources' from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } task testsJar(type: Jar, dependsOn: testClasses) { classifier = 'tests' from sourceSets.test.output } artifacts { // See http://stackoverflow.com/a/21946676/194894 testRuntime testsJar } publishing { publications { mavenJava(MavenPublication) { from components.java artifact sourcesJar artifact javadocJar artifact testsJar pom { name = 'Smack' packaging = 'jar' inceptionYear = '2003' url = 'http://www.igniterealtime.org/projects/smack/' description = project.description issueManagement { system = 'JIRA' url = 'https://igniterealtime.org/issues/browse/SMACK' } scm { url = 'https://github.com/igniterealtime/Smack' connection = 'scm:git:https://github.com/igniterealtime/Smack.git' developerConnection = 'scm:git:https://github.com/igniterealtime/Smack.git' } developers { developer { id = 'flow' name = 'Florian Schmaus' email = 'flow@igniterealtime.org' } } } } } repositories { if (sonatypeCredentialsAvailable && useSonatype) { maven { url isSnapshot ? sonatypeSnapshotUrl : sonatypeStagingUrl credentials { username = sonatypeUsername password = sonatypePassword } } } // Use // gradle publish -P customRepoUrl=https://www.igniterealtime.org/archiva/repository/maven -P customRepoUsername=bamboo -P customRepoPassword=hidden -P useSonatype=false // to deploy to this repo. if (project.hasProperty("customRepoUrl")) { maven { name 'customRepo' url customRepoUrl if (project.hasProperty("customRepoUsername")) { credentials { username customRepoUsername password customRepoPassword } } } } } } rootProject.distributionZip { dependsOn build from(buildDir) { include "$libsDirName/*${version}.jar" include "$libsDirName/*${version}-javadoc.jar" include "$libsDirName/*${version}-sources.jar" } } // Workaround for gpg signatory not supporting the 'required' option // See https://github.com/gradle/gradle/issues/5064#issuecomment-381924984 // Note what we use 'signing.gnupg.keyName' instead of 'signing.keyId'. tasks.withType(Sign) { onlyIf { project.hasProperty('signing.gnupg.keyName') } } signing { useGpgCmd() required { signingRequired } sign publishing.publications.mavenJava } clirr { // 2018-08-14: Disabled Clirr because // - It reports an breaking change in android.jar (seems right, but there is nothing we can do about it) // - Only the first smack-* projects are correctly checked, // the other ones have the output of a clirr report from a previous project // (Look at the clirr reports). enabled false semver false } // Work around https://github.com/gradle/gradle/issues/4046 task copyJavadocDocFiles(type: Copy) { from('src/javadoc') into 'build/docs/javadoc' include '**/doc-files/*.*' } javadoc.dependsOn copyJavadocDocFiles // Make sure root projects 'javadocAll' depends on the // subproject's javadoc, to ensure that all all doc-files/ are // generated and up-to-date. Obviously this means that the // javadocAll task will also create the individual javadoc's of the // subprojects. javadocAll.dependsOn javadoc } // The smack-java8-full project generates the dot and png files of the // current state graph. Ensure they are generated before copied. configure (project(':smack-java8-full')) { copyJavadocDocFiles.dependsOn convertModularXmppClientToServerConnectionStateGraphDotToPng } configure (androidProjects + androidBootClasspathProjects) { apply plugin: 'ru.vyarus.animalsniffer' dependencies { signature "net.sf.androidscents.signature:android-api-level-${smackMinAndroidSdk}:4.4.2_r4@signature" } animalsniffer { sourceSets = [sourceSets.main] } } // There is no need to ever clirr integration test projects and the // smack-repl project. configure(integrationTestProjects + project(':smack-repl')) { clirr { enabled false } } // Disable clirr on omemo modules project(':smack-omemo').clirr.enabled = false project(':smack-omemo-signal').clirr.enabled = false subprojects*.jar { manifest { from sharedManifest } } configure(subprojects - gplLicensedProjects) { checkstyle { configProperties.checkstyleLicenseHeader = "header" } publishing { publications { mavenJava(MavenPublication) { pom { licenses { license { name = 'The Apache Software License, Version 2.0' url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' distribution = 'repo' } } } } } } } configure(gplLicensedProjects) { checkstyle { configProperties.checkstyleLicenseHeader = "${project.name}-gplv3-license-header" } publishing { publications { mavenJava(MavenPublication) { pom { licenses { license { name = 'GNU General Public License, version 3 or any later version' url = 'https://www.gnu.org/licenses/gpl.txt' distribution = 'repo' } } } } } } } configure(androidBootClasspathProjects) { compileJava { options.bootstrapClasspath = files(androidBootClasspath) } javadoc { classpath += files(androidBootClasspath) } } apply plugin: "com.github.kt3k.coveralls" coveralls { sourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs).files.absolutePath } task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) { dependsOn = projectsWithUnitTests.jacocoTestReport getSourceDirectories().setFrom(files(projectsWithUnitTests.sourceSets.main.allSource.srcDirs)) getClassDirectories().setFrom(files(projectsWithUnitTests.sourceSets.main.output)) getExecutionData().setFrom(files(projectsWithUnitTests.jacocoTestReport.executionData)) reports { xml.enabled true xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml") } // We could remove the following setOnlyIf line, but then // jacocoRootReport would silently be SKIPPED if something with // the projectsWithUnitTests is wrong (e.g. a project is missing // in there). setOnlyIf { true } } // Important to specify this task after the subprojects block task clirrRootReport(type: org.kordamp.gradle.clirr.ClirrReportTask) { dependsOn = subprojects.tasks.clirr reports = files((subprojects.findAll { it.clirr.enabled == true }).tasks.clirr.xmlReport) } task integrationTest { description 'Verify correct functionality of Smack by running some integration tests.' dependsOn project(':smack-integration-test').tasks.run } task omemoSignalIntTest { description 'Run integration tests of the smack-omemo module in combination with smack-omemo-signal.' dependsOn project(':smack-omemo-signal-integration-test').tasks.run } task sinttestAll { description 'Run all of Smack\'s integration tests.' dependsOn {[ integrationTest, omemoSignalIntTest, ]} } def getGitCommit() { def projectDirFile = new File("$projectDir") def dotGit = new File(projectDirFile, ".git") if (!dotGit.isDirectory()) return 'non-git build' def cmd = 'git describe --always --tags --dirty=+' def proc = cmd.execute(null, projectDirFile) proc.waitForOrKill(10 * 1000) def gitCommit = proc.text.trim() assert !gitCommit.isEmpty() def srCmd = 'git symbolic-ref --short HEAD' def srProc = srCmd.execute(null, projectDirFile) srProc.waitForOrKill(10 * 1000) if (srProc.exitValue() == 0) { // Only add the information if the git command was // successful. There may be no symbolic reference for HEAD if // e.g. in detached mode. def symbolicReference = srProc.text.trim() assert !symbolicReference.isEmpty() gitCommit += "-$symbolicReference" } gitCommit } def getAndroidRuntimeJar() { def androidApiLevel = ext.smackMinAndroidSdk def androidHome = getAndroidHome() def androidJar = new File("$androidHome/platforms/android-${androidApiLevel}/android.jar") if (androidJar.isFile()) { return androidJar } else { throw new Exception("Can't find android.jar for API level ${androidApiLevel}. Please install corresponding SDK platform package") } } def getAndroidJavadocOffline() { def androidHome = getAndroidHome() return androidHome.toString() + "/docs/reference" } def getAndroidHome() { def androidHomeEnv = System.getenv("ANDROID_HOME") if (androidHomeEnv == null) { throw new Exception("ANDROID_HOME environment variable is not set") } def androidHome = new File(androidHomeEnv) if (!androidHome.isDirectory()) throw new Exception("Environment variable ANDROID_HOME is not pointing to a directory") return androidHome } def readVersionFile() { def versionFile = new File(rootDir, 'version') if (!versionFile.isFile()) { throw new Exception("Could not find version file") } if (versionFile.text.isEmpty()) { throw new Exception("Version file does not contain a version") } versionFile.text.trim() } def getResolvedVersion(queriedProject = 'smack-core', component) { def configuration = project(queriedProject) .configurations .compileClasspath def artifact = configuration .resolvedConfiguration .resolvedArtifacts .findAll { // 'it' is of type ResolvedArtifact, 'id' of // Component*Artifcat*Identifier, and we check the // ComponentIdentifier. it.id.getComponentIdentifier() instanceof org.gradle.api.artifacts.component.ModuleComponentIdentifier } .find { it.id.getComponentIdentifier().toString().startsWith(component + ':') } artifact.getModuleVersion().getId().getVersion() }