[go: nahoru, domu]

blob: 89d64da44cf61200a6442b73fa81343dc21a3b78 [file] [log] [blame]
/*
* Copyright 2019 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.metalava
import androidx.build.checkapi.ApiLocation
import androidx.build.java.JavaCompileInputs
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.FileCollection
import java.io.File
// MetalavaRunner stores common configuration for executing Metalava
fun Project.getMetalavaConfiguration(): Configuration {
return configurations.findByName("metalava") ?: configurations.create("metalava") {
val dependency = dependencies.create("com.android:metalava:1.3.0:shadow@jar")
it.dependencies.add(dependency)
}
}
fun Project.runMetalavaWithArgs(configuration: Configuration, args: List<String>) {
javaexec {
it.classpath = checkNotNull(configuration) { "Configuration not set." }
it.main = "com.android.tools.metalava.Driver"
it.args = listOf(
"--no-banner",
"--hide",
"HiddenSuperclass" // We allow having a hidden parent class
) + args
}
}
// Metalava arguments to hide all experimental API surfaces.
val HIDE_EXPERIMENTAL_ARGS: List<String> = listOf(
"--hide-annotation", "androidx.annotation.experimental.Experimental",
"--hide-annotation", "kotlin.Experimental",
"--hide-meta-annotation", "androidx.annotation.experimental.Experimental",
"--hide-meta-annotation", "kotlin.Experimental"
)
val API_LINT_ARGS: List<String> = listOf(
"--api-lint",
"--hide",
listOf(
// The list of checks that are hidden as they are not useful in androidx
"Enum", // Enums are allowed to be use in androidx
"CallbackInterface", // With target Java 8, we have default methods
"ProtectedMember", // We allow using protected members in androidx
"ManagerLookup", // Managers in androidx are not the same as platfrom services
"ManagerConstructor",
"RethrowRemoteException", // This check is for calls into system_server
// List of checks that have bugs, but should be enabled once fixed.
"GetterSetterNames", // b/135498039
"StaticUtils", // b/135489083
"AllUpper", // b/135708486
"StartWithLower", // b/135710527
// The list of checks that are API lint warnings and are yet to be enabled
"IntentBuilderName",
"OnNameExpected",
"TopLevelBuilder",
"MissingBuild",
"BuilderSetStyle",
"SetterReturnsThis",
"PackageLayering",
"OverlappingConstants",
"IllegalStateException",
"ListenerLast",
"ExecutorRegistration",
"StreamFiles",
"ParcelableList",
"AbstractInner",
"NotCloseable",
"ArrayReturn",
"UserHandle",
"UserHandleName",
"MethodNameTense",
"UseIcu",
"NoByteOrShort",
"CommonArgsFirst",
"SamShouldBeLast",
"MissingJvmStatic"
).joinToString(),
"--error",
"MinMaxConstant"
)
sealed class GenerateApiMode {
object PublicApi : GenerateApiMode()
object RestrictedApi : GenerateApiMode()
object ExperimentalApi : GenerateApiMode()
}
sealed class ApiLintMode {
class CheckBaseline(val apiLintBaseline: File) : ApiLintMode()
object Skip : ApiLintMode()
}
// Generates all of the specified api files
fun Project.generateApi(
files: JavaCompileInputs,
apiLocation: ApiLocation,
tempDir: File,
apiLintMode: ApiLintMode,
includeRestrictedApis: Boolean
) {
generateApi(files.bootClasspath, files.dependencyClasspath, files.sourcePaths.files,
apiLocation.publicApiFile, tempDir, GenerateApiMode.PublicApi, apiLintMode)
generateApi(files.bootClasspath, files.dependencyClasspath, files.sourcePaths.files,
apiLocation.experimentalApiFile, tempDir, GenerateApiMode.ExperimentalApi, apiLintMode)
if (includeRestrictedApis) {
generateApi(files.bootClasspath, files.dependencyClasspath, files.sourcePaths.files,
apiLocation.restrictedApiFile, tempDir, GenerateApiMode.RestrictedApi, ApiLintMode.Skip)
}
}
// Generates the specified api file
fun Project.generateApi(
bootClasspath: Collection<File>,
dependencyClasspath: FileCollection,
sourcePaths: Collection<File>,
outputFile: File,
tempDir: File,
generateApiMode: GenerateApiMode,
apiLintMode: ApiLintMode
) {
val tempOutputFile = if (generateApiMode is GenerateApiMode.RestrictedApi) {
File(tempDir, outputFile.name + ".tmp")
} else {
outputFile
}
// generate public API txt
val args = mutableListOf(
"--classpath",
(bootClasspath + dependencyClasspath.files).joinToString(File.pathSeparator),
"--source-path",
sourcePaths.filter { it.exists() }.joinToString(File.pathSeparator),
"--api",
tempOutputFile.toString(),
"--format=v3",
"--output-kotlin-nulls=yes"
)
when (generateApiMode) {
is GenerateApiMode.PublicApi -> {
args += HIDE_EXPERIMENTAL_ARGS
}
is GenerateApiMode.RestrictedApi -> {
// Show restricted APIs despite @hide.
args += listOf(
"--show-annotation", "androidx.annotation.RestrictTo",
"--show-unannotated"
)
args += HIDE_EXPERIMENTAL_ARGS
}
is GenerateApiMode.ExperimentalApi -> {
// No additional args needed.
}
}
when (apiLintMode) {
is ApiLintMode.CheckBaseline -> {
args += API_LINT_ARGS
if (apiLintMode.apiLintBaseline.exists()) {
args += listOf("--baseline", apiLintMode.apiLintBaseline.toString())
}
args.addAll(listOf(
"--error",
"DeprecationMismatch" // Enforce deprecation mismatch
))
}
is ApiLintMode.Skip -> {
args.addAll(listOf(
"--hide",
"DeprecationMismatch",
"--hide",
"UnhiddenSystemApi",
"--hide",
"ReferencesHidden"
))
}
}
val metalavaConfiguration = getMetalavaConfiguration()
runMetalavaWithArgs(metalavaConfiguration, args)
if (generateApiMode is GenerateApiMode.RestrictedApi) {
removeRestrictToLibraryLines(tempOutputFile, outputFile)
}
}
// until b/119617147 is done, remove lines containing "@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)"
private fun removeRestrictToLibraryLines(inputFile: File, outputFile: File) {
val outputBuilder = StringBuilder()
val lines = inputFile.readLines()
var skipScopeUntil: String? = null
for (line in lines) {
val skip = line.contains("@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY)")
if (skip && line.endsWith("{")) {
skipScopeUntil = line.commonPrefixWith(" ") + "}"
}
if (!skip && skipScopeUntil == null) {
outputBuilder.append(line)
outputBuilder.append("\n")
}
if (line == skipScopeUntil) {
skipScopeUntil = null
}
}
if (skipScopeUntil != null) {
throw GradleException("Skipping until `$skipScopeUntil`, but found EOF")
}
outputFile.writeText(outputBuilder.toString())
}