1/* 2 * Copyright 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.build 18 19import androidx.build.Strategy.Prebuilts 20import androidx.build.Strategy.TipOfTree 21import androidx.build.checkapi.ApiXmlConversionTask 22import androidx.build.checkapi.CheckApiTask 23import androidx.build.checkapi.UpdateApiTask 24import androidx.build.doclava.DoclavaTask 25import androidx.build.doclava.DEFAULT_DOCLAVA_CONFIG 26import androidx.build.doclava.CHECK_API_CONFIG_DEVELOP 27import androidx.build.doclava.CHECK_API_CONFIG_RELEASE 28import androidx.build.doclava.CHECK_API_CONFIG_PATCH 29import androidx.build.doclava.ChecksConfig 30import androidx.build.docs.GenerateDocsTask 31import androidx.build.jdiff.JDiffTask 32import com.android.build.gradle.AppExtension 33import com.android.build.gradle.LibraryExtension 34import com.android.build.gradle.api.BaseVariant 35import com.android.build.gradle.api.LibraryVariant 36import org.gradle.api.GradleException 37import org.gradle.api.Project 38import org.gradle.api.Task 39import org.gradle.api.artifacts.Configuration 40import org.gradle.api.artifacts.ResolveException 41import org.gradle.api.file.FileCollection 42import org.gradle.api.file.FileTree 43import org.gradle.api.plugins.JavaBasePlugin 44import org.gradle.api.tasks.TaskContainer 45import org.gradle.api.tasks.bundling.Zip 46import org.gradle.api.tasks.compile.JavaCompile 47import org.gradle.api.tasks.javadoc.Javadoc 48import java.io.File 49import kotlin.collections.Collection 50import kotlin.collections.List 51import kotlin.collections.MutableMap 52import kotlin.collections.emptyList 53import kotlin.collections.filter 54import kotlin.collections.find 55import kotlin.collections.forEach 56import kotlin.collections.listOf 57import kotlin.collections.mapNotNull 58import kotlin.collections.minus 59import kotlin.collections.mutableMapOf 60import kotlin.collections.plus 61import kotlin.collections.set 62import kotlin.collections.toSet 63 64data class DacOptions(val libraryroot: String, val dataname: String) 65 66object DiffAndDocs { 67 private lateinit var anchorTask: Task 68 private var docsProject: Project? = null 69 70 private lateinit var rules: List<PublishDocsRules> 71 private val docsTasks: MutableMap<String, GenerateDocsTask> = mutableMapOf() 72 73 @JvmStatic 74 fun configureDiffAndDocs( 75 root: Project, 76 supportRootFolder: File, 77 dacOptions: DacOptions, 78 additionalRules: List<PublishDocsRules> = emptyList() 79 ): Task { 80 rules = additionalRules + TIP_OF_TREE 81 docsProject = root.findProject(":docs-fake") 82 anchorTask = root.tasks.create("anchorDocsTask") 83 val doclavaConfiguration = root.configurations.getByName("doclava") 84 val generateSdkApiTask = createGenerateSdkApiTask(root, doclavaConfiguration) 85 rules.forEach { 86 val task = createGenerateDocsTask( 87 project = root, generateSdkApiTask = generateSdkApiTask, 88 doclavaConfig = doclavaConfiguration, 89 supportRootFolder = supportRootFolder, dacOptions = dacOptions, 90 destDir = File(root.docsDir(), it.name), 91 taskName = "${it.name}DocsTask") 92 docsTasks[it.name] = task 93 anchorTask.dependsOn(createDistDocsTask(root, task, it.name)) 94 } 95 96 root.tasks.create("generateDocs").dependsOn(docsTasks[TIP_OF_TREE.name]) 97 98 setupDocsProject() 99 return anchorTask 100 } 101 102 private fun prebuiltSources( 103 root: Project, 104 mavenId: String, 105 originName: String, 106 originRule: DocsRule 107 ): FileTree { 108 val configName = "docs-temp_$mavenId" 109 val configuration = root.configurations.create(configName) 110 root.dependencies.add(configName, mavenId) 111 112 val artifacts = try { 113 configuration.resolvedConfiguration.resolvedArtifacts 114 } catch (e: ResolveException) { 115 throw GradleException("Failed to find prebuilts for $mavenId. " + 116 "A matching rule $originRule in docsRules(\"$originName\") " + 117 "in PublishDocsRules.kt requires it. You should either add a prebuilt, " + 118 "or add overriding \"ignore\" or \"tipOfTree\" rules", e) 119 } 120 121 val artifact = artifacts.find { it.moduleVersion.id.toString() == mavenId } 122 ?: throw GradleException() 123 124 val folder = artifact.file.parentFile 125 val tree = root.zipTree(File(folder, "${artifact.file.nameWithoutExtension}-sources.jar")) 126 .matching { 127 it.exclude("**/*.MF") 128 it.exclude("**/*.aidl") 129 it.exclude("**/*.html") 130 it.exclude("**/*.kt") 131 } 132 root.configurations.remove(configuration) 133 return tree 134 } 135 136 private fun setupDocsProject() { 137 docsProject?.afterEvaluate { docs -> 138 val appExtension = docs.extensions.findByType(AppExtension::class.java) 139 ?: throw GradleException("Android app plugin is missing on docsProject") 140 141 rules.forEach { rule -> 142 appExtension.productFlavors.create(rule.name) { 143 it.dimension = "library-group" 144 } 145 } 146 appExtension.applicationVariants.all { v -> 147 val task = docsTasks[v.flavorName] 148 if (v.buildType.name == "release" && task != null) { 149 registerAndroidProjectForDocsTask(task, v) 150 task.exclude { fileTreeElement -> 151 fileTreeElement.path.endsWith(v.rFile()) 152 } 153 } 154 } 155 } 156 157 docsProject?.rootProject?.subprojects 158 ?.filter { docsProject != it } 159 ?.forEach { docsProject?.evaluationDependsOn(it.path) } 160 } 161 162 private fun registerPrebuilts(extension: SupportLibraryExtension) = 163 docsProject?.afterEvaluate { docs -> 164 val depHandler = docs.dependencies 165 val root = docs.rootProject 166 rules.forEach { rule -> 167 val resolvedRule = rule.resolve(extension) 168 val strategy = resolvedRule.strategy 169 if (strategy is Prebuilts) { 170 val dependency = strategy.dependency(extension) 171 depHandler.add("${rule.name}Implementation", dependency) 172 strategy.stubs?.forEach { path -> 173 depHandler.add("${rule.name}CompileOnly", root.files(path)) 174 } 175 docsTasks[rule.name]!!.source(prebuiltSources(root, dependency, 176 rule.name, resolvedRule)) 177 } 178 } 179 } 180 181 private fun tipOfTreeTasks(extension: SupportLibraryExtension, setup: (DoclavaTask) -> Unit) { 182 rules.filter { rule -> rule.resolve(extension).strategy == TipOfTree } 183 .mapNotNull { rule -> docsTasks[rule.name] } 184 .forEach(setup) 185 } 186 187 /** 188 * Registers a Java project for global docs generation, local API file generation, and 189 * local API diff generation tasks. 190 */ 191 fun registerJavaProject(project: Project, extension: SupportLibraryExtension) { 192 if (!hasApiTasks(project, extension)) { 193 return 194 } 195 val compileJava = project.properties["compileJava"] as JavaCompile 196 197 registerPrebuilts(extension) 198 199 tipOfTreeTasks(extension) { task -> 200 registerJavaProjectForDocsTask(task, compileJava) 201 } 202 203 if (!project.hasApiFolder()) { 204 project.logger.info("Project ${project.name} doesn't have an api folder, " + 205 "ignoring API tasks.") 206 return 207 } 208 val tasks = initializeApiChecksForProject(project) 209 registerJavaProjectForDocsTask(tasks.generateApi, compileJava) 210 registerJavaProjectForDocsTask(tasks.generateDiffs, compileJava) 211 setupDocsTasks(project, tasks) 212 anchorTask.dependsOn(tasks.checkApiTask) 213 } 214 215 /** 216 * Registers an Android project for global docs generation, local API file generation, and 217 * local API diff generation tasks. 218 */ 219 fun registerAndroidProject( 220 project: Project, 221 library: LibraryExtension, 222 extension: SupportLibraryExtension 223 ) { 224 if (!hasApiTasks(project, extension)) { 225 return 226 } 227 228 registerPrebuilts(extension) 229 230 library.libraryVariants.all { variant -> 231 if (variant.name == "release") { 232 // include R.file generated for prebuilts 233 rules.filter { it.resolve(extension).strategy is Prebuilts }.forEach { rule -> 234 docsTasks[rule.name]?.include { fileTreeElement -> 235 fileTreeElement.path.endsWith(variant.rFile()) 236 } 237 } 238 239 tipOfTreeTasks(extension) { task -> 240 registerAndroidProjectForDocsTask(task, variant) 241 } 242 243 if (!variant.hasJavaSources()) { 244 return@all 245 } 246 if (!project.hasApiFolder()) { 247 project.logger.info("Project ${project.name} doesn't have " + 248 "an api folder, ignoring API tasks.") 249 return@all 250 } 251 val tasks = initializeApiChecksForProject(project) 252 registerAndroidProjectForDocsTask(tasks.generateApi, variant) 253 registerAndroidProjectForDocsTask(tasks.generateDiffs, variant) 254 setupDocsTasks(project, tasks) 255 anchorTask.dependsOn(tasks.checkApiTask) 256 } 257 } 258 } 259 260 private fun setupDocsTasks(project: Project, tasks: Tasks) { 261 docsTasks.values.forEach { docs -> 262 tasks.generateDiffs.dependsOn(docs) 263 // Track API change history. 264 docs.addSinceFilesFrom(project.projectDir) 265 // Associate current API surface with the Maven artifact. 266 val artifact = "${project.group}:${project.name}:${project.version}" 267 docs.addArtifact(tasks.generateApi.apiFile!!.absolutePath, artifact) 268 docs.dependsOn(tasks.generateApi) 269 } 270 } 271} 272 273@Suppress("DEPRECATION") 274private fun LibraryVariant.hasJavaSources() = !javaCompile.source 275 .filter { file -> file.name != "R.java" && file.name != "BuildConfig.java" } 276 .isEmpty 277 278fun Project.hasApiFolder() = File(projectDir, "api").exists() 279 280private fun stripExtension(fileName: String) = fileName.substringBeforeLast('.') 281 282private fun getLastReleasedApiFile(rootFolder: File, refVersion: Version?): File? { 283 val apiDir = File(rootFolder, "api") 284 val lastFile = getLastReleasedApiFileFromDir(apiDir, refVersion) 285 if (lastFile != null) { 286 return lastFile 287 } 288 289 return null 290} 291 292private fun getLastReleasedApiFileFromDir(apiDir: File, refVersion: Version?): File? { 293 var lastFile: File? = null 294 var lastVersion: Version? = null 295 apiDir.listFiles().forEach { file -> 296 Version.parseOrNull(file)?.let { version -> 297 if ((lastFile == null || lastVersion!! < version) && 298 (refVersion == null || version < refVersion)) { 299 lastFile = file 300 lastVersion = version 301 } 302 } 303 } 304 305 return lastFile 306} 307 308private fun getApiFile(rootDir: File, refVersion: Version): File { 309 return getApiFile(rootDir, refVersion, false) 310} 311 312/** 313 * Returns the API file for the specified reference version. 314 * 315 * @param refVersion the reference API version, ex. 25.0.0-SNAPSHOT 316 * @return the most recently released API file 317 */ 318private fun getApiFile(rootDir: File, refVersion: Version, forceRelease: Boolean = false): File { 319 val apiDir = File(rootDir, "api") 320 321 if (refVersion.isFinalApi() || forceRelease) { 322 // Release API file is always X.Y.0.txt. 323 return File(apiDir, "${refVersion.major}.${refVersion.minor}.0.txt") 324 } 325 326 // Non-release API file is always current.txt. 327 return File(apiDir, "current.txt") 328} 329 330// Generates API files 331private fun createGenerateApiTask(project: Project, docletpathParam: Collection<File>) = 332 project.tasks.createWithConfig("generateApi", DoclavaTask::class.java) { 333 setDocletpath(docletpathParam) 334 destinationDir = project.docsDir() 335 // Base classpath is Android SDK, sub-projects add their own. 336 classpath = androidJarFile(project) 337 apiFile = File(project.docsDir(), "release/${project.name}/current.txt") 338 generateDocs = false 339 340 coreJavadocOptions { 341 addBooleanOption("stubsourceonly", true) 342 } 343 344 exclude("**/BuildConfig.java") 345 exclude("**/R.java") 346 } 347 348private fun createCheckApiTask( 349 project: Project, 350 taskName: String, 351 docletpath: Collection<File>, 352 config: ChecksConfig, 353 oldApi: File?, 354 newApi: File, 355 whitelist: File? = null 356) = 357 project.tasks.createWithConfig(taskName, CheckApiTask::class.java) { 358 doclavaClasspath = docletpath 359 checksConfig = config 360 newApiFile = newApi 361 oldApiFile = oldApi 362 whitelistErrorsFile = whitelist 363 doFirst { 364 logger.lifecycle("Verifying ${newApi.name} " + 365 "against ${oldApi?.name ?: "nothing"}...") 366 } 367 } 368 369/** 370 * Registers a Java project on the given Javadocs task. 371 * <p> 372 * <ul> 373 * <li>Sets up a dependency to ensure the project is compiled prior to running the task 374 * <li>Adds the project's source files to the Javadoc task's source files 375 * <li>Adds the project's compilation classpath (e.g. dependencies) to the task classpath to ensure 376 * that references in the source files may be resolved 377 * <li>Adds the project's output artifacts to the task classpath to ensure that source references to 378 * generated code may be resolved 379 * </ul> 380 */ 381private fun registerJavaProjectForDocsTask(task: Javadoc, javaCompileTask: JavaCompile) { 382 task.dependsOn(javaCompileTask) 383 task.source(javaCompileTask.source) 384 val project = task.project 385 task.classpath += project.files(javaCompileTask.classpath) + 386 project.files(javaCompileTask.destinationDir) 387} 388 389/** 390 * Registers an Android project on the given Javadocs task. 391 * <p> 392 * @see #registerJavaProjectForDocsTask 393 */ 394private fun registerAndroidProjectForDocsTask(task: Javadoc, releaseVariant: BaseVariant) { 395 // This code makes a number of unsafe assumptions about Android Gradle Plugin, 396 // and there's a good chance that this will break in the near future. 397 @Suppress("DEPRECATION") 398 task.dependsOn(releaseVariant.javaCompile) 399 task.include { fileTreeElement -> 400 fileTreeElement.name != "R.java" || fileTreeElement.path.endsWith(releaseVariant.rFile()) } 401 @Suppress("DEPRECATION") 402 task.source(releaseVariant.javaCompile.source) 403 @Suppress("DEPRECATION") 404 task.classpath += releaseVariant.getCompileClasspath(null) + 405 task.project.files(releaseVariant.javaCompile.destinationDir) 406} 407 408/** 409 * Constructs a new task to copy a generated API file to an appropriately-named "official" API file 410 * suitable for source control. This task should be called prior to source control check-in whenever 411 * the public API has been modified. 412 * <p> 413 * The output API file varies according to version: 414 * <ul> 415 * <li>Snapshot and pre-release versions (e.g. X.Y.Z-SNAPSHOT, X.Y.Z-alphaN) output to current.txt 416 * <li>Release versions (e.g. X.Y.Z) output to X.Y.0.txt, throwing an exception if the API has been 417 * finalized and the file already exists 418 * </ul> 419 */ 420private fun createUpdateApiTask(project: Project, checkApiRelease: CheckApiTask) = 421 project.tasks.createWithConfig("updateApi", UpdateApiTask::class.java) { 422 group = JavaBasePlugin.VERIFICATION_GROUP 423 description = "Updates the candidate API file to incorporate valid changes." 424 newApiFile = checkApiRelease.newApiFile 425 oldApiFile = getApiFile(project.projectDir, project.version()) 426 whitelistErrors = checkApiRelease.whitelistErrors 427 whitelistErrorsFile = checkApiRelease.whitelistErrorsFile 428 doFirst { 429 val version = project.version() 430 if (!version.isFinalApi() && 431 getApiFile(project.projectDir, version, true).exists()) { 432 throw GradleException("Inconsistent version. Public API file already exists.") 433 } 434 // Replace the expected whitelist with the detected whitelist. 435 whitelistErrors = checkApiRelease.detectedWhitelistErrors 436 } 437 } 438 439/** 440 * Converts the <code>fromApi</code>.txt file (or the most recently released 441 * X.Y.Z.txt if not explicitly defined using -PfromAPi=<file>) to XML format 442 * for use by JDiff. 443 */ 444private fun createOldApiXml(project: Project, doclavaConfig: Configuration) = 445 project.tasks.createWithConfig("oldApiXml", ApiXmlConversionTask::class.java) { 446 val toApi = project.processProperty("toApi")?.let { 447 Version.parseOrNull(it) 448 } 449 val fromApi = project.processProperty("fromApi") 450 classpath = project.files(doclavaConfig.resolve()) 451 val rootFolder = project.projectDir 452 inputApiFile = if (fromApi != null) { 453 // Use an explicit API file. 454 File(rootFolder, "api/$fromApi.txt") 455 } else { 456 // Use the most recently released API file bounded by toApi. 457 getLastReleasedApiFile(rootFolder, toApi) 458 } 459 460 outputApiXmlFile = File(project.docsDir(), 461 "release/${stripExtension(inputApiFile?.name ?: "creation")}.xml") 462 463 dependsOn(doclavaConfig) 464 } 465 466/** 467 * Converts the <code>toApi</code>.txt file (or current.txt if not explicitly 468 * defined using -PtoApi=<file>) to XML format for use by JDiff. 469 */ 470private fun createNewApiXmlTask( 471 project: Project, 472 generateApi: DoclavaTask, 473 doclavaConfig: Configuration 474) = 475 project.tasks.createWithConfig("newApiXml", ApiXmlConversionTask::class.java) { 476 classpath = project.files(doclavaConfig.resolve()) 477 val toApi = project.processProperty("toApi") 478 479 if (toApi != null) { 480 // Use an explicit API file. 481 inputApiFile = File(project.projectDir, "api/$toApi.txt") 482 } else { 483 // Use the current API file (e.g. current.txt). 484 inputApiFile = generateApi.apiFile!! 485 dependsOn(generateApi, doclavaConfig) 486 } 487 488 outputApiXmlFile = File(project.docsDir(), 489 "release/${stripExtension(inputApiFile?.name ?: "creation")}.xml") 490 } 491 492/** 493 * Generates API diffs. 494 * <p> 495 * By default, diffs are generated for the delta between current.txt and the 496 * next most recent X.Y.Z.txt API file. Behavior may be changed by specifying 497 * one or both of -PtoApi and -PfromApi. 498 * <p> 499 * If both fromApi and toApi are specified, diffs will be generated for 500 * fromApi -> toApi. For example, 25.0.0 -> 26.0.0 diffs could be generated by 501 * using: 502 * <br><code> 503 * ./gradlew generateDiffs -PfromApi=25.0.0 -PtoApi=26.0.0 504 * </code> 505 * <p> 506 * If only toApi is specified, it MUST be specified as X.Y.Z and diffs will be 507 * generated for (release before toApi) -> toApi. For example, 24.2.0 -> 25.0.0 508 * diffs could be generated by using: 509 * <br><code> 510 * ./gradlew generateDiffs -PtoApi=25.0.0 511 * </code> 512 * <p> 513 * If only fromApi is specified, diffs will be generated for fromApi -> current. 514 * For example, lastApiReview -> current diffs could be generated by using: 515 * <br><code> 516 * ./gradlew generateDiffs -PfromApi=lastApiReview 517 * </code> 518 * <p> 519 */ 520private fun createGenerateDiffsTask( 521 project: Project, 522 oldApiTask: ApiXmlConversionTask, 523 newApiTask: ApiXmlConversionTask, 524 jdiffConfig: Configuration 525): JDiffTask = 526 project.tasks.createWithConfig("generateDiffs", JDiffTask::class.java) { 527 // Base classpath is Android SDK, sub-projects add their own. 528 classpath = androidJarFile(project) 529 530 // JDiff properties. 531 oldApiXmlFile = oldApiTask.outputApiXmlFile 532 newApiXmlFile = newApiTask.outputApiXmlFile 533 534 val newApi = newApiXmlFile.name.substringBeforeLast('.') 535 val docsDir = project.rootProject.docsDir() 536 537 newJavadocPrefix = "../../../../../reference/" 538 destinationDir = File(docsDir, "online/sdk/support_api_diff/${project.name}/$newApi") 539 540 // Javadoc properties. 541 docletpath = jdiffConfig.resolve() 542 title = "Support Library API Differences Report" 543 544 exclude("**/BuildConfig.java", "**/R.java") 545 dependsOn(oldApiTask, newApiTask, jdiffConfig) 546 } 547 548// Generates a distribution artifact for online docs. 549private fun createDistDocsTask(project: Project, generateDocs: DoclavaTask, ruleName: String = ""): Zip = 550 project.tasks.createWithConfig("dist${ruleName}Docs", Zip::class.java) { 551 dependsOn(generateDocs) 552 group = JavaBasePlugin.DOCUMENTATION_GROUP 553 description = "Generates distribution artifact for d.android.com-style documentation." 554 from(generateDocs.destinationDir) 555 baseName = "android-support-$ruleName-docs" 556 version = project.buildNumber() 557 destinationDir = project.distDir() 558 doLast { 559 logger.lifecycle("'Wrote API reference to $archivePath") 560 } 561 } 562 563/** 564 * Creates a task to generate an API file from the platform SDK's source and stub JARs. 565 * <p> 566 * This is useful for federating docs against the platform SDK when no API XML file is available. 567 */ 568private fun createGenerateSdkApiTask(project: Project, doclavaConfig: Configuration): DoclavaTask = 569 project.tasks.createWithConfig("generateSdkApi", DoclavaTask::class.java) { 570 dependsOn(doclavaConfig) 571 description = "Generates API files for the current SDK." 572 setDocletpath(doclavaConfig.resolve()) 573 destinationDir = project.docsDir() 574 classpath = androidJarFile(project) 575 source(project.zipTree(androidSrcJarFile(project))) 576 apiFile = sdkApiFile(project) 577 generateDocs = false 578 coreJavadocOptions { 579 addStringOption("stubpackages", "android.*") 580 } 581 } 582 583private val GENERATEDOCS_HIDDEN = listOf(105, 106, 107, 111, 112, 113, 115, 116, 121) 584private val GENERATE_DOCS_CONFIG = ChecksConfig( 585 warnings = emptyList(), 586 hidden = GENERATEDOCS_HIDDEN + DEFAULT_DOCLAVA_CONFIG.hidden, 587 errors = ((101..122) - GENERATEDOCS_HIDDEN) 588) 589 590private fun createGenerateDocsTask( 591 project: Project, 592 generateSdkApiTask: DoclavaTask, 593 doclavaConfig: Configuration, 594 supportRootFolder: File, 595 dacOptions: DacOptions, 596 destDir: File, 597 taskName: String = "generateDocs" 598): GenerateDocsTask = 599 project.tasks.createWithConfig(taskName, GenerateDocsTask::class.java) { 600 dependsOn(generateSdkApiTask, doclavaConfig) 601 group = JavaBasePlugin.DOCUMENTATION_GROUP 602 description = "Generates d.android.com-style documentation. To generate offline docs " + 603 "use \'-PofflineDocs=true\' parameter." 604 605 setDocletpath(doclavaConfig.resolve()) 606 val offline = project.processProperty("offlineDocs") != null 607 destinationDir = File(destDir, if (offline) "offline" else "online") 608 classpath = androidJarFile(project) 609 checksConfig = GENERATE_DOCS_CONFIG 610 addSinceFilesFrom(supportRootFolder) 611 612 coreJavadocOptions { 613 addStringOption("templatedir", 614 "$supportRootFolder/../../external/doclava/res/assets/templates-sdk") 615 addStringOption("samplesdir", "$supportRootFolder/samples") 616 addMultilineMultiValueOption("federate").value = listOf( 617 listOf("Android", "https://developer.android.com") 618 ) 619 addMultilineMultiValueOption("federationapi").value = listOf( 620 listOf("Android", generateSdkApiTask.apiFile?.absolutePath) 621 ) 622 addMultilineMultiValueOption("hdf").value = listOf( 623 listOf("android.whichdoc", "online"), 624 listOf("android.hasSamples", "true"), 625 listOf("dac", "true") 626 ) 627 628 // Specific to reference docs. 629 if (!offline) { 630 addStringOption("toroot", "/") 631 addBooleanOption("devsite", true) 632 addBooleanOption("yamlV2", true) 633 addStringOption("dac_libraryroot", dacOptions.libraryroot) 634 addStringOption("dac_dataname", dacOptions.dataname) 635 } 636 637 exclude("**/BuildConfig.java") 638 } 639 640 addArtifactsAndSince() 641 } 642 643private data class Tasks( 644 val generateApi: DoclavaTask, 645 val generateDiffs: JDiffTask, 646 val checkApiTask: CheckApiTask 647) 648 649private fun initializeApiChecksForProject(project: Project): Tasks { 650 if (!project.hasProperty("docsDir")) { 651 project.extensions.add("docsDir", File(project.rootProject.docsDir(), project.name)) 652 } 653 val version = project.version() 654 val workingDir = project.projectDir 655 656 val doclavaConfiguration = project.rootProject.configurations.getByName("doclava") 657 val docletClasspath = doclavaConfiguration.resolve() 658 val generateApi = createGenerateApiTask(project, docletClasspath) 659 generateApi.dependsOn(doclavaConfiguration) 660 661 // Make sure the API surface has not broken since the last release. 662 val lastReleasedApiFile = getLastReleasedApiFile(workingDir, version) 663 664 val whitelistFile = lastReleasedApiFile?.let { apiFile -> 665 File(lastReleasedApiFile.parentFile, stripExtension(apiFile.name) + ".ignore") 666 } 667 val checkApiRelease = createCheckApiTask(project, 668 "checkApiRelease", 669 docletClasspath, 670 CHECK_API_CONFIG_RELEASE, 671 lastReleasedApiFile, 672 generateApi.apiFile!!, 673 whitelistFile) 674 checkApiRelease.dependsOn(generateApi) 675 676 // Allow a comma-delimited list of whitelisted errors. 677 if (project.hasProperty("ignore")) { 678 checkApiRelease.whitelistErrors = (project.properties["ignore"] as String) 679 .split(',').toSet() 680 } 681 682 // Check whether the development API surface has changed. 683 val verifyConfig = if (version.isPatch()) CHECK_API_CONFIG_PATCH else CHECK_API_CONFIG_DEVELOP 684 val currentApiFile = getApiFile(workingDir, version) 685 val checkApi = createCheckApiTask(project, 686 "checkApi", 687 docletClasspath, 688 verifyConfig, 689 currentApiFile, 690 generateApi.apiFile!!, 691 null) 692 checkApi.dependsOn(generateApi, checkApiRelease) 693 694 checkApi.group = JavaBasePlugin.VERIFICATION_GROUP 695 checkApi.description = "Verify the API surface." 696 697 val updateApiTask = createUpdateApiTask(project, checkApiRelease) 698 updateApiTask.dependsOn(checkApiRelease) 699 val newApiTask = createNewApiXmlTask(project, generateApi, doclavaConfiguration) 700 val oldApiTask = createOldApiXml(project, doclavaConfiguration) 701 702 val jdiffConfiguration = project.rootProject.configurations.getByName("jdiff") 703 val generateDiffTask = createGenerateDiffsTask(project, 704 oldApiTask, 705 newApiTask, 706 jdiffConfiguration) 707 return Tasks(generateApi, generateDiffTask, checkApi) 708} 709 710fun hasApiTasks(project: Project, extension: SupportLibraryExtension): Boolean { 711 if (!extension.publish) { 712 project.logger.info("Project ${project.name} is not published, ignoring API tasks.") 713 return false 714 } 715 716 if (!extension.generateDocs) { 717 project.logger.info("Project ${project.name} specified generateDocs = false, " + 718 "ignoring API tasks.") 719 return false 720 } 721 return true 722} 723 724private fun sdkApiFile(project: Project) = File(project.docsDir(), "release/sdk_current.txt") 725 726private fun <T : Task> TaskContainer.createWithConfig( 727 name: String, 728 taskClass: Class<T>, 729 config: T.() -> Unit 730) = 731 create(name, taskClass) { task -> task.config() } 732 733private fun androidJarFile(project: Project): FileCollection = 734 project.files(arrayOf(File(project.fullSdkPath(), 735 "platforms/android-${SupportConfig.CURRENT_SDK_VERSION}/android.jar"))) 736 737private fun androidSrcJarFile(project: Project): File = File(project.fullSdkPath(), 738 "platforms/android-${SupportConfig.CURRENT_SDK_VERSION}/android-stubs-src.jar") 739 740private fun PublishDocsRules.resolve(extension: SupportLibraryExtension) = 741 resolve(extension.mavenGroup!!, extension.project.name) 742 743private fun Prebuilts.dependency(extension: SupportLibraryExtension) = 744 "${extension.mavenGroup}:${extension.project.name}:$version" 745 746private fun BaseVariant.rFile() = "${applicationId.replace('.', '/')}/R.java" 747 748// Nasty part. Get rid of that eventually! 749private fun Project.docsDir(): File = properties["docsDir"] as File 750 751private fun Project.fullSdkPath(): File = rootProject.properties["fullSdkPath"] as File 752 753private fun Project.version() = Version(project.version as String) 754 755private fun Project.buildNumber() = properties["buildNumber"] as String 756 757private fun Project.distDir(): File = rootProject.properties["distDir"] as File 758 759private fun Project.processProperty(name: String) = 760 if (hasProperty(name)) { 761 properties[name] as String 762 } else { 763 null 764 } 765