diff_and_docs.gradle revision cef9526cf31392b0e267a962944b119280654279
1/* 2 * Copyright (C) 2017 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 17import android.support.checkapi.ApiXmlConversionTask 18import android.support.checkapi.CheckApiTask 19import android.support.checkapi.UpdateApiTask 20import android.support.doclava.DoclavaMultilineJavadocOptionFileOption 21import android.support.doclava.DoclavaTask 22import android.support.jdiff.JDiffTask 23 24import org.gradle.api.InvalidUserDataException 25 26import groovy.io.FileType 27 28// Set up platform API files for federation. 29if (project.androidApiTxt != null) { 30 task generateSdkApi(type: Copy) { 31 description = 'Copies the API files for the current SDK.' 32 33 // Export the API files so this looks like a DoclavaTask. 34 ext.apiFile = new File(project.docsDir, 'release/sdk_current.txt') 35 ext.removedApiFile = new File(project.docsDir, 'release/sdk_removed.txt') 36 37 from project.androidApiTxt.absolutePath 38 into apiFile.parent 39 rename { apiFile.name } 40 41 // Register the fake removed file as an output. 42 outputs.file removedApiFile 43 44 doLast { 45 removedApiFile.createNewFile() 46 } 47 } 48} else { 49 task generateSdkApi(type: DoclavaTask, dependsOn: [configurations.doclava]) { 50 description = 'Generates API files for the current SDK.' 51 52 docletpath = configurations.doclava.resolve() 53 destinationDir = project.docsDir 54 55 classpath = project.androidJar 56 source zipTree(project.androidSrcJar) 57 58 apiFile = new File(project.docsDir, 'release/sdk_current.txt') 59 removedApiFile = new File(project.docsDir, 'release/sdk_removed.txt') 60 generateDocs = false 61 62 options { 63 addStringOption "stubpackages", "android.*" 64 } 65 } 66} 67 68// Generates online docs. 69task generateDocs(type: DoclavaTask, dependsOn: [configurations.doclava, generateSdkApi]) { 70 group = JavaBasePlugin.DOCUMENTATION_GROUP 71 description = 'Generates d.android.com-style documentation.' 72 73 docletpath = configurations.doclava.resolve() 74 destinationDir = new File(project.docsDir, "online") 75 76 // Base classpath is Android SDK, sub-projects add their own. 77 classpath = project.ext.androidJar 78 79 def hdfOption = new DoclavaMultilineJavadocOptionFileOption('hdf') 80 hdfOption.add( 81 ['android.whichdoc', 'online'], 82 ['android.hasSamples', 'true']) 83 84 def federateOption = new DoclavaMultilineJavadocOptionFileOption('federate') 85 federateOption.add(['Android', 'https://developer.android.com']) 86 87 def federationapiOption = new DoclavaMultilineJavadocOptionFileOption('federationapi') 88 federationapiOption.add(['Android', generateSdkApi.apiFile.absolutePath]) 89 90 // Track API change history. 91 def apiFilePattern = /(\d+\.\d+\.\d).txt/ 92 def sinceOption = new DoclavaMultilineJavadocOptionFileOption('since') 93 File apiDir = new File(supportRootFolder, 'api') 94 apiDir.eachFileMatch FileType.FILES, ~apiFilePattern, { File apiFile -> 95 def apiLevel = (apiFile.name =~ apiFilePattern)[0][1] 96 sinceOption.add([apiFile.absolutePath, apiLevel]) 97 } 98 99 // Default hidden errors + hidden superclass (111) and 100 // deprecation mismatch (113) to match framework docs. 101 final def hidden = [105, 107, 111, 112, 113, 115, 116, 121] 102 103 doclavaErrors = (101..122) - hidden 104 doclavaWarnings = [] 105 doclavaHidden += hidden 106 107 options { 108 addStringOption "templatedir", 109 "${supportRootFolder}/../../external/doclava/res/assets/templates-sdk" 110 addStringOption "stubpackages", "android.support.*" 111 addStringOption "samplesdir", "${supportRootFolder}/samples" 112 addOption federateOption 113 addOption federationapiOption 114 addOption hdfOption 115 addOption sinceOption 116 117 // Specific to reference docs. 118 addStringOption "toroot", "/" 119 addBooleanOption "devsite", true 120 addBooleanOption "androidSupportRef", true 121 } 122 123 exclude '**/BuildConfig.java' 124} 125 126// Generates a distribution artifact for online docs. 127task distDocs(type: Zip, dependsOn: generateDocs) { 128 group = JavaBasePlugin.DOCUMENTATION_GROUP 129 description = 'Generates distribution artifact for d.android.com-style documentation.' 130 131 from generateDocs.destinationDir 132 destinationDir project.distDir 133 baseName = "android-support-docs" 134 version = project.buildNumber 135 136 doLast { 137 logger.lifecycle("'Wrote API reference to ${archivePath}") 138 } 139} 140 141// Generates API files. 142task generateApi(type: DoclavaTask, dependsOn: configurations.doclava) { 143 docletpath = configurations.doclava.resolve() 144 destinationDir = project.docsDir 145 146 // Base classpath is Android SDK, sub-projects add their own. 147 classpath = project.ext.androidJar 148 149 apiFile = new File(project.docsDir, 'release/current.txt') 150 removedApiFile = new File(project.docsDir, 'release/removed.txt') 151 generateDocs = false 152 153 options { 154 addStringOption "templatedir", 155 "${supportRootFolder}/../../external/doclava/res/assets/templates-sdk" 156 addStringOption "stubpackages", "android.support.*" 157 } 158 exclude '**/BuildConfig.java' 159 exclude '**/R.java' 160} 161 162// Copies generated API files to current version. 163task updateApi(type: UpdateApiTask, dependsOn: generateApi) { 164 group JavaBasePlugin.VERIFICATION_GROUP 165 description 'Invoke Doclava\'s ApiCheck tool to update current.txt based on current changes.' 166 newApiFile = new File(project.docsDir, 'release/current.txt') 167 oldApiFile = new File(supportRootFolder, 'api/current.txt') 168 newRemovedApiFile = new File(project.docsDir, 'release/removed.txt') 169 oldRemovedApiFile = new File(supportRootFolder, 'api/removed.txt') 170} 171 172// Finalizes the API file for a release version. 173task finalizeApi(type: Copy, dependsOn: updateApi) { 174 group JavaBasePlugin.VERIFICATION_GROUP 175 description 'Finalize the API definition for the current release.' 176 177 def apiVersion = project.supportVersion; 178 if (project.hasProperty("revision")) { 179 apiVersion = revision; 180 } 181 182 File currentApiFile = new File(project.rootDir, 'api/current.txt') 183 File finalizedApiFile = new File(currentApiFile.parentFile, "${apiVersion}.txt") 184 185 from currentApiFile.absolutePath 186 into finalizedApiFile.parent 187 rename { finalizedApiFile.name } 188 189 doFirst { 190 // Verify this is a proper release version. 191 if (!(apiVersion ==~ /^\d+\.\d+\.\d+$/)) { 192 throw new InvalidUserDataException("${apiVersion} is not valid release version format. " 193 + "Use -Prevision=X.Y.Z to specify an explicit version.") 194 } 195 196 // Verify that we're not accidentally overwriting an existing API file. 197 if (finalizedApiFile.exists() && !(project.hasProperty("overwrite") && overwrite)) { 198 throw new InvalidUserDataException("Version ${apiVersion} has already been " 199 + "finalized. Use -Poverwrite=true to overwrite.") 200 } 201 } 202 203 doLast { 204 project.logger.warn("Wrote ${finalizedApiFile.getParentFile().name}/" 205 + "${finalizedApiFile.name} API file.") 206 } 207} 208 209// Checks generated API files against current version. 210task checkApi(type: CheckApiTask, dependsOn: generateApi) { 211 doclavaClasspath = generateApi.docletpath 212 213 checkApiTaskPath = name 214 updateApiTaskPath = updateApi.name 215 216 // Check that the API we're building hasn't changed from the development 217 // version. These typed of changes require an explicit API file update. 218 checkApiErrors = (2..30)-[22] 219 checkApiWarnings = [] 220 checkApiHidden = [22] 221 222 newApiFile = new File(project.docsDir, 'release/current.txt') 223 oldApiFile = new File(supportRootFolder, 'api/current.txt') 224 newRemovedApiFile = new File(project.docsDir, 'release/removed.txt') 225 oldRemovedApiFile = new File(supportRootFolder, 'api/removed.txt') 226} 227 228rootProject.createArchive.dependsOn checkApi 229 230 231// Checks generated API files against current version. 232task checkApiStable(type: CheckApiTask, dependsOn: generateApi) { 233 doclavaClasspath = generateApi.docletpath 234 235 checkApiTaskPath = name 236 updateApiTaskPath = updateApi.name 237 238 // Check that the API we're building hasn't broken the last-released 239 // library version. These types of changes are forbidden. 240 checkApiErrors = (7..18) 241 checkApiWarnings = [23, 24] 242 checkApiHidden = (2..6) + (19..22) + (25..30) 243 244 newApiFile = new File(project.docsDir, 'release/current.txt') 245 oldApiFile = getReleasedApiFile() 246 newRemovedApiFile = new File(project.docsDir, 'release/removed.txt') 247 oldRemovedApiFile = new File(supportRootFolder, 'api/removed.txt') 248} 249 250checkApi.dependsOn checkApiStable 251 252 253/** 254 * Converts the <code>toApi</code>.txt file (or current.txt if not explicitly 255 * defined using -PtoAPi=<file>) to XML format for use by JDiff. 256 */ 257task newApiXml(type: ApiXmlConversionTask, dependsOn: configurations.doclava) { 258 classpath configurations.doclava.resolve() 259 260 if (project.hasProperty("toApi")) { 261 // Use an explicit API file. 262 inputApiFile = new File(rootProject.ext.supportRootFolder, "api/${toApi}.txt") 263 } else { 264 // Use the current API file (e.g. current.txt). 265 inputApiFile = generateApi.apiFile 266 dependsOn generateApi 267 } 268 269 int lastDot = inputApiFile.name.lastIndexOf('.') 270 outputApiXmlFile = new File(project.docsDir, 271 "release/" + inputApiFile.name.substring(0, lastDot) + ".xml") 272} 273 274/** 275 * Converts the <code>fromApi</code>.txt file (or the most recently released 276 * X.Y.Z.txt if not explicitly defined using -PfromAPi=<file>) to XML format 277 * for use by JDiff. 278 */ 279task oldApiXml(type: ApiXmlConversionTask, dependsOn: configurations.doclava) { 280 classpath configurations.doclava.resolve() 281 282 if (project.hasProperty("fromApi")) { 283 // Use an explicit API file. 284 inputApiFile = new File(rootProject.ext.supportRootFolder, "api/${fromApi}.txt") 285 } else if (project.hasProperty("toApi") && toApi.matches(~/(\d+\.){2}\d+/)) { 286 // If toApi matches released API (X.Y.Z) format, use the most recently 287 // released API file prior to toApi. 288 inputApiFile = getReleasedApiFile(toApi) 289 } else { 290 // Use the most recently released API file. 291 inputApiFile = getReleasedApiFile(); 292 } 293 294 int lastDot = inputApiFile.name.lastIndexOf('.') 295 outputApiXmlFile = new File(project.docsDir, 296 "release/" + inputApiFile.name.substring(0, lastDot) + ".xml") 297} 298 299/** 300 * Generates API diffs. 301 * <p> 302 * By default, diffs are generated for the delta between current.txt and the 303 * next most recent X.Y.Z.txt API file. Behavior may be changed by specifying 304 * one or both of -PtoApi and -PfromApi. 305 * <p> 306 * If both fromApi and toApi are specified, diffs will be generated for 307 * fromApi -> toApi. For example, 25.0.0 -> 26.0.0 diffs could be generated by 308 * using: 309 * <br><code> 310 * ./gradlew generateDiffs -PfromApi=25.0.0 -PtoApi=26.0.0 311 * </code> 312 * <p> 313 * If only toApi is specified, it MUST be specified as X.Y.Z and diffs will be 314 * generated for (release before toApi) -> toApi. For example, 24.2.0 -> 25.0.0 315 * diffs could be generated by using: 316 * <br><code> 317 * ./gradlew generateDiffs -PtoApi=25.0.0 318 * </code> 319 * <p> 320 * If only fromApi is specified, diffs will be generated for fromApi -> current. 321 * For example, lastApiReview -> current diffs could be generated by using: 322 * <br><code> 323 * ./gradlew generateDiffs -PfromApi=lastApiReview 324 * </code> 325 * <p> 326 */ 327task generateDiffs(type: JDiffTask, dependsOn: [configurations.jdiff, configurations.doclava, 328 oldApiXml, newApiXml, generateDocs]) { 329 // Base classpath is Android SDK, sub-projects add their own. 330 classpath = project.ext.androidJar 331 332 // JDiff properties. 333 oldApiXmlFile = oldApiXml.outputApiXmlFile 334 newApiXmlFile = newApiXml.outputApiXmlFile 335 newJavadocPrefix = "../../../../reference/" 336 337 String newApi = newApiXmlFile.name 338 int lastDot = newApi.lastIndexOf('.') 339 newApi = newApi.substring(0, lastDot) 340 341 // Javadoc properties. 342 docletpath = configurations.jdiff.resolve() 343 destinationDir = new File(project.docsDir, "online/sdk/support_api_diff/$newApi") 344 title = "Support Library API Differences Report" 345 346 exclude '**/BuildConfig.java' 347 exclude '**/R.java' 348} 349 350/** 351 * Returns the most recently released API, optionally restricting to APIs 352 * before <code>beforeApi</code>. 353 * 354 * @param beforeApi the API to find an API file before, ex. 25.0.0 355 * @return the most recently released API file 356 */ 357File getReleasedApiFile(String beforeApi = null) { 358 String beforeApiFileName = beforeApi != null ? beforeApi + ".txt" : null 359 File lastReleasedApiFile = null 360 File apiDir = new File(ext.supportRootFolder, 'api') 361 362 apiDir.eachFileMatch FileType.FILES, ~/(\d+\.){3}txt/, { File apiFile -> 363 // Is the current API file newer than the last one we saw? 364 if (lastReleasedApiFile == null || apiFile.name > lastReleasedApiFile.name) { 365 // Is the current API file older than the "before" API? 366 if (beforeApiFileName == null || apiFile.name < beforeApiFileName) { 367 lastReleasedApiFile = apiFile 368 } 369 } 370 } 371 372 return lastReleasedApiFile 373} 374 375// configuration file for setting up api diffs and api docs 376void registerForDocsTask(Task task, Project subProject, releaseVariant) { 377 task.dependsOn releaseVariant.javaCompile 378 task.source { 379 def buildConfig = fileTree(releaseVariant.getGenerateBuildConfig().sourceOutputDir) 380 return releaseVariant.javaCompile.source.minus(buildConfig) + 381 fileTree(releaseVariant.aidlCompile.sourceOutputDir) + 382 fileTree(releaseVariant.outputs[0].processResources.sourceOutputDir) 383 } 384 task.classpath += files(releaseVariant.javaCompile.classpath) + 385 files(releaseVariant.javaCompile.destinationDir) 386} 387 388// configuration file for setting up api diffs and api docs 389void registerJavaProjectForDocsTask(Task task, Project subProject, javaCompileTask) { 390 task.dependsOn javaCompileTask 391 task.source javaCompileTask.source 392 task.classpath += files(javaCompileTask.classpath) + 393 files(javaCompileTask.destinationDir) 394} 395 396subprojects { subProject -> 397 subProject.afterEvaluate { p -> 398 if (!p.hasProperty("noDocs") || !p.noDocs) { 399 if (p.hasProperty('android') && p.android.hasProperty('libraryVariants')) { 400 p.android.libraryVariants.all { v -> 401 if (v.name == 'release') { 402 registerForDocsTask(rootProject.generateDocs, p, v) 403 registerForDocsTask(rootProject.generateApi, p, v) 404 registerForDocsTask(rootProject.generateDiffs, p, v) 405 } 406 } 407 } else if (p.hasProperty("compileJava")) { 408 registerJavaProjectForDocsTask(rootProject.generateDocs, p, p.compileJava) 409 } 410 } 411 } 412} 413