From 4e0f7a7b4f66d7331580f5807bfe301624e9c7ba Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:28:40 +0200 Subject: [PATCH] workaround bugged Gradle attributes (#215) * workaround bugged Gradle attributes - use `Attribute` 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. https://github.com/adamko-dev/dokkatoo/issues/214 * Update modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt --- .../kotlin/dependencies/DokkatooAttribute.kt | 21 ++++-- .../dependencies/FormatDependenciesManager.kt | 17 +++-- .../ModuleComponentDependencies.kt | 28 ++++---- .../kotlin/dependencies/attributeValues.kt | 17 +++-- .../kotlin/formats/DokkatooFormatPlugin.kt | 8 +-- .../src/main/kotlin/internal/gradleUtils.kt | 72 +++++++++++++++++-- 6 files changed, 119 insertions(+), 44 deletions(-) diff --git a/modules/dokkatoo-plugin/src/main/kotlin/dependencies/DokkatooAttribute.kt b/modules/dokkatoo-plugin/src/main/kotlin/dependencies/DokkatooAttribute.kt index ad4baf46..c5ce803a 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/dependencies/DokkatooAttribute.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/dependencies/DokkatooAttribute.kt @@ -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 = + val DokkatooFormatAttribute: Attribute = Attribute("dev.adamko.dokkatoo.format") - val DokkatooModuleComponentAttribute: Attribute = + val DokkatooModuleComponentAttribute: Attribute = Attribute("dev.adamko.dokkatoo.module-component") - val DokkatooClasspathAttribute: Attribute = + val DokkatooClasspathAttribute: Attribute = Attribute("dev.adamko.dokkatoo.classpath") } } diff --git a/modules/dokkatoo-plugin/src/main/kotlin/dependencies/FormatDependenciesManager.kt b/modules/dokkatoo-plugin/src/main/kotlin/dependencies/FormatDependenciesManager.kt index f0f4b3ed..0219b115 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/dependencies/FormatDependenciesManager.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/dependencies/FormatDependenciesManager.kt @@ -48,7 +48,6 @@ class FormatDependenciesManager( internal val formatAttributes: FormatAttributes = FormatAttributes( formatName = formatName, - objects = objects, ) init { @@ -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 @@ -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) } } @@ -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) } } } @@ -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 diff --git a/modules/dokkatoo-plugin/src/main/kotlin/dependencies/ModuleComponentDependencies.kt b/modules/dokkatoo-plugin/src/main/kotlin/dependencies/ModuleComponentDependencies.kt index cfb3178d..23518ecc 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/dependencies/ModuleComponentDependencies.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/dependencies/ModuleComponentDependencies.kt @@ -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) } } @@ -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) } } @@ -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) } @@ -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 } } diff --git a/modules/dokkatoo-plugin/src/main/kotlin/dependencies/attributeValues.kt b/modules/dokkatoo-plugin/src/main/kotlin/dependencies/attributeValues.kt index 676a7339..a67d9205 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/dependencies/attributeValues.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/dependencies/attributeValues.kt @@ -15,10 +15,15 @@ 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") } @@ -26,10 +31,10 @@ class BaseAttributes( @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") } diff --git a/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatPlugin.kt b/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatPlugin.kt index 661d6849..fdcfb7e9 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatPlugin.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/formats/DokkatooFormatPlugin.kt @@ -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 */ diff --git a/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt b/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt index 30f6bc6f..412e34e9 100644 --- a/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt +++ b/modules/dokkatoo-plugin/src/main/kotlin/internal/gradleUtils.kt @@ -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 Attribute( name: String ): Attribute = 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 = + Attribute.of(name, String::class.java) + + internal val ArtifactTypeAttribute: Attribute = Attribute("artifactType") @@ -262,14 +279,59 @@ internal fun AttributeContainer.toMap(): Map, 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 AttributeContainer.get(key: Attribute): T? = - getAttribute(key) +internal operator fun AttributeContainer.get(key: Attribute): 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 Attribute?.eq(other: Attribute) = - 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 }