blob: a299124c90c37c00606737f0d0d7cbcfd12d1821 [file] [log] [blame]
/*
* Copyright (C) 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 org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.tasks.TaskAction
/**
* Task for verifying the androidx dependency-stability-suffix rule
* (A library is only as stable as its lease stable dependency)
*/
open class VerifyDependencyVersionsTask : DefaultTask() {
init {
group = "Verification"
description = "Task for verifying the androidx dependency-stability-suffix rule"
}
/**
* Iterate through the dependencies of the project and ensure none of them are of an inferior
* release. This means that a beta project should not have any alpha dependencies, an rc project
* should not have any alpha or beta dependencies and a stable version should only depend on
* other stable versions. Dependencies defined with testCompile and friends along with
* androidTestImplementation and similars are excluded from this verification.
*/
@TaskAction
fun verifyDependencyVersions() {
project.configurations.filter(::shouldVerifyConfiguration).forEach { configuration ->
configuration.allDependencies.filter(::shouldVerifyDependency).forEach { dependency ->
verifyDependencyVersion(configuration, dependency)
}
}
}
private fun verifyDependencyVersion(configuration: Configuration, dependency: Dependency) {
// If the version is unspecified then treat as an alpha version. If the depending project's
// version is unspecified then it won't matter, and if the dependency's version is
// unspecified then any non alpha project won't be able to depend on it to ensure safety.
val projectVersionExtra = if (project.version ==
AndroidXExtension.DEFAULT_UNSPECIFIED_VERSION
) {
"-alpha01"
} else {
Version(project.version.toString()).extra ?: ""
}
val dependencyVersionExtra = if (dependency.version!! ==
AndroidXExtension.DEFAULT_UNSPECIFIED_VERSION
) {
"-alpha01"
} else {
Version(dependency.version!!).extra ?: ""
}
val projectReleasePhase = releasePhase(projectVersionExtra)
if (projectReleasePhase < 0) {
throw GradleException(
"Project ${project.name} has unexpected release phase " + projectVersionExtra
)
}
val dependencyReleasePhase = releasePhase(dependencyVersionExtra)
if (dependencyReleasePhase < 0) {
throw GradleException(
"Dependency ${dependency.group}:${dependency.name}" +
":${dependency.version} has unexpected release phase $dependencyVersionExtra"
)
}
if (dependencyReleasePhase < projectReleasePhase) {
throw GradleException(
"Project ${project.name} with version ${project.version} may " +
"not take a dependency on less-stable artifact ${dependency.group}:" +
"${dependency.name}:${dependency.version} for configuration " +
"${configuration.name}. Dependency versions must be at least as stable as " +
"the project version."
)
}
}
private fun releasePhase(versionExtra: String): Int {
return if (versionExtra == "") {
4
} else if (versionExtra.startsWith("-rc")) {
3
} else if (versionExtra.startsWith("-beta")) {
2
} else if (versionExtra.startsWith("-alpha") || versionExtra.startsWith("-qpreview") ||
versionExtra.startsWith("-dev")
) {
1
} else {
-1
}
}
}
fun shouldVerifyConfiguration(configuration: Configuration): Boolean {
// Only verify configurations that are exported to POM. In an ideal world, this would be an
// inclusion derived from the mappings used by the Maven Publish Plugin; however, since we
// don't have direct access to those, this should remain an exclusion list.
val name = configuration.name
// Don't check any Android-specific variants of Java plugin configurations -- releaseApi for
// api, debugImplementation for implementation, etc. -- or test configurations.
if (name.startsWith("androidTest")) return false
if (name.startsWith("androidAndroidTest")) return false
if (name.startsWith("debug")) return false
if (name.startsWith("androidDebug")) return false
if (name.startsWith("release")) return false
if (name.startsWith("test")) return false
// Don't check any tooling configurations.
if (name == "annotationProcessor") return false
if (name == "errorprone") return false
if (name.startsWith("jacoco")) return false
if (name.startsWith("lint")) return false
if (name == "metalava") return false
// Don't check any configurations that directly bundle the dependencies with the output
if (name == "bundleInside") return false
// Don't check any compile-only configurations
if (name.startsWith("compile")) return false
return true
}
fun shouldVerifyDependency(dependency: Dependency): Boolean {
// Only verify dependencies within the scope of our versioning policies.
if (dependency.group == null) return false
if (!dependency.group.toString().startsWith("androidx.")) return false
if (dependency.name == "annotation-sampled") return false
if (dependency.version == AndroidXPlaygroundRootPlugin.SNAPSHOT_MARKER) {
// This only happens in playground builds where this magic version gets replaced with
// the version from the snapshotBuildId defined in playground-common/playground.properties.
// It is best to leave their validation to the aosp build to ensure it is the right
// version.
return false
}
return true
}