| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.build |
| |
| import androidx.build.Strategy.Prebuilts |
| import androidx.build.Strategy.TipOfTree |
| import androidx.build.checkapi.ApiXmlConversionTask |
| import androidx.build.checkapi.CheckApiTask |
| import androidx.build.checkapi.UpdateApiTask |
| import androidx.build.doclava.DoclavaTask |
| import androidx.build.doclava.DEFAULT_DOCLAVA_CONFIG |
| import androidx.build.doclava.CHECK_API_CONFIG_DEVELOP |
| import androidx.build.doclava.CHECK_API_CONFIG_RELEASE |
| import androidx.build.doclava.CHECK_API_CONFIG_PATCH |
| import androidx.build.doclava.ChecksConfig |
| import androidx.build.docs.ConcatenateFilesTask |
| import androidx.build.docs.GenerateDocsTask |
| import androidx.build.jdiff.JDiffTask |
| import com.android.build.gradle.AppExtension |
| import com.android.build.gradle.LibraryExtension |
| import com.android.build.gradle.api.BaseVariant |
| import com.android.build.gradle.api.LibraryVariant |
| import org.gradle.api.GradleException |
| import org.gradle.api.Project |
| import org.gradle.api.Task |
| import org.gradle.api.artifacts.Configuration |
| import org.gradle.api.artifacts.ResolveException |
| import org.gradle.api.file.FileCollection |
| import org.gradle.api.file.FileTree |
| import org.gradle.api.plugins.JavaBasePlugin |
| import org.gradle.api.tasks.TaskContainer |
| import org.gradle.api.tasks.bundling.Zip |
| import org.gradle.api.tasks.compile.JavaCompile |
| import org.gradle.api.tasks.javadoc.Javadoc |
| import java.io.File |
| import java.time.LocalDateTime |
| import java.time.format.DateTimeFormatter |
| import kotlin.collections.Collection |
| import kotlin.collections.List |
| import kotlin.collections.MutableMap |
| import kotlin.collections.emptyList |
| import kotlin.collections.filter |
| import kotlin.collections.find |
| import kotlin.collections.forEach |
| import kotlin.collections.listOf |
| import kotlin.collections.mapNotNull |
| import kotlin.collections.minus |
| import kotlin.collections.mutableMapOf |
| import kotlin.collections.plus |
| import kotlin.collections.set |
| import kotlin.collections.toSet |
| |
| data class DacOptions(val libraryroot: String, val dataname: String) |
| |
| object DiffAndDocs { |
| private lateinit var anchorTask: Task |
| private var docsProject: Project? = null |
| |
| private lateinit var rules: List<PublishDocsRules> |
| private val docsTasks: MutableMap<String, GenerateDocsTask> = mutableMapOf() |
| private lateinit var aggregateOldApiTxtsTask: ConcatenateFilesTask |
| private lateinit var aggregateNewApiTxtsTask: ConcatenateFilesTask |
| private lateinit var generateDiffsTask: JDiffTask |
| |
| /** |
| * Initialization that should happen only once (and on the root project) |
| */ |
| @JvmStatic |
| fun configureDiffAndDocs( |
| root: Project, |
| supportRootFolder: File, |
| dacOptions: DacOptions, |
| additionalRules: List<PublishDocsRules> = emptyList() |
| ): Task { |
| rules = additionalRules + TIP_OF_TREE |
| docsProject = root.findProject(":docs-fake") |
| anchorTask = root.tasks.create("anchorDocsTask") |
| val doclavaConfiguration = root.configurations.getByName("doclava") |
| val generateSdkApiTask = createGenerateSdkApiTask(root, doclavaConfiguration) |
| val now = LocalDateTime.now() |
| // The diff output assumes that each library is of the same version, but our libraries may each be of different versions |
| // So, we display the date as the new version |
| val newVersion = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) |
| rules.forEach { |
| val task = createGenerateDocsTask( |
| project = root, generateSdkApiTask = generateSdkApiTask, |
| doclavaConfig = doclavaConfiguration, |
| supportRootFolder = supportRootFolder, dacOptions = dacOptions, |
| destDir = File(root.docsDir(), it.name), |
| taskName = "${it.name}DocsTask") |
| docsTasks[it.name] = task |
| anchorTask.dependsOn(createDistDocsTask(root, task, it.name)) |
| } |
| |
| root.tasks.create("generateDocs").dependsOn(docsTasks[TIP_OF_TREE.name]) |
| |
| val docletClasspath = doclavaConfiguration.resolve() |
| |
| aggregateOldApiTxtsTask = root.tasks.create("aggregateOldApiTxts", ConcatenateFilesTask::class.java) |
| aggregateOldApiTxtsTask.Output = File(root.docsDir(), "previous.txt") |
| |
| val oldApisTask = root.tasks.createWithConfig("oldApisXml", ApiXmlConversionTask::class.java) { |
| classpath = root.files(docletClasspath) |
| dependsOn(doclavaConfiguration) |
| |
| inputApiFile = aggregateOldApiTxtsTask.Output |
| dependsOn(aggregateOldApiTxtsTask) |
| |
| outputApiXmlFile = File(root.docsDir(), "previous.xml") |
| } |
| |
| aggregateNewApiTxtsTask = root.tasks.create("aggregateNewApiTxts", ConcatenateFilesTask::class.java) |
| aggregateNewApiTxtsTask.Output = File(root.docsDir(), "$newVersion") |
| |
| val newApisTask = root.tasks.createWithConfig("newApisXml", ApiXmlConversionTask::class.java) { |
| classpath = root.files(docletClasspath) |
| |
| inputApiFile = aggregateNewApiTxtsTask.Output |
| dependsOn(aggregateNewApiTxtsTask) |
| |
| outputApiXmlFile = File(root.docsDir(), "$newVersion.xml") |
| } |
| |
| val jdiffConfiguration = root.configurations.getByName("jdiff") |
| generateDiffsTask = createGenerateDiffsTask(root, |
| oldApisTask, |
| newApisTask, |
| jdiffConfiguration) |
| |
| setupDocsProject() |
| |
| return anchorTask |
| } |
| |
| private fun prebuiltSources( |
| root: Project, |
| mavenId: String, |
| originName: String, |
| originRule: DocsRule |
| ): FileTree { |
| val configName = "docs-temp_$mavenId" |
| val configuration = root.configurations.create(configName) |
| root.dependencies.add(configName, mavenId) |
| |
| val artifacts = try { |
| configuration.resolvedConfiguration.resolvedArtifacts |
| } catch (e: ResolveException) { |
| throw GradleException("Failed to find prebuilts for $mavenId. " + |
| "A matching rule $originRule in docsRules(\"$originName\") " + |
| "in PublishDocsRules.kt requires it. You should either add a prebuilt, " + |
| "or add overriding \"ignore\" or \"tipOfTree\" rules", e) |
| } |
| |
| val artifact = artifacts.find { it.moduleVersion.id.toString() == mavenId } |
| ?: throw GradleException() |
| |
| val folder = artifact.file.parentFile |
| val tree = root.zipTree(File(folder, "${artifact.file.nameWithoutExtension}-sources.jar")) |
| .matching { |
| it.exclude("**/*.MF") |
| it.exclude("**/*.aidl") |
| it.exclude("**/*.html") |
| it.exclude("**/*.kt") |
| } |
| root.configurations.remove(configuration) |
| return tree |
| } |
| |
| private fun setupDocsProject() { |
| docsProject?.afterEvaluate { docs -> |
| val appExtension = docs.extensions.findByType(AppExtension::class.java) |
| ?: throw GradleException("Android app plugin is missing on docsProject") |
| |
| rules.forEach { rule -> |
| appExtension.productFlavors.create(rule.name) { |
| it.dimension = "library-group" |
| } |
| } |
| appExtension.applicationVariants.all { v -> |
| val task = docsTasks[v.flavorName] |
| if (v.buildType.name == "release" && task != null) { |
| registerAndroidProjectForDocsTask(task, v) |
| task.exclude { fileTreeElement -> |
| fileTreeElement.path.endsWith(v.rFile()) |
| } |
| } |
| } |
| } |
| |
| docsProject?.rootProject?.subprojects |
| ?.filter { docsProject != it } |
| ?.forEach { docsProject?.evaluationDependsOn(it.path) } |
| } |
| |
| private fun registerPrebuilts(extension: SupportLibraryExtension) = |
| docsProject?.afterEvaluate { docs -> |
| val depHandler = docs.dependencies |
| val root = docs.rootProject |
| rules.forEach { rule -> |
| val resolvedRule = rule.resolve(extension) |
| val strategy = resolvedRule.strategy |
| if (strategy is Prebuilts) { |
| val dependency = strategy.dependency(extension) |
| depHandler.add("${rule.name}Implementation", dependency) |
| strategy.stubs?.forEach { path -> |
| depHandler.add("${rule.name}CompileOnly", root.files(path)) |
| } |
| docsTasks[rule.name]!!.source(prebuiltSources(root, dependency, |
| rule.name, resolvedRule)) |
| } |
| } |
| } |
| |
| private fun tipOfTreeTasks(extension: SupportLibraryExtension, setup: (DoclavaTask) -> Unit) { |
| rules.filter { rule -> rule.resolve(extension).strategy == TipOfTree } |
| .mapNotNull { rule -> docsTasks[rule.name] } |
| .forEach(setup) |
| } |
| |
| /** |
| * Registers a Java project to be included in docs generation, local API file generation, and |
| * local API diff generation tasks. |
| */ |
| fun registerJavaProject(project: Project, extension: SupportLibraryExtension) { |
| if (!hasApiTasks(project, extension)) { |
| return |
| } |
| val compileJava = project.properties["compileJava"] as JavaCompile |
| |
| registerPrebuilts(extension) |
| |
| tipOfTreeTasks(extension) { task -> |
| registerJavaProjectForDocsTask(task, compileJava) |
| } |
| |
| if (!project.hasApiFolder()) { |
| project.logger.info("Project ${project.name} doesn't have an api folder, " + |
| "ignoring API tasks.") |
| return |
| } |
| val tasks = initializeApiChecksForProject(project, aggregateOldApiTxtsTask, aggregateNewApiTxtsTask) |
| registerJavaProjectForDocsTask(tasks.generateApi, compileJava) |
| registerJavaProjectForDocsTask(generateDiffsTask, compileJava) |
| setupDocsTasks(project, tasks) |
| anchorTask.dependsOn(tasks.checkApiTask) |
| } |
| |
| /** |
| * Registers an Android project to be included in global docs generation, local API file |
| * generation, and local API diff generation tasks. |
| */ |
| fun registerAndroidProject( |
| project: Project, |
| library: LibraryExtension, |
| extension: SupportLibraryExtension |
| ) { |
| if (!hasApiTasks(project, extension)) { |
| return |
| } |
| |
| registerPrebuilts(extension) |
| |
| library.libraryVariants.all { variant -> |
| if (variant.name == "release") { |
| // include R.file generated for prebuilts |
| rules.filter { it.resolve(extension).strategy is Prebuilts }.forEach { rule -> |
| docsTasks[rule.name]?.include { fileTreeElement -> |
| fileTreeElement.path.endsWith(variant.rFile()) |
| } |
| } |
| |
| tipOfTreeTasks(extension) { task -> |
| registerAndroidProjectForDocsTask(task, variant) |
| } |
| |
| if (!variant.hasJavaSources()) { |
| return@all |
| } |
| if (!project.hasApiFolder()) { |
| project.logger.info("Project ${project.name} doesn't have " + |
| "an api folder, ignoring API tasks.") |
| return@all |
| } |
| val tasks = initializeApiChecksForProject(project, aggregateOldApiTxtsTask, aggregateNewApiTxtsTask) |
| registerAndroidProjectForDocsTask(tasks.generateApi, variant) |
| registerAndroidProjectForDocsTask(generateDiffsTask, variant) |
| setupDocsTasks(project, tasks) |
| anchorTask.dependsOn(tasks.checkApiTask) |
| } |
| } |
| } |
| |
| private fun setupDocsTasks(project: Project, tasks: Tasks) { |
| docsTasks.values.forEach { docs -> |
| generateDiffsTask.dependsOn(docs) |
| // Track API change history. |
| docs.addSinceFilesFrom(project.projectDir) |
| // Associate current API surface with the Maven artifact. |
| val artifact = "${project.group}:${project.name}:${project.version}" |
| docs.addArtifact(tasks.generateApi.apiFile!!.absolutePath, artifact) |
| docs.dependsOn(tasks.generateApi) |
| } |
| } |
| } |
| |
| @Suppress("DEPRECATION") |
| private fun LibraryVariant.hasJavaSources() = !javaCompile.source |
| .filter { file -> file.name != "R.java" && file.name != "BuildConfig.java" } |
| .isEmpty |
| |
| fun Project.hasApiFolder() = File(projectDir, "api").exists() |
| |
| private fun stripExtension(fileName: String) = fileName.substringBeforeLast('.') |
| |
| private fun getLastReleasedApiFile(rootFolder: File, refVersion: Version?): File? { |
| val apiDir = File(rootFolder, "api") |
| return getLastReleasedApiFileFromDir(apiDir, refVersion) |
| } |
| |
| /** |
| * Returns the api file with highest version among those having version less than refVersion |
| */ |
| private fun getLastReleasedApiFileFromDir(apiDir: File, refVersion: Version?): File? { |
| var lastFile: File? = null |
| var lastVersion: Version? = null |
| apiDir.listFiles().forEach { file -> |
| val parsed = Version.parseOrNull(file) |
| parsed?.let { version -> |
| if ((lastFile == null || lastVersion!! < version) |
| && (refVersion == null || version < refVersion)) { |
| lastFile = file |
| lastVersion = version |
| } |
| } |
| } |
| |
| return lastFile |
| } |
| |
| private fun getApiFile(rootDir: File, refVersion: Version): File { |
| return getApiFile(rootDir, refVersion, false) |
| } |
| |
| /** |
| * Returns the API file for the specified reference version. |
| * |
| * @param refVersion the reference API version, ex. 25.0.0-SNAPSHOT |
| * @return the most recently released API file |
| */ |
| private fun getApiFile(rootDir: File, refVersion: Version, forceRelease: Boolean = false): File { |
| val apiDir = File(rootDir, "api") |
| |
| if (refVersion.isFinalApi() || forceRelease) { |
| // Release API file is always X.Y.0.txt. |
| return File(apiDir, "${refVersion.major}.${refVersion.minor}.0.txt") |
| } |
| |
| // Non-release API file is always current.txt. |
| return File(apiDir, "current.txt") |
| } |
| |
| |
| // Creates a new task on the project for generating API files |
| private fun createGenerateApiTask(project: Project, docletpathParam: Collection<File>) = |
| project.tasks.createWithConfig("generateApi", DoclavaTask::class.java) { |
| setDocletpath(docletpathParam) |
| destinationDir = project.docsDir() |
| // Base classpath is Android SDK, sub-projects add their own. |
| classpath = androidJarFile(project) |
| apiFile = File(project.docsDir(), "release/${project.name}/current.txt") |
| generateDocs = false |
| |
| coreJavadocOptions { |
| addBooleanOption("stubsourceonly", true) |
| } |
| |
| exclude("**/BuildConfig.java") |
| exclude("**/R.java") |
| } |
| |
| // Creates a new task on the project for verifying the API |
| private fun createCheckApiTask( |
| project: Project, |
| taskName: String, |
| docletpath: Collection<File>, |
| config: ChecksConfig, |
| oldApi: File?, |
| newApi: File, |
| whitelist: File? = null |
| ) = |
| project.tasks.createWithConfig(taskName, CheckApiTask::class.java) { |
| doclavaClasspath = docletpath |
| checksConfig = config |
| newApiFile = newApi |
| oldApiFile = oldApi |
| whitelistErrorsFile = whitelist |
| doFirst { |
| logger.lifecycle("Verifying ${newApi.name} " + |
| "against ${oldApi?.name ?: "nothing"}...") |
| } |
| } |
| |
| /** |
| * Registers a Java project on the given Javadocs task. |
| * <p> |
| * <ul> |
| * <li>Sets up a dependency to ensure the project is compiled prior to running the task |
| * <li>Adds the project's source files to the Javadoc task's source files |
| * <li>Adds the project's compilation classpath (e.g. dependencies) to the task classpath to ensure |
| * that references in the source files may be resolved |
| * <li>Adds the project's output artifacts to the task classpath to ensure that source references to |
| * generated code may be resolved |
| * </ul> |
| */ |
| private fun registerJavaProjectForDocsTask(task: Javadoc, javaCompileTask: JavaCompile) { |
| task.dependsOn(javaCompileTask) |
| task.source(javaCompileTask.source) |
| val project = task.project |
| task.classpath += project.files(javaCompileTask.classpath) + |
| project.files(javaCompileTask.destinationDir) |
| } |
| |
| /** |
| * Registers an Android project on the given Javadocs task. |
| * <p> |
| * @see #registerJavaProjectForDocsTask |
| */ |
| private fun registerAndroidProjectForDocsTask(task: Javadoc, releaseVariant: BaseVariant) { |
| // This code makes a number of unsafe assumptions about Android Gradle Plugin, |
| // and there's a good chance that this will break in the near future. |
| @Suppress("DEPRECATION") |
| task.dependsOn(releaseVariant.javaCompile) |
| task.include { fileTreeElement -> |
| fileTreeElement.name != "R.java" || fileTreeElement.path.endsWith(releaseVariant.rFile()) } |
| @Suppress("DEPRECATION") |
| task.source(releaseVariant.javaCompile.source) |
| @Suppress("DEPRECATION") |
| task.classpath += releaseVariant.getCompileClasspath(null) + |
| task.project.files(releaseVariant.javaCompile.destinationDir) |
| } |
| |
| /** |
| * Constructs a new task to copy a generated API file to an appropriately-named "official" API file |
| * suitable for source control. This task should be called prior to source control check-in whenever |
| * the public API has been modified. |
| * <p> |
| * The output API file varies according to version: |
| * <ul> |
| * <li>Snapshot and pre-release versions (e.g. X.Y.Z-SNAPSHOT, X.Y.Z-alphaN) output to current.txt |
| * <li>Release versions (e.g. X.Y.Z) output to X.Y.0.txt, throwing an exception if the API has been |
| * finalized and the file already exists |
| * </ul> |
| */ |
| private fun createUpdateApiTask(project: Project, checkApiRelease: CheckApiTask) = |
| project.tasks.createWithConfig("updateApi", UpdateApiTask::class.java) { |
| group = JavaBasePlugin.VERIFICATION_GROUP |
| description = "Updates the candidate API file to incorporate valid changes." |
| newApiFile = checkApiRelease.newApiFile |
| oldApiFile = getApiFile(project.projectDir, project.version()) |
| whitelistErrors = checkApiRelease.whitelistErrors |
| whitelistErrorsFile = checkApiRelease.whitelistErrorsFile |
| doFirst { |
| val version = project.version() |
| if (!version.isFinalApi() && |
| getApiFile(project.projectDir, version, true).exists()) { |
| throw GradleException("Inconsistent version. Public API file already exists.") |
| } |
| // Replace the expected whitelist with the detected whitelist. |
| whitelistErrors = checkApiRelease.detectedWhitelistErrors |
| } |
| } |
| |
| /** |
| * Returns the filepath of the previous API txt file (for computing diffs against) |
| */ |
| private fun getOldApiTxt(project: Project): File? { |
| val toApi = project.processProperty("toApi")?.let { |
| Version.parseOrNull(it) |
| } |
| val fromApi = project.processProperty("fromApi") |
| val rootFolder = project.projectDir |
| if (fromApi != null) { |
| // Use an explicit API file. |
| return File(rootFolder, "api/$fromApi.txt") |
| } else { |
| // Use the most recently released API file bounded by toApi. |
| return getLastReleasedApiFile(rootFolder, toApi) |
| } |
| } |
| |
| |
| data class FileProvider(val file: File, val task: Task?) |
| |
| private fun getNewApiTxt(project: Project, generateApi: DoclavaTask): FileProvider { |
| val toApi = project.processProperty("toApi") |
| if (toApi != null) { |
| // Use an explicit API file. |
| return FileProvider(File(project.projectDir, "api/$toApi.txt"), null) |
| } else { |
| // Use the current API file (e.g. current.txt). |
| return FileProvider(generateApi.apiFile!!, generateApi) |
| } |
| |
| } |
| |
| /** |
| * Generates API diffs. |
| * <p> |
| * By default, diffs are generated for the delta between current.txt and the |
| * next most recent X.Y.Z.txt API file. Behavior may be changed by specifying |
| * one or both of -PtoApi and -PfromApi. |
| * <p> |
| * If both fromApi and toApi are specified, diffs will be generated for |
| * fromApi -> toApi. For example, 25.0.0 -> 26.0.0 diffs could be generated by |
| * using: |
| * <br><code> |
| * ./gradlew generateDiffs -PfromApi=25.0.0 -PtoApi=26.0.0 |
| * </code> |
| * <p> |
| * If only toApi is specified, it MUST be specified as X.Y.Z and diffs will be |
| * generated for (release before toApi) -> toApi. For example, 24.2.0 -> 25.0.0 |
| * diffs could be generated by using: |
| * <br><code> |
| * ./gradlew generateDiffs -PtoApi=25.0.0 |
| * </code> |
| * <p> |
| * If only fromApi is specified, diffs will be generated for fromApi -> current. |
| * For example, lastApiReview -> current diffs could be generated by using: |
| * <br><code> |
| * ./gradlew generateDiffs -PfromApi=lastApiReview |
| * </code> |
| * <p> |
| */ |
| private fun createGenerateDiffsTask( |
| project: Project, |
| oldApiTask: ApiXmlConversionTask, |
| newApiTask: ApiXmlConversionTask, |
| jdiffConfig: Configuration |
| ): JDiffTask = |
| project.tasks.createWithConfig("generateDiffs", JDiffTask::class.java) { |
| // Base classpath is Android SDK, sub-projects add their own. |
| classpath = androidJarFile(project) |
| |
| // JDiff properties. |
| oldApiXmlFile = oldApiTask.outputApiXmlFile |
| newApiXmlFile = newApiTask.outputApiXmlFile |
| |
| val newApi = newApiXmlFile.name.substringBeforeLast('.') |
| val docsDir = File(project.rootProject.docsDir(), "public") |
| |
| newJavadocPrefix = "../../../../../reference/" |
| destinationDir = File(docsDir, "online/sdk/support_api_diff/${project.name}/$newApi") |
| |
| // Javadoc properties. |
| docletpath = jdiffConfig.resolve() |
| title = "Support Library API Differences Report" |
| |
| exclude("**/BuildConfig.java", "**/R.java") |
| dependsOn(oldApiTask, newApiTask, jdiffConfig) |
| doLast { |
| project.logger.lifecycle("generated diffs into $destinationDir") |
| } |
| } |
| |
| // Generates a distribution artifact for online docs. |
| private fun createDistDocsTask(project: Project, generateDocs: DoclavaTask, ruleName: String = ""): Zip = |
| project.tasks.createWithConfig("dist${ruleName}Docs", Zip::class.java) { |
| dependsOn(generateDocs) |
| group = JavaBasePlugin.DOCUMENTATION_GROUP |
| description = "Generates distribution artifact for d.android.com-style documentation." |
| from(generateDocs.destinationDir) |
| baseName = "android-support-$ruleName-docs" |
| version = project.buildNumber() |
| destinationDir = project.distDir() |
| doLast { |
| logger.lifecycle("'Wrote API reference to $archivePath") |
| } |
| } |
| |
| /** |
| * Creates a task to generate an API file from the platform SDK's source and stub JARs. |
| * <p> |
| * This is useful for federating docs against the platform SDK when no API XML file is available. |
| */ |
| private fun createGenerateSdkApiTask(project: Project, doclavaConfig: Configuration): DoclavaTask = |
| project.tasks.createWithConfig("generateSdkApi", DoclavaTask::class.java) { |
| dependsOn(doclavaConfig) |
| description = "Generates API files for the current SDK." |
| setDocletpath(doclavaConfig.resolve()) |
| destinationDir = project.docsDir() |
| classpath = androidJarFile(project) |
| source(project.zipTree(androidSrcJarFile(project))) |
| apiFile = sdkApiFile(project) |
| generateDocs = false |
| coreJavadocOptions { |
| addStringOption("stubpackages", "android.*") |
| } |
| } |
| |
| private val GENERATEDOCS_HIDDEN = listOf(105, 106, 107, 111, 112, 113, 115, 116, 121) |
| private val GENERATE_DOCS_CONFIG = ChecksConfig( |
| warnings = emptyList(), |
| hidden = GENERATEDOCS_HIDDEN + DEFAULT_DOCLAVA_CONFIG.hidden, |
| errors = ((101..122) - GENERATEDOCS_HIDDEN) |
| ) |
| |
| private fun createGenerateDocsTask( |
| project: Project, |
| generateSdkApiTask: DoclavaTask, |
| doclavaConfig: Configuration, |
| supportRootFolder: File, |
| dacOptions: DacOptions, |
| destDir: File, |
| taskName: String = "generateDocs" |
| ): GenerateDocsTask = |
| project.tasks.createWithConfig(taskName, GenerateDocsTask::class.java) { |
| dependsOn(generateSdkApiTask, doclavaConfig) |
| group = JavaBasePlugin.DOCUMENTATION_GROUP |
| description = "Generates d.android.com-style documentation. To generate offline docs " + |
| "use \'-PofflineDocs=true\' parameter." |
| |
| setDocletpath(doclavaConfig.resolve()) |
| val offline = project.processProperty("offlineDocs") != null |
| destinationDir = File(destDir, if (offline) "offline" else "online") |
| classpath = androidJarFile(project) |
| checksConfig = GENERATE_DOCS_CONFIG |
| addSinceFilesFrom(supportRootFolder) |
| |
| coreJavadocOptions { |
| addStringOption("templatedir", |
| "$supportRootFolder/../../external/doclava/res/assets/templates-sdk") |
| addStringOption("samplesdir", "$supportRootFolder/samples") |
| addMultilineMultiValueOption("federate").value = listOf( |
| listOf("Android", "https://developer.android.com") |
| ) |
| addMultilineMultiValueOption("federationapi").value = listOf( |
| listOf("Android", generateSdkApiTask.apiFile?.absolutePath) |
| ) |
| addMultilineMultiValueOption("hdf").value = listOf( |
| listOf("android.whichdoc", "online"), |
| listOf("android.hasSamples", "true"), |
| listOf("dac", "true") |
| ) |
| |
| // Specific to reference docs. |
| if (!offline) { |
| addStringOption("toroot", "/") |
| addBooleanOption("devsite", true) |
| addBooleanOption("yamlV2", true) |
| addStringOption("dac_libraryroot", dacOptions.libraryroot) |
| addStringOption("dac_dataname", dacOptions.dataname) |
| } |
| |
| exclude("**/BuildConfig.java") |
| } |
| |
| addArtifactsAndSince() |
| } |
| |
| private data class Tasks( |
| val generateApi: DoclavaTask, |
| val checkApiTask: CheckApiTask |
| ) |
| |
| /** |
| * Sets up api tasks for the given project |
| */ |
| private fun initializeApiChecksForProject(project: Project, aggregateOldApiTxtsTask: ConcatenateFilesTask, aggregateNewApiTxtsTask:ConcatenateFilesTask): Tasks { |
| if (!project.hasProperty("docsDir")) { |
| project.extensions.add("docsDir", File(project.rootProject.docsDir(), project.name)) |
| } |
| val version = project.version() |
| val workingDir = project.projectDir |
| |
| val doclavaConfiguration = project.rootProject.configurations.getByName("doclava") |
| val docletClasspath = doclavaConfiguration.resolve() |
| val generateApi = createGenerateApiTask(project, docletClasspath) |
| generateApi.dependsOn(doclavaConfiguration) |
| |
| // for verifying that the API surface has not broken since the last release |
| val lastReleasedApiFile = getLastReleasedApiFile(workingDir, version) |
| |
| val whitelistFile = lastReleasedApiFile?.let { apiFile -> |
| File(lastReleasedApiFile.parentFile, stripExtension(apiFile.name) + ".ignore") |
| } |
| val checkApiRelease = createCheckApiTask(project, |
| "checkApiRelease", |
| docletClasspath, |
| CHECK_API_CONFIG_RELEASE, |
| lastReleasedApiFile, |
| generateApi.apiFile!!, |
| whitelistFile) |
| checkApiRelease.dependsOn(generateApi) |
| |
| // Allow a comma-delimited list of whitelisted errors. |
| if (project.hasProperty("ignore")) { |
| checkApiRelease.whitelistErrors = (project.properties["ignore"] as String) |
| .split(',').toSet() |
| } |
| |
| // Check whether the development API surface has changed. |
| val verifyConfig = if (version.isPatch()) CHECK_API_CONFIG_PATCH else CHECK_API_CONFIG_DEVELOP |
| val currentApiFile = getApiFile(workingDir, version) |
| val checkApi = createCheckApiTask(project, |
| "checkApi", |
| docletClasspath, |
| verifyConfig, |
| currentApiFile, |
| generateApi.apiFile!!, |
| null) |
| checkApi.dependsOn(generateApi, checkApiRelease) |
| |
| checkApi.group = JavaBasePlugin.VERIFICATION_GROUP |
| checkApi.description = "Verify the API surface." |
| |
| val updateApiTask = createUpdateApiTask(project, checkApiRelease) |
| updateApiTask.dependsOn(checkApiRelease) |
| |
| |
| val oldApiTxt = getOldApiTxt(project) |
| if (oldApiTxt != null) { |
| aggregateOldApiTxtsTask.addInput(project.name, oldApiTxt) |
| } |
| val newApiTxtProvider = getNewApiTxt(project, generateApi) |
| aggregateNewApiTxtsTask.inputs.file(newApiTxtProvider.file) |
| aggregateNewApiTxtsTask.addInput(project.name, newApiTxtProvider.file) |
| if (newApiTxtProvider.task != null) { |
| aggregateNewApiTxtsTask.dependsOn(newApiTxtProvider.task) |
| } |
| |
| return Tasks(generateApi, checkApi) |
| } |
| |
| fun hasApiTasks(project: Project, extension: SupportLibraryExtension): Boolean { |
| if (!extension.publish) { |
| project.logger.info("Project ${project.name} is not published, ignoring API tasks.") |
| return false |
| } |
| |
| if (!extension.generateDocs) { |
| project.logger.info("Project ${project.name} specified generateDocs = false, " + |
| "ignoring API tasks.") |
| return false |
| } |
| return true |
| } |
| |
| private fun sdkApiFile(project: Project) = File(project.docsDir(), "release/sdk_current.txt") |
| |
| private fun <T : Task> TaskContainer.createWithConfig( |
| name: String, |
| taskClass: Class<T>, |
| config: T.() -> Unit |
| ) = |
| create(name, taskClass) { task -> task.config() } |
| |
| private fun androidJarFile(project: Project): FileCollection = |
| project.files(arrayOf(File(project.fullSdkPath(), |
| "platforms/android-${SupportConfig.CURRENT_SDK_VERSION}/android.jar"))) |
| |
| private fun androidSrcJarFile(project: Project): File = File(project.fullSdkPath(), |
| "platforms/android-${SupportConfig.CURRENT_SDK_VERSION}/android-stubs-src.jar") |
| |
| private fun PublishDocsRules.resolve(extension: SupportLibraryExtension) = |
| resolve(extension.mavenGroup!!, extension.project.name) |
| |
| private fun Prebuilts.dependency(extension: SupportLibraryExtension) = |
| "${extension.mavenGroup}:${extension.project.name}:$version" |
| |
| private fun BaseVariant.rFile() = "${applicationId.replace('.', '/')}/R.java" |
| |
| // Nasty part. Get rid of that eventually! |
| private fun Project.docsDir(): File = properties["docsDir"] as File |
| |
| private fun Project.fullSdkPath(): File = rootProject.properties["fullSdkPath"] as File |
| |
| private fun Project.version() = Version(project.version as String) |
| |
| private fun Project.buildNumber() = properties["buildNumber"] as String |
| |
| private fun Project.distDir(): File = rootProject.properties["distDir"] as File |
| |
| private fun Project.processProperty(name: String) = |
| if (hasProperty(name)) { |
| properties[name] as String |
| } else { |
| null |
| } |