1 /*
<lambda>null2 * 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
17 package androidx.build
18
19 import androidx.build.Strategy.Prebuilts
20 import androidx.build.Strategy.TipOfTree
21 import androidx.build.checkapi.ApiXmlConversionTask
22 import androidx.build.checkapi.CheckApiTask
23 import androidx.build.checkapi.UpdateApiTask
24 import androidx.build.doclava.DoclavaTask
25 import androidx.build.doclava.DEFAULT_DOCLAVA_CONFIG
26 import androidx.build.doclava.CHECK_API_CONFIG_DEVELOP
27 import androidx.build.doclava.CHECK_API_CONFIG_RELEASE
28 import androidx.build.doclava.CHECK_API_CONFIG_PATCH
29 import androidx.build.doclava.ChecksConfig
30 import androidx.build.docs.GenerateDocsTask
31 import androidx.build.jdiff.JDiffTask
32 import com.android.build.gradle.AppExtension
33 import com.android.build.gradle.LibraryExtension
34 import com.android.build.gradle.api.BaseVariant
35 import com.android.build.gradle.api.LibraryVariant
36 import org.gradle.api.GradleException
37 import org.gradle.api.Project
38 import org.gradle.api.Task
39 import org.gradle.api.artifacts.Configuration
40 import org.gradle.api.artifacts.ResolveException
41 import org.gradle.api.file.FileCollection
42 import org.gradle.api.file.FileTree
43 import org.gradle.api.plugins.JavaBasePlugin
44 import org.gradle.api.tasks.TaskContainer
45 import org.gradle.api.tasks.bundling.Zip
46 import org.gradle.api.tasks.compile.JavaCompile
47 import org.gradle.api.tasks.javadoc.Javadoc
48 import java.io.File
49 import kotlin.collections.Collection
50 import kotlin.collections.List
51 import kotlin.collections.MutableMap
52 import kotlin.collections.emptyList
53 import kotlin.collections.filter
54 import kotlin.collections.find
55 import kotlin.collections.forEach
56 import kotlin.collections.listOf
57 import kotlin.collections.mapNotNull
58 import kotlin.collections.minus
59 import kotlin.collections.mutableMapOf
60 import kotlin.collections.plus
61 import kotlin.collections.set
62 import kotlin.collections.toSet
63
64 data class DacOptions(val libraryroot: String, val dataname: String)
65
66 object 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")
hasJavaSourcesnull274 private fun LibraryVariant.hasJavaSources() = !javaCompile.source
275 .filter { file -> file.name != "R.java" && file.name != "BuildConfig.java" }
276 .isEmpty
277
Projectnull278 fun Project.hasApiFolder() = File(projectDir, "api").exists()
279
280 private fun stripExtension(fileName: String) = fileName.substringBeforeLast('.')
281
282 private 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
getLastReleasedApiFileFromDirnull292 private 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
getApiFilenull308 private 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 */
getApiFilenull318 private 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
createGenerateApiTasknull331 private 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
createCheckApiTasknull348 private 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 */
registerJavaProjectForDocsTasknull381 private 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 */
registerAndroidProjectForDocsTasknull394 private 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 */
createUpdateApiTasknull420 private 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 */
createOldApiXmlnull444 private 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 */
createNewApiXmlTasknull470 private 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 */
createGenerateDiffsTasknull520 private 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.
createDistDocsTasknull549 private 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 */
createGenerateSdkApiTasknull568 private 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
583 private val GENERATEDOCS_HIDDEN = listOf(105, 106, 107, 111, 112, 113, 115, 116, 121)
584 private val GENERATE_DOCS_CONFIG = ChecksConfig(
585 warnings = emptyList(),
586 hidden = GENERATEDOCS_HIDDEN + DEFAULT_DOCLAVA_CONFIG.hidden,
587 errors = ((101..122) - GENERATEDOCS_HIDDEN)
588 )
589
createGenerateDocsTasknull590 private 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
643 private data class Tasks(
644 val generateApi: DoclavaTask,
645 val generateDiffs: JDiffTask,
646 val checkApiTask: CheckApiTask
647 )
648
initializeApiChecksForProjectnull649 private 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
hasApiTasksnull710 fun 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
sdkApiFilenull724 private fun sdkApiFile(project: Project) = File(project.docsDir(), "release/sdk_current.txt")
725
726 private 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
androidJarFilenull733 private fun androidJarFile(project: Project): FileCollection =
734 project.files(arrayOf(File(project.fullSdkPath(),
735 "platforms/android-${SupportConfig.CURRENT_SDK_VERSION}/android.jar")))
736
737 private fun androidSrcJarFile(project: Project): File = File(project.fullSdkPath(),
738 "platforms/android-${SupportConfig.CURRENT_SDK_VERSION}/android-stubs-src.jar")
739
740 private fun PublishDocsRules.resolve(extension: SupportLibraryExtension) =
741 resolve(extension.mavenGroup!!, extension.project.name)
742
743 private fun Prebuilts.dependency(extension: SupportLibraryExtension) =
744 "${extension.mavenGroup}:${extension.project.name}:$version"
745
746 private fun BaseVariant.rFile() = "${applicationId.replace('.', '/')}/R.java"
747
748 // Nasty part. Get rid of that eventually!
749 private fun Project.docsDir(): File = properties["docsDir"] as File
750
751 private fun Project.fullSdkPath(): File = rootProject.properties["fullSdkPath"] as File
752
753 private fun Project.version() = Version(project.version as String)
754
755 private fun Project.buildNumber() = properties["buildNumber"] as String
756
757 private fun Project.distDir(): File = rootProject.properties["distDir"] as File
758
759 private fun Project.processProperty(name: String) =
760 if (hasProperty(name)) {
761 properties[name] as String
762 } else {
763 null
764 }
765