Home | History | Annotate | Download | only in support
      1 import android.support.doclava.DoclavaMultilineJavadocOptionFileOption
      2 import com.android.build.gradle.internal.coverage.JacocoReportTask
      3 import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
      4 
      5 import android.support.checkapi.CheckApiTask
      6 import android.support.checkapi.UpdateApiTask
      7 import android.support.doclava.DoclavaTask
      8 
      9 buildscript {
     10     repositories {
     11         maven { url '../../prebuilts/gradle-plugin' }
     12         maven { url '../../prebuilts/tools/common/m2/repository' }
     13         maven { url '../../prebuilts/tools/common/m2/internal' }
     14         maven { url "../../prebuilts/maven_repo/android" }
     15     }
     16     dependencies {
     17         classpath 'com.android.tools.build:gradle:2.2.0'
     18     }
     19 }
     20 
     21 repositories {
     22     maven { url '../../prebuilts/tools/common/m2/repository' }
     23 }
     24 
     25 configurations {
     26     doclava
     27 }
     28 
     29 dependencies {
     30     doclava project(':doclava')
     31 }
     32 
     33 ext.supportVersion = '25.0.0'
     34 ext.extraVersion = 39
     35 ext.supportRepoOut = ''
     36 ext.buildToolsVersion = '23.0.2'
     37 ext.buildNumber = Integer.toString(ext.extraVersion)
     38 
     39 ext.testRunnerVersion = '0.6-alpha'
     40 ext.espressoVersion = '2.3-alpha'
     41 
     42 // Enforce the use of prebuilt dependencies in all sub-projects. This is
     43 // required for the doclava dependency.
     44 ext.usePrebuilts = "true"
     45 
     46 /*
     47  * With the build server you are given two env variables.
     48  * The OUT_DIR is a temporary directory you can use to put things during the build.
     49  * The DIST_DIR is where you want to save things from the build.
     50  *
     51  * The build server will copy the contents of DIST_DIR to somewhere and make it available.
     52  */
     53 if (System.env.DIST_DIR != null && System.env.OUT_DIR != null) {
     54     buildDir = new File(System.env.OUT_DIR + '/gradle/frameworks/support/build').getCanonicalFile()
     55     project.ext.distDir = new File(System.env.DIST_DIR).getCanonicalFile()
     56 
     57     // the build server does not pass the build number so we infer it from the last folder of the dist path.
     58     ext.buildNumber = project.ext.distDir.getName()
     59 } else {
     60     buildDir = file("${project.rootDir}/../../out/host/gradle/frameworks/support/build")
     61     project.ext.distDir = file("${project.rootDir}/../../out/dist")
     62 }
     63 
     64 subprojects {
     65     // Change buildDir first so that all plugins pick up the new value.
     66     project.buildDir = project.file("$project.parent.buildDir/../$project.name/build")
     67 }
     68 
     69 ext.docsDir = new File(buildDir, 'javadoc')
     70 ext.supportRepoOut = new File(buildDir, 'support_repo')
     71 ext.testApkDistOut = ext.distDir
     72 
     73 // Main task called by the build server.
     74 task(createArchive) << {
     75 }
     76 
     77 // upload anchor for subprojects to upload their artifacts
     78 // to the local repo.
     79 task(mainUpload) << {
     80 }
     81 
     82 // repository creation task
     83 task createRepository(type: Zip, dependsOn: mainUpload) {
     84     from project.ext.supportRepoOut
     85     destinationDir project.ext.distDir
     86     into 'm2repository'
     87     baseName = String.format("sdk-repo-linux-m2repository-%s", project.ext.buildNumber)
     88 }
     89 createArchive.dependsOn createRepository
     90 
     91 // prepare repository with older versions
     92 task unzipRepo(type: Copy) {
     93     from "${project.rootDir}/../../prebuilts/maven_repo/android"
     94     into project.ext.supportRepoOut
     95 }
     96 
     97 unzipRepo.doFirst {
     98     project.ext.supportRepoOut.deleteDir()
     99     project.ext.supportRepoOut.mkdirs()
    100 }
    101 
    102 // anchor for prepare repo. This is post unzip + sourceProp.
    103 task(prepareRepo) << {
    104 }
    105 
    106 
    107 import android.support.build.ApiModule
    108 import com.google.common.io.Files
    109 import com.google.common.base.Charsets
    110 
    111 task(createXml) << {
    112     def repoArchive = createRepository.archivePath
    113     def repoArchiveName = createRepository.archiveName
    114     def size = repoArchive.length()
    115     def sha1 = getSha1(repoArchive)
    116 
    117     def xml =
    118 "<sdk:sdk-addon xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:sdk=\"http://schemas.android.com/sdk/android/addon/6\">\n\
    119   <sdk:extra>\n\
    120     <sdk:revision>\n\
    121       <sdk:major>${project.ext.extraVersion}</sdk:major>\n\
    122     </sdk:revision>\n\
    123     <sdk:vendor-display>Android</sdk:vendor-display>\n\
    124     <sdk:vendor-id>android</sdk:vendor-id>\n\
    125     <sdk:name-display>Local Maven repository for Support Libraries</sdk:name-display>\n\
    126     <sdk:path>m2repository</sdk:path>\n\
    127     <sdk:archives>\n\
    128       <sdk:archive>\n\
    129        <sdk:size>${size}</sdk:size>\n\
    130        <sdk:checksum type=\"sha1\">${sha1}</sdk:checksum>\n\
    131        <sdk:url>${repoArchiveName}</sdk:url>\n\
    132       </sdk:archive>\n\
    133     </sdk:archives>\n\
    134   </sdk:extra>\n\
    135 </sdk:sdk-addon>"
    136 
    137     Files.write(xml, new File(project.ext.distDir, 'repo-extras.xml'), Charsets.UTF_8)
    138 }
    139 createArchive.dependsOn createXml
    140 
    141 task(createSourceProp) << {
    142     def sourceProp =
    143 "Extra.VendorDisplay=Android\n\
    144 Extra.Path=m2repository\n\
    145 Archive.Arch=ANY\n\
    146 Extra.NameDisplay=Android Support Repository\n\
    147 Archive.Os=ANY\n\
    148 Pkg.Desc=Local Maven repository for Support Libraries\n\
    149 Pkg.Revision=${project.ext.extraVersion}.0.0\n\
    150 Extra.VendorId=android"
    151 
    152     Files.write(sourceProp, new File(project.ext.supportRepoOut, 'source.properties'), Charsets.UTF_8)
    153 }
    154 createSourceProp.dependsOn unzipRepo
    155 prepareRepo.dependsOn createSourceProp
    156 
    157 import com.google.common.hash.HashCode
    158 import com.google.common.hash.HashFunction
    159 import com.google.common.hash.Hashing
    160 import java.nio.charset.Charset
    161 
    162 /**
    163  * Generates SHA1 hash for the specified file's absolute path.
    164  *
    165  * @param inputFile file to hash
    166  * @return SHA1 hash
    167  */
    168 String getSha1(File inputFile) {
    169     HashFunction hashFunction = Hashing.sha1()
    170     HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath(), Charset.forName("UTF-8"))
    171     return hashCode.toString()
    172 }
    173 
    174 /**
    175  * Returns the Android prebuilt JAR for the specified API level.
    176  *
    177  * @param apiLevel the API level or "current"
    178  * @return a file collection containing the Android prebuilt JAR
    179  */
    180 FileCollection getAndroidPrebuilt(apiLevel) {
    181     files("${project.rootDir}/../../prebuilts/sdk/$apiLevel/android.jar")
    182 }
    183 
    184 /**
    185  * Populates the sub-project's set of source sets with the specified modules.
    186  *
    187  * @param subProject the sub-project to which the modules belong
    188  * @param apiModules the modules from which to populate
    189  */
    190 void createApiSourceSets(Project subProject, List<ApiModule> apiModules) {
    191     subProject.ext._apiModules = apiModules
    192     subProject.ext.allSS = []
    193     if (gradle.ext.studioCompat.enableApiModules) {
    194         // nothing to do, they are all modules
    195         return
    196     }
    197     // create a jar task for the api specific internal implementations
    198     def internalJar = subProject.tasks.create(name: "internalJar", type: Jar) {
    199         baseName "internal_impl"
    200     }
    201     apiModules.each { ApiModule apiModule ->
    202         apiModule.sourceSet = createApiSourceset(subProject, apiModule.folderName, apiModule.folderName,
    203                 apiModule.apiForSourceSet.toString(), apiModule.prev == null ? null : apiModule.prev.sourceSet)
    204         subProject.ext.allSS.add(apiModule.sourceSet)
    205     }
    206     subProject.android.libraryVariants.all { variant ->
    207         variant.javaCompile.dependsOn internalJar
    208     }
    209 }
    210 
    211 /**
    212  * Adds the specified module to the sub-project's set of source sets and
    213  * internal JAR. Also sets up dependencies, if supplied.
    214  *
    215  * @param subProject the sub-project to which the module belongs
    216  * @param name the name of the module
    217  * @param folder the module's source folder
    218  * @param apiLevel the module's compile API level
    219  * @param previousSource source set dependency (optional)
    220  * @return a source set for the module
    221  */
    222 SourceSet createApiSourceset(Project subProject, String name, String folder, String apiLevel,
    223                        SourceSet previousSource) {
    224     def sourceSet = subProject.sourceSets.create(name)
    225     sourceSet.java.srcDirs = [folder]
    226 
    227     // The Android gradle plugin doesn't touch Java sub-tasks, so we need to
    228     // manually set the Java task's boot classpath to the correct Android SDK.
    229     def compileJavaTaskName = sourceSet.getCompileJavaTaskName();
    230     def compileJavaOptions = subProject.tasks."${compileJavaTaskName}".options
    231     compileJavaOptions.bootClasspath = getAndroidPrebuilt(apiLevel)
    232 
    233     // Useful for cleaning up compiler warnings...
    234     //compileJavaOptions.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
    235 
    236     def configName = sourceSet.getCompileConfigurationName()
    237     subProject.getDependencies().add(configName, getAndroidPrebuilt(apiLevel))
    238     if (previousSource != null) {
    239         setupDependencies(subProject, configName, previousSource)
    240     }
    241     subProject.ext.allSS.add(sourceSet)
    242     subProject.tasks.internalJar.from sourceSet.output
    243     return sourceSet
    244 }
    245 
    246 /**
    247  * Adds the specified source set as a dependency for the sub-project.
    248  *
    249  * @param subProject the sub-project to modify
    250  * @param configName
    251  * @param previousSourceSet the source set to add as a dependency
    252  */
    253 void setupDependencies(Project subProject, String configName, SourceSet previousSourceSet) {
    254     subProject.getDependencies().add(configName, previousSourceSet.output)
    255     subProject.getDependencies().add(configName, previousSourceSet.compileClasspath)
    256 }
    257 
    258 void setApiModuleDependencies(Project subProject, DependencyHandler handler, List extraDeps) {
    259     if (gradle.ext.studioCompat.enableApiModules) {
    260         subProject.android.enforceUniquePackageName=false
    261         // add dependency on the latest module
    262         handler.compile(project(subProject.ext._apiModules.last().moduleName))
    263     } else {
    264         handler.compile(files(subProject.tasks.internalJar.archivePath))
    265         def firstModule = subProject.ext._apiModules[0]
    266         extraDeps.each { dep ->
    267             handler."${firstModule.folderName}Compile"(project(dep))
    268             handler.compile(project(dep))
    269         }
    270     }
    271 }
    272 
    273 void registerForDocsTask(Task task, Project subProject, releaseVariant) {
    274     task.dependsOn releaseVariant.javaCompile
    275     task.source {
    276         def buildConfig = fileTree(releaseVariant.getGenerateBuildConfig().sourceOutputDir)
    277         return releaseVariant.javaCompile.source.minus(buildConfig) +
    278             fileTree(releaseVariant.aidlCompile.sourceOutputDir) +
    279             fileTree(releaseVariant.outputs[0].processResources.sourceOutputDir)
    280     }
    281     task.classpath += files(releaseVariant.javaCompile.classpath) +
    282             files(releaseVariant.javaCompile.destinationDir)
    283 
    284     if (subProject.hasProperty('allSS')) {
    285         subProject.allSS.each { ss ->
    286             task.source ss.java
    287         }
    288     }
    289 }
    290 
    291 // Generates online docs.
    292 task generateDocs(type: DoclavaTask, dependsOn: configurations.doclava) {
    293     docletpath = configurations.doclava.resolve()
    294     destinationDir = new File(project.docsDir, "online")
    295 
    296     // Base classpath is Android SDK, sub-projects add their own.
    297     classpath = getAndroidPrebuilt(gradle.ext.currentSdk)
    298 
    299     def hdfOption = new DoclavaMultilineJavadocOptionFileOption('hdf')
    300     hdfOption.add(
    301             ['android.whichdoc', 'online'],
    302             ['android.hasSamples', 'true']);
    303 
    304     options {
    305         addStringOption "templatedir",
    306                 "${project.rootDir}/../../build/tools/droiddoc/templates-sdk"
    307         addStringOption "federate Android", "http://developer.android.com"
    308         addStringOption "federationapi Android",
    309                 "${project.rootDir}/../../prebuilts/sdk/api/24.txt"
    310         addStringOption "stubpackages", "android.support.*"
    311         addStringOption "samplesdir", "${project.rootDir}/samples"
    312         addOption hdfOption
    313     }
    314 
    315     exclude '**/BuildConfig.java'
    316 }
    317 
    318 // Generates API files.
    319 task generateApi(type: DoclavaTask, dependsOn: configurations.doclava) {
    320     docletpath = configurations.doclava.resolve()
    321     destinationDir = project.docsDir
    322 
    323     // Base classpath is Android SDK, sub-projects add their own.
    324     classpath = getAndroidPrebuilt(gradle.ext.currentSdk)
    325 
    326     apiFile = new File(project.docsDir, 'release/current.txt')
    327     removedApiFile = new File(project.docsDir, 'release/removed.txt')
    328     generateDocs = false
    329 
    330     options {
    331         addStringOption "templatedir",
    332                 "${project.rootDir}/../../build/tools/droiddoc/templates-sdk"
    333         addStringOption "federate Android", "http://developer.android.com"
    334         addStringOption "federationapi Android",
    335                 "${project.rootDir}/../../prebuilts/sdk/api/24.txt"
    336         addStringOption "stubpackages", "android.support.*"
    337     }
    338     exclude '**/BuildConfig.java'
    339     exclude '**/R.java'
    340 }
    341 
    342 // Copies generated API files to current version.
    343 task updateApi(type: UpdateApiTask, dependsOn: generateApi) {
    344     newApiFile = new File(project.docsDir, 'release/current.txt')
    345     oldApiFile = new File(project.rootDir, 'api/current.txt')
    346     newRemovedApiFile = new File(project.docsDir, 'release/removed.txt')
    347     oldRemovedApiFile = new File(project.rootDir, 'api/removed.txt')
    348 }
    349 
    350 // Checks generated API files against current version.
    351 task checkApi(type: CheckApiTask, dependsOn: generateApi) {
    352     doclavaClasspath = generateApi.docletpath
    353 
    354     checkApiTaskPath = name
    355     updateApiTaskPath = updateApi.name
    356 
    357     newApiFile = new File(project.docsDir, 'release/current.txt')
    358     oldApiFile = new File(project.rootDir, 'api/current.txt')
    359     newRemovedApiFile = new File(project.docsDir, 'release/removed.txt')
    360     oldRemovedApiFile = new File(project.rootDir, 'api/removed.txt')
    361 }
    362 createArchive.dependsOn checkApi
    363 
    364 subprojects {
    365     // Only modify android projects.
    366     if (project.name.equals('doclava')) return;
    367 
    368     // current SDK is set in studioCompat.gradle
    369     project.ext.currentSdk = gradle.ext.currentSdk
    370     apply plugin: 'maven'
    371     project.ext.createApiSourceSets = this.&createApiSourceset
    372     project.ext.setApiModuleDependencies = this.&setApiModuleDependencies
    373 
    374     version = rootProject.ext.supportVersion
    375     group = 'com.android.support'
    376 
    377     repositories {
    378         maven { url "${project.parent.projectDir}/../../prebuilts/tools/common/m2/repository" }
    379         maven { url "${project.parent.projectDir}/../../prebuilts/tools/common/m2/internal" }
    380         maven { url "${project.parent.projectDir}/../../prebuilts/maven_repo/android" }
    381     }
    382 
    383     project.plugins.whenPluginAdded { plugin ->
    384         if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)
    385                 || "com.android.build.gradle.AppPlugin".equals(plugin.class.name)) {
    386             project.android.buildToolsVersion = rootProject.buildToolsVersion
    387             // enable code coverage for debug builds only if we are not running inside the IDE
    388             // enabling coverage reports breaks the method parameter resolution in the IDE debugger
    389             project.android.buildTypes.debug.testCoverageEnabled = !hasProperty('android.injected.invoked.from.ide')
    390         }
    391 
    392         // Create release and separate zip task for Android libraries (and android-annotations,
    393         // which is just a Java library).
    394         if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)
    395                 || "org.gradle.api.plugins.JavaPlugin".equals(plugin.class.name)) {
    396             task release(type: Upload) {
    397                 configuration = configurations.archives
    398                 repositories {
    399                     mavenDeployer {
    400                         repository(url: uri("$rootProject.ext.supportRepoOut"))
    401 
    402                         // Disable unique names for SNAPSHOTS so they can be updated in place.
    403                         setUniqueVersion(false)
    404                         doLast {
    405                             // Remove any invalid maven-metadata.xml files that may have been
    406                             // created for SNAPSHOT versions that are *not* uniquely versioned.
    407                             pom*.each { pom ->
    408                                 if (pom.version.endsWith('-SNAPSHOT')) {
    409                                     final File artifactDir = new File(
    410                                             rootProject.ext.supportRepoOut,
    411                                             pom.groupId.replace('.', '/')
    412                                                     + '/' + pom.artifactId
    413                                                     + '/' + pom.version)
    414                                     delete fileTree(dir: artifactDir,
    415                                             include: 'maven-metadata.xml*')
    416                                 }
    417                             }
    418                         }
    419                     }
    420                 }
    421             }
    422 
    423             def deployer = release.repositories.mavenDeployer
    424             deployer.pom*.whenConfigured { pom ->
    425                 pom.dependencies.findAll { dep ->
    426                     dep.groupId == 'com.android.support' && dep.artifactId != 'support-annotations'
    427                 }*.type = 'aar'
    428             }
    429 
    430             ext.versionDir = {
    431                 def groupDir = new File(rootProject.ext.supportRepoOut,
    432                         project.group.replace('.','/'))
    433                 def artifactDir = new File(groupDir, archivesBaseName)
    434                 return new File(artifactDir, version)
    435             }
    436 
    437             task generateSourceProps(dependsOn: createRepository) << {
    438                 def content = "Maven.GroupId=$deployer.pom.groupId\n" +
    439                         "Maven.ArtifactId=$deployer.pom.artifactId\n" +
    440                         "Maven.Version=$deployer.pom.version\n" +
    441                         "Extra.VendorDisplay=Android\n" +
    442                         "Extra.VendorId=android\n" +
    443                         "Pkg.Desc=$project.name\n" +
    444                         "Pkg.Revision=1\n" +
    445                         "Maven.Dependencies=" +
    446                         String.join(",", project.configurations.compile.allDependencies.collect {
    447                             def p = parent.findProject(it.name)
    448                             return p ? "$p.group:$p.archivesBaseName:$p.version" : null
    449                         }.grep()) +
    450                         "\n"
    451                 Files.write(content, new File(versionDir(), 'source.properties'), Charsets.UTF_8)
    452             }
    453 
    454             task createSeparateZip(type: Zip, dependsOn: generateSourceProps)  {
    455                 into archivesBaseName
    456                 destinationDir project.parent.ext.distDir
    457                 baseName = project.group
    458                 version = project.parent.ext.buildNumber
    459             }
    460             project.parent.createArchive.dependsOn createSeparateZip
    461 
    462             // before the upload, make sure the repo is ready.
    463             release.dependsOn rootProject.tasks.prepareRepo
    464             // make the mainupload depend on this one.
    465             mainUpload.dependsOn release
    466         }
    467     }
    468 
    469     project.afterEvaluate {
    470         // The archivesBaseName isn't available intially, so set it now
    471         def createZipTask = project.tasks.findByName("createSeparateZip")
    472         if (createZipTask != null) {
    473             createZipTask.appendix = archivesBaseName
    474             createZipTask.from versionDir()
    475         }
    476 
    477         // Copy instrumentation test APK into the dist dir
    478         def assembleTestTask = project.tasks.findByPath('assembleAndroidTest')
    479         if (assembleTestTask != null) {
    480             assembleTestTask.doLast {
    481                 // If the project actually has some instrumentation tests, copy its APK
    482                 if (!project.android.sourceSets.androidTest.java.sourceFiles.isEmpty()) {
    483                     def pkgTask = project.tasks.findByPath('packageDebugAndroidTest')
    484                     copy {
    485                         from(pkgTask.outputFile)
    486                         into(rootProject.ext.testApkDistOut)
    487                     }
    488                 }
    489             }
    490         }
    491     }
    492 
    493     project.afterEvaluate { p ->
    494         // remove dependency on the test so that we still get coverage even if some tests fail
    495         p.tasks.findAll { it instanceof JacocoReportTask}.each { task ->
    496             def toBeRemoved = new ArrayList()
    497             def dependencyList = task.taskDependencies.values
    498             dependencyList.each { dep ->
    499                 if (dep instanceof String) {
    500                     def t = tasks.findByName(dep)
    501                     if (t instanceof DeviceProviderInstrumentTestTask) {
    502                         toBeRemoved.add(dep)
    503                         task.mustRunAfter(t)
    504                     }
    505                 }
    506             }
    507             toBeRemoved.each { dep ->
    508                 dependencyList.remove(dep)
    509             }
    510         }
    511     }
    512 
    513     project.afterEvaluate { p ->
    514         if (p.hasProperty('android')
    515                 && p.android.hasProperty('libraryVariants')
    516                 && !(p.android.hasProperty('noDocs') && p.android.noDocs)) {
    517             p.android.libraryVariants.all { v ->
    518                 if (v.name == 'release') {
    519                     registerForDocsTask(rootProject.generateDocs, p, v)
    520                     registerForDocsTask(rootProject.generateApi, p, v)
    521                 }
    522             }
    523         }
    524     }
    525 }
    526 
    527 project.gradle.buildFinished { buildResult ->
    528     if (buildResult.getFailure() != null) {
    529         println()
    530         println 'Build failed. Possible causes include:'
    531         println '    1) Bad codes'
    532         println '    2) Out of date prebuilts in prebuilts/sdk'
    533         println '    3) Need to update the compileSdkVersion in a library\'s build.gradle'
    534         println()
    535     }
    536 }
    537