blob: 7218e3012b60886f241386c4fb3c038d3fd5429d [file] [log] [blame]
/*
* 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 com.android.build.gradle.LibraryPlugin
import groovy.util.Node
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.XmlProvider
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.component.SoftwareComponent
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPom
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.tasks.GenerateModuleMetadata
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.create
import java.io.File
fun Project.configureMavenArtifactUpload(extension: AndroidXExtension) {
apply(mapOf("plugin" to "maven-publish"))
afterEvaluate {
components.all { component ->
configureComponent(extension, component)
}
}
}
private fun Project.configureComponent(
extension: AndroidXExtension,
component: SoftwareComponent
) {
if (extension.publish.shouldPublish() && component.isAndroidOrJavaReleaseComponent()) {
val androidxGroup = validateCoordinatesAndGetGroup(extension)
group = androidxGroup
tasks.withType(GenerateModuleMetadata::class.java) {
it.enabled = false
}
configure<PublishingExtension> {
repositories {
it.maven { repo ->
repo.setUrl(getRepositoryDirectory())
}
}
publications {
if (appliesJavaGradlePluginPlugin()) {
// The 'java-gradle-plugin' will also add to the 'pluginMaven' publication
it.create<MavenPublication>("pluginMaven").pom { pom ->
addInformativeMetadata(pom, extension)
tweakDependenciesMetadata(extension, pom)
}
tasks.getByName("publishPluginMavenPublicationToMavenRepository").doFirst {
removePreviouslyUploadedArchives(androidxGroup)
}
} else {
it.create<MavenPublication>("maven") {
from(component)
pom { pom ->
addInformativeMetadata(pom, extension)
tweakDependenciesMetadata(extension, pom)
}
}
tasks.getByName("publishMavenPublicationToMavenRepository").doFirst {
removePreviouslyUploadedArchives(androidxGroup)
}
}
}
}
// Register it as part of release so that we create a Zip file for it
Release.register(this, extension)
}
}
private fun SoftwareComponent.isAndroidOrJavaReleaseComponent() =
name == "release" || name == "java"
private fun Project.validateCoordinatesAndGetGroup(extension: AndroidXExtension): String {
val mavenGroup = extension.mavenGroup?.group
?: throw Exception("You must specify mavenGroup for $name project")
val strippedGroupId = mavenGroup.substringAfterLast(".")
if (mavenGroup.startsWith("androidx") && !name.startsWith(strippedGroupId)) {
throw Exception("Your artifactId must start with $strippedGroupId")
}
return mavenGroup
}
/**
* Delete any existing archives, so that developers don't get
* confused/surprised by the presence of old versions.
* Additionally, deleting old versions makes it more convenient to iterate
* over all existing archives without visiting archives having old versions too
*/
private fun Project.removePreviouslyUploadedArchives(group: String) {
val projectArchiveDir = File(
getRepositoryDirectory(),
"${group.replace('.', '/')}/${project.name}"
)
projectArchiveDir.deleteRecursively()
}
private fun Project.addInformativeMetadata(pom: MavenPom, extension: AndroidXExtension) {
pom.name.set(provider { extension.name })
pom.description.set(provider { extension.description })
pom.url.set(provider { extension.url })
pom.inceptionYear.set(provider { extension.inceptionYear })
pom.licenses { licenses ->
licenses.license { license ->
license.name.set("The Apache Software License, Version 2.0")
license.url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
license.distribution.set("repo")
}
for (extraLicense in extension.getLicenses()) {
licenses.license { license ->
license.name.set(provider { extraLicense.name })
license.url.set(provider { extraLicense.url })
license.distribution.set("repo")
}
}
}
pom.scm { scm ->
scm.url.set("http://source.android.com")
scm.connection.set(ANDROID_GIT_URL)
}
pom.developers { devs ->
devs.developer { dev ->
dev.name.set("The Android Open Source Project")
}
}
}
private fun Project.tweakDependenciesMetadata(extension: AndroidXExtension, pom: MavenPom) {
pom.withXml { xml ->
// The following code depends on getProjectsMap which is only available late in
// configuration at which point Java Library plugin's variants are not allowed to be
// modified. TODO remove the use of getProjectsMap and move to earlier configuration.
// For more context see:
// https://android-review.googlesource.com/c/platform/frameworks/support/+/1144664/8/buildSrc/src/main/kotlin/androidx/build/MavenUploadHelper.kt#177
// assignSingleVersionDependenciesInGroupForGradleMetadata(
// configurations.findByName("apiElements"), extension)
// assignSingleVersionDependenciesInGroupForGradleMetadata(
// configurations.findByName("runtimeElements"), extension)
assignSingleVersionDependenciesInGroupForGradleMetadata(
configurations.findByName("releaseApiPublication"), extension)
assignSingleVersionDependenciesInGroupForGradleMetadata(
configurations.findByName("releaseRuntimePublication"), extension)
assignSingleVersionDependenciesInGroupForPom(xml, extension)
assignAarTypes(xml)
}
}
// TODO(aurimas): remove this when Gradle bug is fixed.
// https://github.com/gradle/gradle/issues/3170
private fun Project.assignAarTypes(xml: XmlProvider) {
val androidxDependencies = HashSet<Dependency>()
collectDependenciesForConfiguration(androidxDependencies, "api")
collectDependenciesForConfiguration(androidxDependencies, "implementation")
collectDependenciesForConfiguration(androidxDependencies, "compile")
val dependencies = xml.asNode().children().find { it is Node && it.name().toString()
.endsWith("dependencies") } as Node?
dependencies?.children()?.forEach { dep ->
if (dep !is Node) {
return@forEach
}
val groupId = dep.children().first { it is Node && it.name().toString()
.endsWith("groupId") } as Node
val artifactId = dep.children().first { it is Node && it.name().toString()
.endsWith("artifactId") } as Node
if (isAndroidProject(groupId.children()[0] as String,
artifactId.children()[0] as String, androidxDependencies)) {
dep.appendNode("type", "aar")
}
}
}
/**
* Create dependency constraints between projects of a group that require the same versions.
* The constraints are published in Gradle Module Metadata and cause all modules to align.
*/
fun Project.assignSingleVersionDependenciesInGroupForGradleMetadata(
configuration: Configuration?,
extension: AndroidXExtension
) {
if (configuration != null && extension.mavenGroup?.requireSameVersion == true) {
getProjectsMap().forEach { (id, _) ->
val group = id.split(":")[0]
val name = id.split(":")[1]
if (group == extension.mavenGroup?.group && name != project.name) {
configuration.dependencyConstraints.add(
dependencies.constraints.create("$id:${project.version}")
)
}
}
}
}
/**
* Modifies the given .pom to specify that every dependency in <group> refers to a single version
* and can't be automatically promoted to a new version.
* This will replace, for example, a version string of "1.0" with a version string of "[1.0]"
*
* Note: this is not enforced in Gradle nor in plain Maven (without the Enforcer plugin)
* (https://github.com/gradle/gradle/issues/8297)
*/
private fun assignSingleVersionDependenciesInGroupForPom(
xml: XmlProvider,
extension: AndroidXExtension
) {
val group = extension.mavenGroup
if (group == null || !group.requireSameVersion) {
return
}
val dependencies = xml.asNode().children().find { it is Node && it.name().toString()
.endsWith("dependencies") } as Node?
dependencies?.children()?.forEach { dep ->
if (dep !is Node) {
return@forEach
}
val groupId = dep.children().first { it is Node && it.name().toString()
.endsWith("groupId") } as Node
if (groupId.children()[0].toString() == group.group) {
val versionNode = dep.children().first { it is Node && it.name().toString()
.endsWith("version") } as Node
val declaredVersion = versionNode.children()[0].toString()
if (isVersionRange(declaredVersion)) {
throw GradleException("Unsupported version '$declaredVersion': " +
"already is a version range")
}
val pinnedVersion = "[$declaredVersion]"
versionNode.setValue(pinnedVersion)
}
}
}
private fun isVersionRange(text: String): Boolean {
return text.contains("[") ||
text.contains("]") ||
text.contains("(") ||
text.contains(")") ||
text.contains(",")
}
private fun Project.collectDependenciesForConfiguration(
androidxDependencies: MutableSet<Dependency>,
name: String
) {
val config = configurations.findByName(name)
config?.dependencies?.forEach { dep ->
if (dep.group?.startsWith("androidx.") == true) {
androidxDependencies.add(dep)
}
}
}
private fun Project.isAndroidProject(
groupId: String,
artifactId: String,
deps: Set<Dependency>
): Boolean {
for (dep in deps) {
if (dep is ProjectDependency) {
if (dep.group == groupId && dep.name == artifactId) {
return dep.dependencyProject.plugins.hasPlugin(LibraryPlugin::class.java)
}
}
}
val projectModules = project.getProjectsMap()
projectModules["$groupId:$artifactId"]?.let { module ->
return project.findProject(module)?.plugins?.hasPlugin(LibraryPlugin::class.java) ?: false
}
return false
}
private fun Project.appliesJavaGradlePluginPlugin() = pluginManager.hasPlugin("java-gradle-plugin")
private const val ANDROID_GIT_URL =
"scm:git:https://android.googlesource.com/platform/frameworks/support"