Skip to content

Commit

Permalink
workaround bugged Gradle attributes (#215)
Browse files Browse the repository at this point in the history
* workaround bugged Gradle attributes

- use `Attribute<String>` instead of typed attributes.
- change the Dokkatoo attributes to be value classes, so they can still be used mostly type-safely.
- Improve logging (include hashcodes) and error reporting.

#214

* Update modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt
  • Loading branch information
aSemy authored Apr 5, 2024
1 parent ce50b94 commit 4e0f7a7
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,34 @@ interface DokkatooAttribute {

/** HTML, Markdown, etc. */
@DokkatooInternalApi
interface Format : Named
@JvmInline
value class Format(private val named: String) : Named {
override fun getName(): String = named
}

/** Generated output, or subproject classpath, or included files, etc */
@DokkatooInternalApi
interface ModuleComponent : Named
@JvmInline
value class ModuleComponent(private val named: String) : Named {
override fun getName(): String = named
}

/** A classpath, e.g. for Dokka Plugins or the Dokka Generator. */
@DokkatooInternalApi
interface Classpath : Named
@JvmInline
value class Classpath(private val named: String) : Named {
override fun getName(): String = named
}

@DokkatooInternalApi
companion object {
val DokkatooFormatAttribute: Attribute<Format> =
val DokkatooFormatAttribute: Attribute<String> =
Attribute("dev.adamko.dokkatoo.format")

val DokkatooModuleComponentAttribute: Attribute<ModuleComponent> =
val DokkatooModuleComponentAttribute: Attribute<String> =
Attribute("dev.adamko.dokkatoo.module-component")

val DokkatooClasspathAttribute: Attribute<Classpath> =
val DokkatooClasspathAttribute: Attribute<String> =
Attribute("dev.adamko.dokkatoo.classpath")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ class FormatDependenciesManager(
internal val formatAttributes: FormatAttributes =
FormatAttributes(
formatName = formatName,
objects = objects,
)

init {
Expand Down Expand Up @@ -94,8 +93,8 @@ class FormatDependenciesManager(
isTransitive = false
attributes {
jvmJar()
attribute(DokkatooFormatAttribute, formatAttributes.format)
attribute(DokkatooClasspathAttribute, baseAttributes.dokkaPlugins)
attribute(DokkatooFormatAttribute, formatAttributes.format.name)
attribute(DokkatooClasspathAttribute, baseAttributes.dokkaPlugins.name)
}
}
//endregion
Expand All @@ -117,8 +116,8 @@ class FormatDependenciesManager(
extendsFrom(dokkaPublicationPluginClasspath.get())
attributes {
jvmJar()
attribute(DokkatooFormatAttribute, formatAttributes.format)
attribute(DokkatooClasspathAttribute, baseAttributes.dokkaPublicationPlugins)
attribute(DokkatooFormatAttribute, formatAttributes.format.name)
attribute(DokkatooClasspathAttribute, baseAttributes.dokkaPublicationPlugins.name)
}
}

Expand All @@ -137,8 +136,8 @@ class FormatDependenciesManager(
extendsFrom(dokkaPublicationPluginClasspathApiOnly.get())
attributes {
jvmJar()
attribute(DokkatooFormatAttribute, formatAttributes.format)
attribute(DokkatooClasspathAttribute, baseAttributes.dokkaPublicationPlugins)
attribute(DokkatooFormatAttribute, formatAttributes.format.name)
attribute(DokkatooClasspathAttribute, baseAttributes.dokkaPublicationPlugins.name)
}
}
}
Expand Down Expand Up @@ -186,8 +185,8 @@ class FormatDependenciesManager(

attributes {
jvmJar()
attribute(DokkatooFormatAttribute, formatAttributes.format)
attribute(DokkatooClasspathAttribute, baseAttributes.dokkaGenerator)
attribute(DokkatooFormatAttribute, formatAttributes.format.name)
attribute(DokkatooClasspathAttribute, baseAttributes.dokkaGenerator.name)
}
}
//endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class ModuleComponentDependencies(
extendsFrom(declaredDependencies)
attributes {
attribute(USAGE_ATTRIBUTE, baseAttributes.dokkatooUsage)
attribute(DokkatooFormatAttribute, formatAttributes.format)
attribute(DokkatooModuleComponentAttribute, component)
attribute(DokkatooFormatAttribute, formatAttributes.format.name)
attribute(DokkatooModuleComponentAttribute, component.name)
}
}

Expand All @@ -46,8 +46,8 @@ class ModuleComponentDependencies(
extendsFrom(declaredDependencies)
attributes {
attribute(USAGE_ATTRIBUTE, baseAttributes.dokkatooUsage)
attribute(DokkatooFormatAttribute, formatAttributes.format)
attribute(DokkatooModuleComponentAttribute, component)
attribute(DokkatooFormatAttribute, formatAttributes.format.name)
attribute(DokkatooModuleComponentAttribute, component.name)
}
}

Expand Down Expand Up @@ -83,8 +83,8 @@ class ModuleComponentDependencies(
withVariantReselection()
attributes {
attribute(USAGE_ATTRIBUTE, usage)
attribute(DokkatooFormatAttribute, formatAttributes.format)
attribute(DokkatooModuleComponentAttribute, component)
attribute(DokkatooFormatAttribute, formatAttributes.format.name)
attribute(DokkatooModuleComponentAttribute, component.name)
}
lenient(true)
}
Expand All @@ -98,23 +98,23 @@ class ModuleComponentDependencies(
.filter { artifact ->
val variantAttributes = artifact.variant.attributes
when {
artifact.variant.attributes[USAGE_ATTRIBUTE]?.name != baseAttributes.dokkatooUsage.name -> {
logger.info("[${incomingName}] ignoring artifact $artifact - USAGE_ATTRIBUTE != ${baseAttributes.dokkatooUsage} | attributes:${variantAttributes.toMap()}")
variantAttributes[USAGE_ATTRIBUTE]?.name != baseAttributes.dokkatooUsage.name -> {
logger.info("[${incomingName}] ignoring artifact $artifact - USAGE_ATTRIBUTE != ${baseAttributes.dokkatooUsage} | attributes:${variantAttributes.toDebugString()}")
false
}

variantAttributes[DokkatooFormatAttribute]?.name != formatAttributes.format.name -> {
logger.info("[${incomingName}] ignoring artifact $artifact - DokkatooFormatAttribute != ${formatAttributes.format} | attributes:${variantAttributes.toMap()}")
variantAttributes[DokkatooFormatAttribute] != formatAttributes.format.name -> {
logger.info("[${incomingName}] ignoring artifact $artifact - DokkatooFormatAttribute != ${formatAttributes.format} | attributes:${variantAttributes.toDebugString()}")
false
}

variantAttributes[DokkatooModuleComponentAttribute]?.name != component.name -> {
logger.info("[${incomingName}] ignoring artifact $artifact - DokkatooModuleComponentAttribute != $component | attributes:${variantAttributes.toMap()}")
variantAttributes[DokkatooModuleComponentAttribute] != component.name -> {
logger.info("[${incomingName}] ignoring artifact $artifact - DokkatooModuleComponentAttribute != $component | attributes:${variantAttributes.toDebugString()}")
false
}

else -> {
logger.info("[${incomingName}] found valid artifact $artifact | attributes:${variantAttributes.toMap()}")
else -> {
logger.info("[${incomingName}] found valid artifact $artifact | attributes:${variantAttributes.toDebugString()}")
true
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,26 @@ class BaseAttributes(
objects: ObjectFactory,
) {
val dokkatooUsage: Usage = objects.named("dev.adamko.dokkatoo")
val dokkaPlugins: DokkatooAttribute.Classpath = objects.named("dokka-plugins")

val dokkaPlugins: DokkatooAttribute.Classpath =
DokkatooAttribute.Classpath("dokka-plugins")

val dokkaPublicationPlugins: DokkatooAttribute.Classpath =
objects.named("dokka-publication-plugins")
val dokkaGenerator: DokkatooAttribute.Classpath = objects.named("dokka-generator")
DokkatooAttribute.Classpath("dokka-publication-plugins")

val dokkaGenerator: DokkatooAttribute.Classpath =
DokkatooAttribute.Classpath("dokka-generator")
}


/** [Attribute] values for a specific Dokka format. */
@DokkatooInternalApi
class FormatAttributes(
formatName: String,
objects: ObjectFactory,
) {
val format: DokkatooAttribute.Format = objects.named(formatName)
val format: DokkatooAttribute.Format =
DokkatooAttribute.Format(formatName)

val moduleOutputDirectories: DokkatooAttribute.ModuleComponent =
objects.named("ModuleOutputDirectories")
DokkatooAttribute.ModuleComponent("ModuleOutputDirectories")
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,13 @@ abstract class DokkatooFormatPlugin(
dokkatooExtension.versions.jetbrainsDokka.map { version -> create("org.jetbrains.dokka:$module:$version") }

private fun AttributeContainer.dokkaPluginsClasspath() {
attribute(DokkatooFormatAttribute, formatDependencies.formatAttributes.format)
attribute(DokkatooClasspathAttribute, formatDependencies.baseAttributes.dokkaPlugins)
attribute(DokkatooFormatAttribute, formatDependencies.formatAttributes.format.name)
attribute(DokkatooClasspathAttribute, formatDependencies.baseAttributes.dokkaPlugins.name)
}

private fun AttributeContainer.dokkaGeneratorClasspath() {
attribute(DokkatooFormatAttribute, formatDependencies.formatAttributes.format)
attribute(DokkatooClasspathAttribute, formatDependencies.baseAttributes.dokkaGenerator)
attribute(DokkatooFormatAttribute, formatDependencies.formatAttributes.format.name)
attribute(DokkatooClasspathAttribute, formatDependencies.baseAttributes.dokkaGenerator.name)
}

/** Add a dependency to the Dokka plugins classpath */
Expand Down
72 changes: 67 additions & 5 deletions modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -237,16 +237,33 @@ internal fun ObjectFactory.dokkaPluginParametersContainer(): DokkaPluginParamete


/**
* Creates a new attribute of the given name with the given type.
* Creates a new [Attribute] of the given name with the given type [T].
*
* @see Attribute.of
*/
@Deprecated(
"Typed attributes are broken - use String attributes instead. https://github.com/adamko-dev/dokkatoo/issues/214",
ReplaceWith("dev.adamko.dokkatoo.internal.Attribute(name)"),
)
@JvmName("TypedAttribute")
internal inline fun <reified T> Attribute(
name: String
): Attribute<T> =
Attribute.of(name, T::class.java)


/**
* Creates a new [Attribute] of the given name with a type of [String].
*
* @see Attribute.of
*/
@JvmName("StringAttribute")
internal fun Attribute(
name: String
): Attribute<String> =
Attribute.of(name, String::class.java)


internal val ArtifactTypeAttribute: Attribute<String> = Attribute("artifactType")


Expand All @@ -262,14 +279,59 @@ internal fun AttributeContainer.toMap(): Map<Attribute<*>, Any?> =
keySet().associateWith { getAttribute(it) }


internal fun AttributeContainer.toDebugString(): String =
toMap().entries.joinToString { (k, v) -> "$k[name:${k.name}, type:${k.type}, type.hc:${k.type.hashCode()}]=$v" }


/**
* Get an [Attribute] from an [AttributeContainer].
*
* (Nicer Kotlin accessor function).
*/
internal operator fun <T : Any> AttributeContainer.get(key: Attribute<T>): T? =
getAttribute(key)
internal operator fun <T : Any> AttributeContainer.get(key: Attribute<T>): T? {
// first, try the official way
val value = getAttribute(key)
if (value != null) {
return value
}

// Failed to get attribute using official method, which might have been caused by a Gradle bug
// https://github.com/gradle/gradle/issues/28695
// Attempting to check...

internal infix fun <T> Attribute<T>?.eq(other: Attribute<T>) =
this?.name == other.name
// Quickly check that any attribute has the same name.
// (There's no point in checking further if no names match.)
if (keySet().none { it.name == key.name }) {
return null
}

val actualKey = keySet()
.firstOrNull { candidate -> candidate.matchesTypeOf(key) }
?: return null

error(
"""
Gradle failed to fetch attribute from AttributeContainer, even though the attribute is present.
Please report this error to Gradle https://github.com/gradle/gradle/issues/28695
Requested attribute: $key ${key.type} ${key.type.hashCode()}
Actual attribute: $actualKey ${actualKey.type} ${actualKey.type.hashCode()}
All attributes: ${toDebugString()}
Gradle Version: $CurrentGradleVersion
""".trimIndent()
)
}

/** Leniently check if [Attribute.type]s are equal, avoiding [Class.hashCode] classloader differences. */
private fun Attribute<*>.matchesTypeOf(other: Attribute<*>): Boolean {
val thisTypeId = this.typeId() ?: false
val otherTypeId = other.typeId() ?: false
return thisTypeId == otherTypeId
}

/**
* An ID for [Attribute.type] that is stable across different classloaders.
*
* Workaround for https://github.com/gradle/gradle/issues/28695.
*/
private fun Attribute<*>.typeId(): String? =
type.toString().ifBlank { null }

0 comments on commit 4e0f7a7

Please sign in to comment.