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&nbsp;Library&nbsp;API&nbsp;Differences&nbsp;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