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