blob: 81e0726d75a331b58a72820310d9d7741d07db30 [file] [log] [blame]
import android.support.checkapi.ApiXmlConversionTask
import android.support.checkapi.CheckApiTask
import android.support.checkapi.UpdateApiTask
import android.support.doclava.DoclavaMultilineJavadocOptionFileOption
import android.support.doclava.DoclavaTask
import android.support.jdiff.JDiffTask
import com.android.build.gradle.internal.coverage.JacocoPlugin
import com.android.build.gradle.internal.coverage.JacocoReportTask
import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
import org.gradle.internal.os.OperatingSystem
import com.google.common.base.Charsets
import com.google.common.hash.HashCode
import com.google.common.hash.HashFunction
import com.google.common.hash.Hashing
import com.google.common.io.Files
import groovy.io.FileType
buildscript {
apply from: 'buildSrc/dependencies.gradle'
repositories {
maven { url '../../prebuilts/gradle-plugin' }
maven { url '../../prebuilts/tools/common/m2/repository' }
maven { url '../../prebuilts/tools/common/m2/internal' }
maven { url '../../prebuilts/maven_repo/android' }
}
dependencies {
classpath libs.gradle
}
}
apply from: 'buildSrc/dependencies.gradle'
repositories {
maven { url '../../prebuilts/tools/common/m2/repository' }
}
configurations {
doclava
jdiff
}
dependencies {
doclava project(':doclava')
jdiff project(':jdiff')
jdiff libs.xml_parser_apis
jdiff libs.xerces_impl
}
// Version code components.
ext.supportVersion = "26.0.0-SNAPSHOT"
// This number gets incremented for each public release.
ext.extraVersion = 43
// Enforce the use of prebuilt dependencies in all sub-projects. This is
// required for the doclava dependency.
ext.usePrebuilts = "true"
final String platform = OperatingSystem.current().isMacOsX() ? 'darwin' : 'linux'
System.setProperty('android.dir', "${rootDir}/../../")
final String fullSdkPath = "${rootDir}/../../prebuilts/fullsdk-${platform}"
if (file(fullSdkPath).exists()) {
gradle.ext.currentSdk = 26
ext.buildToolsVersion = '26.0.0'
project.ext.androidJar = files("${fullSdkPath}/platforms/android-${gradle.ext.currentSdk}/android.jar")
System.setProperty('android.home', "${rootDir}/../../prebuilts/fullsdk-${platform}")
File props = file("local.properties")
props.write "sdk.dir=${fullSdkPath}"
} else {
gradle.ext.currentSdk = 'current'
ext.buildToolsVersion = '24.0.1'
project.ext.androidJar = files("${project.rootDir}/../../prebuilts/sdk/current/android.jar")
File props = file("local.properties")
props.write "android.dir=../../"
}
ext.supportRepoOut = ''
ext.buildNumber = Integer.toString(ext.extraVersion)
/*
* With the build server you are given two env variables.
* The OUT_DIR is a temporary directory you can use to put things during the build.
* The DIST_DIR is where you want to save things from the build.
*
* The build server will copy the contents of DIST_DIR to somewhere and make it available.
*/
if (System.env.DIST_DIR != null && System.env.OUT_DIR != null) {
buildDir = new File(System.env.OUT_DIR + '/gradle/frameworks/support/build').getCanonicalFile()
project.ext.distDir = new File(System.env.DIST_DIR).getCanonicalFile()
// the build server does not pass the build number so we infer it from the last folder of the dist path.
ext.buildNumber = project.ext.distDir.getName()
} else {
buildDir = file("${project.rootDir}/../../out/host/gradle/frameworks/support/build")
project.ext.distDir = file("${project.rootDir}/../../out/dist")
}
subprojects {
// Change buildDir first so that all plugins pick up the new value.
project.buildDir = project.file("$project.parent.buildDir/../$project.name/build")
}
ext.docsDir = new File(buildDir, 'javadoc')
ext.supportRepoOut = new File(buildDir, 'support_repo')
ext.testApkDistOut = ext.distDir
// Main task called by the build server.
task(createArchive)
// upload anchor for subprojects to upload their artifacts
// to the local repo.
task(mainUpload)
// repository creation task
task createRepository(type: Zip, dependsOn: mainUpload) {
from project.ext.supportRepoOut
destinationDir project.ext.distDir
into 'm2repository'
baseName = String.format("sdk-repo-linux-m2repository-%s", project.ext.buildNumber)
}
createArchive.dependsOn createRepository
// prepare repository with older versions
task unzipRepo(type: Copy) {
from "${project.rootDir}/../../prebuilts/maven_repo/android"
into project.ext.supportRepoOut
}
unzipRepo.doFirst {
project.ext.supportRepoOut.deleteDir()
project.ext.supportRepoOut.mkdirs()
}
// anchor for prepare repo. This is post unzip + sourceProp.
task(prepareRepo)
// lint every library
task(lint)
task(createXml).doLast({
def repoArchive = createRepository.archivePath
def repoArchiveName = createRepository.archiveName
def size = repoArchive.length()
def sha1 = getSha1(repoArchive)
def xml =
"<sdk:sdk-addon xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:sdk=\"http://schemas.android.com/sdk/android/addon/6\">\n\
<sdk:extra>\n\
<sdk:revision>\n\
<sdk:major>${project.ext.extraVersion}</sdk:major>\n\
</sdk:revision>\n\
<sdk:vendor-display>Android</sdk:vendor-display>\n\
<sdk:vendor-id>android</sdk:vendor-id>\n\
<sdk:name-display>Local Maven repository for Support Libraries</sdk:name-display>\n\
<sdk:path>m2repository</sdk:path>\n\
<sdk:archives>\n\
<sdk:archive>\n\
<sdk:size>${size}</sdk:size>\n\
<sdk:checksum type=\"sha1\">${sha1}</sdk:checksum>\n\
<sdk:url>${repoArchiveName}</sdk:url>\n\
</sdk:archive>\n\
</sdk:archives>\n\
</sdk:extra>\n\
</sdk:sdk-addon>"
Files.write(xml, new File(project.ext.distDir, 'repo-extras.xml'), Charsets.UTF_8)
})
createArchive.dependsOn createXml
createXml.dependsOn createRepository
task(createSourceProp).doLast({
def sourceProp =
"Extra.VendorDisplay=Android\n\
Extra.Path=m2repository\n\
Archive.Arch=ANY\n\
Extra.NameDisplay=Android Support Repository\n\
Archive.Os=ANY\n\
Pkg.Desc=Local Maven repository for Support Libraries\n\
Pkg.Revision=${project.ext.extraVersion}.0.0\n\
Extra.VendorId=android"
Files.write(sourceProp, new File(project.ext.supportRepoOut, 'source.properties'), Charsets.UTF_8)
})
createSourceProp.dependsOn unzipRepo
prepareRepo.dependsOn createSourceProp
import java.nio.charset.Charset
/**
* Generates SHA1 hash for the specified file's absolute path.
*
* @param inputFile file to hash
* @return SHA1 hash
*/
String getSha1(File inputFile) {
HashFunction hashFunction = Hashing.sha1()
HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath(), Charset.forName("UTF-8"))
return hashCode.toString()
}
void registerForDocsTask(Task task, Project subProject, releaseVariant) {
task.dependsOn releaseVariant.javaCompile
task.source {
def buildConfig = fileTree(releaseVariant.getGenerateBuildConfig().sourceOutputDir)
return releaseVariant.javaCompile.source.minus(buildConfig) +
fileTree(releaseVariant.aidlCompile.sourceOutputDir) +
fileTree(releaseVariant.outputs[0].processResources.sourceOutputDir)
}
task.classpath += files(releaseVariant.javaCompile.classpath) +
files(releaseVariant.javaCompile.destinationDir)
}
// Generates online docs.
task generateDocs(type: DoclavaTask, dependsOn: configurations.doclava) {
docletpath = configurations.doclava.resolve()
destinationDir = new File(project.docsDir, "online")
// Base classpath is Android SDK, sub-projects add their own.
classpath = project.ext.androidJar
def hdfOption = new DoclavaMultilineJavadocOptionFileOption('hdf')
hdfOption.add(
['android.whichdoc', 'online'],
['android.hasSamples', 'true']);
// Default hidden errors + hidden superclass (111) and
// deprecation mismatch (113) to match framework docs.
final def hidden = [105, 107, 111, 112, 113, 115, 116, 121]
doclavaErrors = (101..122) - hidden
doclavaWarnings = []
doclavaHidden += hidden
options {
addStringOption "templatedir",
"${project.rootDir}/../../build/tools/droiddoc/templates-sdk"
addStringOption "federate Android", "http://developer.android.com"
addStringOption "stubpackages", "android.support.*"
addStringOption "samplesdir", "${project.rootDir}/samples"
addOption hdfOption
}
exclude '**/BuildConfig.java'
}
JDiffTask createApiDiffsTask(String taskName, File oldApiXml, File newApiXml, File outDir,
Configuration jdiff, Task... dependencies) {
return tasks.create(name: taskName, type: JDiffTask.class) {
dependsOn jdiff
dependsOn dependencies
docletpath = jdiff.resolve()
oldApiXmlFile oldApiXml
newApiXmlFile newApiXml
destinationDir = outDir
// This prefix is based on the assumption that the output diffs will
// ultimately land in frameworks/base/docs/html/sdk/support_api_diff/.
newJavadocPrefix = "../reference/"
}
}
// Generates API files.
task generateApi(type: DoclavaTask, dependsOn: configurations.doclava) {
docletpath = configurations.doclava.resolve()
destinationDir = project.docsDir
// Base classpath is Android SDK, sub-projects add their own.
classpath = project.ext.androidJar
apiFile = new File(project.docsDir, 'release/current.txt')
removedApiFile = new File(project.docsDir, 'release/removed.txt')
generateDocs = false
options {
addStringOption "templatedir",
"${project.rootDir}/../../build/tools/droiddoc/templates-sdk"
addStringOption "federate Android", "http://developer.android.com"
addStringOption "stubpackages", "android.support.*"
}
exclude '**/BuildConfig.java'
exclude '**/R.java'
}
// Copies generated API files to current version.
task updateApi(type: UpdateApiTask, dependsOn: generateApi) {
newApiFile = new File(project.docsDir, 'release/current.txt')
oldApiFile = new File(project.rootDir, 'api/current.txt')
newRemovedApiFile = new File(project.docsDir, 'release/removed.txt')
oldRemovedApiFile = new File(project.rootDir, 'api/removed.txt')
}
// Checks generated API files against current version.
task checkApi(type: CheckApiTask, dependsOn: generateApi) {
doclavaClasspath = generateApi.docletpath
checkApiTaskPath = name
updateApiTaskPath = updateApi.name
// Check that the API we're building hasn't changed from the development
// version. These typed of changes require an explicit API file update.
checkApiErrors = (2..30)-[22]
checkApiWarnings = []
checkApiHidden = [22]
newApiFile = new File(project.docsDir, 'release/current.txt')
oldApiFile = new File(project.rootDir, 'api/current.txt')
newRemovedApiFile = new File(project.docsDir, 'release/removed.txt')
oldRemovedApiFile = new File(project.rootDir, 'api/removed.txt')
}
createArchive.dependsOn checkApi
// Checks generated API files against current version.
task checkApiStable(type: CheckApiTask, dependsOn: generateApi) {
doclavaClasspath = generateApi.docletpath
checkApiTaskPath = name
updateApiTaskPath = updateApi.name
// Check that the API we're building hasn't broken the last-released
// library version. These types of changes are forbidden.
checkApiErrors = (7..18)
checkApiWarnings = [23, 24]
checkApiHidden = (2..6) + (19..22) + (25..30)
newApiFile = new File(project.docsDir, 'release/current.txt')
oldApiFile = getReleasedApiFile()
newRemovedApiFile = new File(project.docsDir, 'release/removed.txt')
oldRemovedApiFile = new File(project.rootDir, 'api/removed.txt')
}
checkApi.dependsOn checkApiStable
/**
* Converts the <code>toApi</code>.txt file (or current.txt if not explicitly
* defined using -PtoAPi=<file>) to XML format for use by JDiff.
*/
task newApiXml(type: ApiXmlConversionTask, dependsOn: configurations.doclava) {
classpath configurations.doclava.resolve()
if (project.hasProperty("toApi")) {
// Use an explicit API file.
inputApiFile = new File(project.rootDir, "api/${toApi}.txt")
} else {
// Use the current API file (e.g. current.txt).
inputApiFile = generateApi.apiFile
dependsOn generateApi
}
int lastDot = inputApiFile.name.lastIndexOf('.')
outputApiXmlFile = new File(project.docsDir,
"release/" + inputApiFile.name.substring(0, lastDot) + ".xml")
}
/**
* Converts the <code>fromApi</code>.txt file (or the most recently released
* X.Y.Z.txt if not explicitly defined using -PfromAPi=<file>) to XML format
* for use by JDiff.
*/
task oldApiXml(type: ApiXmlConversionTask, dependsOn: configurations.doclava) {
classpath configurations.doclava.resolve()
if (project.hasProperty("fromApi")) {
// Use an explicit API file.
inputApiFile = new File(project.rootDir, "api/${fromApi}.txt")
} else if (project.hasProperty("toApi") && toApi.matches(~/(\d+\.){2}\d+/)) {
// If toApi matches released API (X.Y.Z) format, use the most recently
// released API file prior to toApi.
inputApiFile = getReleasedApiFile(toApi)
} else {
// Use the most recently released API file.
inputApiFile = getReleasedApiFile();
}
int lastDot = inputApiFile.name.lastIndexOf('.')
outputApiXmlFile = new File(project.docsDir,
"release/" + inputApiFile.name.substring(0, lastDot) + ".xml")
}
/**
* Generates API diffs.
* <p>
* By default, diffs are generated for the delta between current.txt and the
* next most recent X.Y.Z.txt API file. Behavior may be changed by specifying
* one or both of -PtoApi and -PfromApi.
* <p>
* If both fromApi and toApi are specified, diffs will be generated for
* fromApi -> toApi. For example, 25.0.0 -> 26.0.0 diffs could be generated by
* using:
* <br><code>
* ./gradlew generateDiffs -PfromApi=25.0.0 -PtoApi=26.0.0
* </code>
* <p>
* If only toApi is specified, it MUST be specified as X.Y.Z and diffs will be
* generated for (release before toApi) -> toApi. For example, 24.2.0 -> 25.0.0
* diffs could be generated by using:
* <br><code>
* ./gradlew generateDiffs -PtoApi=25.0.0
* </code>
* <p>
* If only fromApi is specified, diffs will be generated for fromApi -> current.
* For example, lastApiReview -> current diffs could be generated by using:
* <br><code>
* ./gradlew generateDiffs -PfromApi=lastApiReview
* </code>
* <p>
*/
task generateDiffs(type: JDiffTask, dependsOn: [configurations.jdiff, configurations.doclava,
oldApiXml, newApiXml, generateDocs]) {
// Base classpath is Android SDK, sub-projects add their own.
classpath = project.ext.androidJar
// JDiff properties.
oldApiXmlFile = oldApiXml.outputApiXmlFile
newApiXmlFile = newApiXml.outputApiXmlFile
newJavadocPrefix = "../../../../reference/"
String newApi = newApiXmlFile.name
int lastDot = newApi.lastIndexOf('.')
newApi = newApi.substring(0, lastDot)
// Javadoc properties.
docletpath = configurations.jdiff.resolve()
destinationDir = new File(project.docsDir, "online/sdk/support_api_diff/$newApi")
title = "Support&nbsp;Library&nbsp;API&nbsp;Differences&nbsp;Report"
exclude '**/BuildConfig.java'
exclude '**/R.java'
}
/**
* Returns the most recently released API, optionally restricting to APIs
* before <code>beforeApi</code>.
*
* @param beforeApi the API to find an API file before, ex. 25.0.0
* @return the most recently released API file
*/
File getReleasedApiFile(String beforeApi = null) {
String beforeApiFileName = beforeApi != null ? beforeApi + ".txt" : null
File lastReleasedApiFile = null
File apiDir = new File(project.rootDir, 'api')
apiDir.eachFileMatch FileType.FILES, ~/(\d+\.){3}txt/, { File apiFile ->
// Is the current API file newer than the last one we saw?
if (lastReleasedApiFile == null || apiFile.name > lastReleasedApiFile.name) {
// Is the current API file older than the "before" API?
if (beforeApiFileName == null || apiFile.name < beforeApiFileName) {
lastReleasedApiFile = apiFile
}
}
}
return lastReleasedApiFile
}
subprojects {
// Only modify Android projects.
if (project.name.equals('doclava') || project.name.equals('jdiff')) return;
// Current SDK is set in studioCompat.gradle.
project.ext.currentSdk = gradle.ext.currentSdk
apply plugin: 'maven'
version = rootProject.ext.supportVersion
group = 'com.android.support'
repositories {
maven { url "${project.parent.projectDir}/../../prebuilts/tools/common/m2/repository" }
maven { url "${project.parent.projectDir}/../../prebuilts/tools/common/m2/internal" }
maven { url "${project.parent.projectDir}/../../prebuilts/maven_repo/android" }
}
project.plugins.whenPluginAdded { plugin ->
def isAndroidLibrary = "com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)
def isAndroidApp = "com.android.build.gradle.AppPlugin".equals(plugin.class.name)
def isJavaLibrary = "org.gradle.api.plugins.JavaPlugin".equals(plugin.class.name)
if (isAndroidLibrary || isAndroidApp) {
project.android.buildToolsVersion = rootProject.buildToolsVersion
// Enable code coverage for debug builds only if we are not running inside the IDE,
// since enabling coverage reports breaks the method parameter resolution in the IDE
// debugger.
project.android.buildTypes.debug.testCoverageEnabled = !hasProperty('android.injected.invoked.from.ide')
// Copy the class files in a jar to be later used to generate code coverage report
project.android.testVariants.all { v ->
// check if the variant has any source files
// and test coverage is enabled
if (v.buildType.testCoverageEnabled
&& v.sourceSets.any { !it.java.sourceFiles.isEmpty() }) {
def jarifyTask = project.tasks.create(
name: "package${v.name.capitalize()}ClassFilesForCoverageReport",
type: Jar) {
from v.testedVariant.javaCompile.destinationDir
exclude "**/R.class"
exclude "**/R\$*.class"
exclude "**/BuildConfig.class"
destinationDir file(project.distDir)
archiveName "${project.archivesBaseName}-${v.baseName}-allclasses.jar"
}
def jacocoAntConfig =
project.configurations[JacocoPlugin.ANT_CONFIGURATION_NAME]
def jacocoAntArtifacts = jacocoAntConfig.resolvedConfiguration.resolvedArtifacts
def version = jacocoAntArtifacts.find { "org.jacoco.ant".equals(it.name) }
.moduleVersion.id.version
def collectJacocoAntPackages = project.tasks.create(
name: "collectJacocoAntPackages",
type: Jar) {
from (jacocoAntArtifacts.collect { zipTree(it.getFile()) }) {
// exclude all the signatures the jar might have
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
destinationDir file(project.distDir)
archiveName "jacocoant-" + version + ".jar"
}
jarifyTask.dependsOn v.getJavaCompiler()
v.assemble.dependsOn jarifyTask, collectJacocoAntPackages
}
}
// Enforce NewApi lint check as fatal.
project.android.lintOptions.check 'NewApi'
project.android.lintOptions.fatal 'NewApi'
project.parent.lint.dependsOn project.lint
}
if (isAndroidLibrary || isJavaLibrary) {
// Add library to the aggregate dependency report.
task allDeps(type: DependencyReportTask) {}
// Create release and separate zip task for library.
task release(type: Upload) {
configuration = configurations.archives
repositories {
mavenDeployer {
repository(url: uri("$rootProject.ext.supportRepoOut"))
// Disable unique names for SNAPSHOTS so they can be updated in place.
setUniqueVersion(false)
doLast {
// Remove any invalid maven-metadata.xml files that may have been
// created for SNAPSHOT versions that are *not* uniquely versioned.
pom*.each { pom ->
if (pom.version.endsWith('-SNAPSHOT')) {
final File artifactDir = new File(
rootProject.ext.supportRepoOut,
pom.groupId.replace('.', '/')
+ '/' + pom.artifactId
+ '/' + pom.version)
delete fileTree(dir: artifactDir,
include: 'maven-metadata.xml*')
}
}
}
}
}
}
def deployer = release.repositories.mavenDeployer
deployer.pom*.whenConfigured { pom ->
pom.dependencies.findAll { dep ->
dep.groupId == 'com.android.support' && dep.artifactId != 'support-annotations'
}*.type = 'aar'
}
ext.versionDir = {
def groupDir = new File(rootProject.ext.supportRepoOut,
project.group.replace('.','/'))
def artifactDir = new File(groupDir, archivesBaseName)
return new File(artifactDir, version)
}
task generateSourceProps(dependsOn: createRepository)
generateSourceProps.doLast({
def content = "Maven.GroupId=$deployer.pom.groupId\n" +
"Maven.ArtifactId=$deployer.pom.artifactId\n" +
"Maven.Version=$deployer.pom.version\n" +
"Extra.VendorDisplay=Android\n" +
"Extra.VendorId=android\n" +
"Pkg.Desc=$project.name\n" +
"Pkg.Revision=1\n" +
"Maven.Dependencies=" +
String.join(",", project.configurations.compile.allDependencies.collect {
def p = parent.findProject(it.name)
return p ? "$p.group:$p.archivesBaseName:$p.version" : null
}.grep()) +
"\n"
Files.write(content, new File(versionDir(), 'source.properties'), Charsets.UTF_8)
})
task createSeparateZip(type: Zip, dependsOn: generateSourceProps) {
into archivesBaseName
destinationDir project.parent.ext.distDir
baseName = project.group
version = project.parent.ext.buildNumber
}
project.parent.createArchive.dependsOn createSeparateZip
// Before the upload, make sure the repo is ready.
release.dependsOn rootProject.tasks.prepareRepo
// Make the mainupload depend on this one.
mainUpload.dependsOn release
}
}
project.afterEvaluate {
// The archivesBaseName isn't available intially, so set it now
def createZipTask = project.tasks.findByName("createSeparateZip")
if (createZipTask != null) {
createZipTask.appendix = archivesBaseName
createZipTask.from versionDir()
}
// Copy instrumentation test APK into the dist dir
def assembleTestTask = project.tasks.findByPath('assembleAndroidTest')
if (assembleTestTask != null) {
assembleTestTask.doLast {
// If the project actually has some instrumentation tests, copy its APK
if (!project.android.sourceSets.androidTest.java.sourceFiles.isEmpty()) {
def pkgTask = project.tasks.findByPath('packageDebugAndroidTest')
copy {
from(pkgTask.outputFile)
into(rootProject.ext.testApkDistOut)
}
}
}
}
}
project.afterEvaluate { p ->
// remove dependency on the test so that we still get coverage even if some tests fail
p.tasks.findAll { it instanceof JacocoReportTask}.each { task ->
def toBeRemoved = new ArrayList()
def dependencyList = task.taskDependencies.values
dependencyList.each { dep ->
if (dep instanceof String) {
def t = tasks.findByName(dep)
if (t instanceof DeviceProviderInstrumentTestTask) {
toBeRemoved.add(dep)
task.mustRunAfter(t)
}
}
}
toBeRemoved.each { dep ->
dependencyList.remove(dep)
}
}
}
project.afterEvaluate { p ->
if (p.hasProperty('android')
&& p.android.hasProperty('libraryVariants')
&& !(p.android.hasProperty('noDocs') && p.android.noDocs)) {
p.android.libraryVariants.all { v ->
if (v.name == 'release') {
registerForDocsTask(rootProject.generateDocs, p, v)
registerForDocsTask(rootProject.generateApi, p, v)
registerForDocsTask(rootProject.generateDiffs, p, v)
}
}
}
}
}
project.gradle.buildFinished { buildResult ->
if (buildResult.getFailure() != null) {
println()
println 'Build failed. Possible causes include:'
println ' 1) Bad codes'
println ' 2) Out of date prebuilts in prebuilts/sdk'
println ' 3) Need to update the compileSdkVersion in a library\'s build.gradle'
println()
}
}