io.github.gmazzo.codeowners.CodeOwnersJVMPlugin.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jvm-plugin Show documentation
Show all versions of jvm-plugin Show documentation
CodeOwners JVM Gradle Plugin
@file:Suppress("UnstableApiUsage")
package io.github.gmazzo.codeowners
import com.android.build.api.variant.Component
import com.android.build.api.variant.HasAndroidTest
import com.android.build.api.variant.HasUnitTest
import io.github.gmazzo.codeowners.CodeOwnersInspectDependencies.DependenciesMode
import io.github.gmazzo.codeowners.matcher.CodeOwnersFile
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.type.ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE
import org.gradle.api.artifacts.type.ArtifactTypeDefinition.JAR_TYPE
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.HasAttributes
import org.gradle.api.attributes.HasConfigurableAttributes
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.Usage
import org.gradle.api.attributes.java.TargetJvmEnvironment
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.AbstractCopyTask
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.codeOwners
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.generateTask
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.invoke
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.registerTransform
import org.gradle.kotlin.dsl.the
class CodeOwnersJVMPlugin : CodeOwnersPlugin(CodeOwnersJVMExtension::class.java) {
private companion object {
const val ARTIFACT_TYPE_CODEOWNERS = "codeowners"
private const val ARTIFACT_TYPE_ANDROID_JAVA_RES = "android-java-res"
}
override fun Project.configureExtension(extension: CodeOwnersJVMExtension) {
val codeOwners = extension.codeOwnersFile.asFile.map { file -> file.useLines { CodeOwnersFile(it) } }
extension.inspectDependencies
.convention(DependenciesMode.LOCAL_PROJECTS)
.finalizeValueOnRead()
extension.enabled
.convention(true)
.finalizeValueOnRead()
configureSourceSets(extension, codeOwners)
setupArtifactTransform(objects)
}
private fun Project.configureSourceSets(
extension: CodeOwnersJVMExtension,
codeOwnersFile: Provider,
) = extension.sourceSets.configureEach ss@{
enabled
.convention(true)
.finalizeValueOnRead()
inspectDependencies
.convention(extension.inspectDependencies)
.finalizeValueOnRead()
@Suppress("DEPRECATION")
val prefix = when (name) {
SourceSet.MAIN_SOURCE_SET_NAME -> ""
else -> name.capitalize()
}
generateTask = tasks.register("generate${prefix}CodeOwnersResources") {
group = TASK_GROUP
description = "Process CODEOWNERS entries for source set '${[email protected]}'"
sources.from([email protected])
codeOwners.set(codeOwnersFile)
rootDirectory.set(extension.rootDirectory)
outputDirectory.set(layout.buildDirectory.dir("codeOwners/resources/${[email protected]}"))
rawMappedCodeOwnersFile.set(layout.buildDirectory.file("codeOwners/mappings/${[email protected]}-raw.codeowners"))
simplifiedMappedCodeOwnersFile.set(layout.buildDirectory.file("codeOwners/mappings/${[email protected]}-simplified.codeowners"))
}
}
override fun Project.configureBySourceSet(extension: CodeOwnersJVMExtension) {
the().configureEach ss@{
val sourceSet = extension.sourceSets.maybeCreate([email protected])
sourceSet.generateTask {
addDependencies(objects, sourceSet, configurations[runtimeClasspathConfigurationName])
}
addCodeDependency(extension, sourceSet, implementationConfigurationName)
if (name == SourceSet.MAIN_SOURCE_SET_NAME) {
addOutgoingVariant(sourceSet, configurations[runtimeClasspathConfigurationName])
}
[email protected](CodeOwnersJVMSourceSet::class.java, Component::codeOwners.name, sourceSet)
tasks.named(processResourcesTaskName).addResources(extension, sourceSet)
}
}
override fun Project.configureByAndroidVariants(extension: CodeOwnersJVMExtension) {
plugins.withId("com.android.base") {
fun bind(
component: Component,
defaultsTo: CodeOwnersJVMSourceSet? = null,
): CodeOwnersJVMSourceSet {
val sourceSet = extension.sourceSets.maybeCreate(component.name)
component.codeOwners = sourceSet
sourceSet.generateTask {
addDependencies(objects, sourceSet, component.runtimeConfiguration)
}
addCodeDependency(extension, sourceSet, component.compileConfiguration.name)
addCodeDependency(extension, sourceSet, component.runtimeConfiguration.name)
// TODO there is no `variant.sources.resources.addGeneratedSourceDirectory` DSL for this?
afterEvaluate {
@Suppress("DEPRECATION")
tasks.named("process${component.name.capitalize()}JavaRes")
.addResources(extension, sourceSet)
}
if (defaultsTo != null) {
sourceSet.enabled.convention(defaultsTo.enabled)
}
return sourceSet
}
AndroidSupport(project).configureVariants {
val variant = this@configureVariants
variant.packaging.resources.merges.add("**/*.codeowners")
val sourceSet = bind(variant)
(variant as? HasUnitTest)?.unitTest?.let { bind(component = it, defaultsTo = sourceSet) }
(variant as? HasAndroidTest)?.androidTest?.let { bind(component = it, defaultsTo = sourceSet) }
addOutgoingVariant(sourceSet, variant.runtimeConfiguration).configure {
val attrs = variant.runtimeConfiguration.attributes
// copies variant attributes
attributes {
attrs.keySet().forEach { key ->
if (key.name.startsWith("com.android")) {
@Suppress("UNCHECKED_CAST")
attribute(key as Attribute, attrs.getAttribute(key)!!)
}
}
}
}
}
}}
private fun CodeOwnersResourcesTask.addDependencies(
objects: ObjectFactory,
sourceSet: CodeOwnersJVMSourceSet,
configuration: Configuration,
) {
val mode = sourceSet.inspectDependencies.get()
if (mode != DependenciesMode.NONE) {
transitiveCodeOwners.from(configuration.incoming.artifactView {
/**
* [configuration] is `runtimeClasspath`, and we want to allow the `codeowners` outgoing variant to be picked
* otherwise, to avoid an unnecessary [CodeOwnersTransform] to be performed on its `.jar`
*/
withVariantReselection()
attributes {
copyNonStandardAttributes(configuration)
attribute(Usage.USAGE_ATTRIBUTE, objects.named(ARTIFACT_TYPE_CODEOWNERS))
attribute(ARTIFACT_TYPE_ATTRIBUTE, ARTIFACT_TYPE_CODEOWNERS)
}
if (mode == DependenciesMode.LOCAL_PROJECTS) {
componentFilter { it is ProjectComponentIdentifier }
}
}.files)
}
}
private fun TaskProvider.addResources(
extension: CodeOwnersJVMExtension,
sources: CodeOwnersJVMSourceSet
) = configure {
from(extension.enabled.and(sources.enabled).map {
if (it) sources.generateTask.map(CodeOwnersResourcesTask::outputDirectory) else emptyList()
})
}
private fun Project.addCodeDependency(
extension: CodeOwnersJVMExtension,
sourceSet: CodeOwnersJVMSourceSet,
configurationName: String,
) {
dependencies.add(
configurationName,
extension.enabled.and(sourceSet.enabled)
.map { if (it) BuildConfig.CORE_DEPENDENCY else files() })
}
private fun Project.addOutgoingVariant(
sourceSet: CodeOwnersJVMSourceSet,
base: Configuration,
) = configurations.register("${sourceSet.name}CodeOwnersElements") {
isCanBeResolved = false
isCanBeConsumed = true
description = "CODEOWNERS information for the sourceSet ${sourceSet.name}"
attributes {
copyNonStandardAttributes(base)
attribute(Usage.USAGE_ATTRIBUTE, objects.named(ARTIFACT_TYPE_CODEOWNERS))
}
outgoing {
base.outgoing.capabilities.forEach(::capability)
artifacts(sourceSet.enabled.map { enabled ->
if (enabled) listOf(sourceSet.generateTask.flatMap { it.rawMappedCodeOwnersFile })
else emptyList()
}) { type = ARTIFACT_TYPE_CODEOWNERS }
}
}
private fun Project.setupArtifactTransform(
objects: ObjectFactory,
) = dependencies {
registerTransform(CodeOwnersTransform::class) {
from.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
from.attribute(ARTIFACT_TYPE_ATTRIBUTE, JAR_TYPE)
to.attribute(Usage.USAGE_ATTRIBUTE, objects.named(ARTIFACT_TYPE_CODEOWNERS))
to.attribute(ARTIFACT_TYPE_ATTRIBUTE, ARTIFACT_TYPE_CODEOWNERS)
}
registerTransform(CodeOwnersTransform::class) {
from.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
from.attribute(ARTIFACT_TYPE_ATTRIBUTE, ARTIFACT_TYPE_ANDROID_JAVA_RES)
to.attribute(Usage.USAGE_ATTRIBUTE, objects.named(ARTIFACT_TYPE_CODEOWNERS))
to.attribute(ARTIFACT_TYPE_ATTRIBUTE, ARTIFACT_TYPE_CODEOWNERS)
}
}
@Suppress("UNCHECKED_CAST")
private fun > Type.copyNonStandardAttributes(
from: HasAttributes,
) = attributes {
from.attributes.keySet().forEach {
when (it.name) {
Usage.USAGE_ATTRIBUTE.name,
Category.CATEGORY_ATTRIBUTE.name,
LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE.name,
TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE.name,
"org.jetbrains.kotlin.platform.type" -> Unit // do nothing
else -> attribute(it as Attribute, from.attributes.getAttribute(it)!!)
}
}
}
private fun Provider.and(vararg others: Provider) =
sequenceOf(this, *others).reduce { acc, it -> acc.zip(it, Boolean::and) }
}