Repository: GradleUp/shadow (kotlin)

Issue:
Non-relocating shadow jars should use `Bundling.EMBEDDED`
### Description

The `shadowRuntimeElements` is published with the publication using the `Bundling.SHADOWED` attribute. In Gradle's [documentation for Bundling](https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api.attributes/-bundling/index.html):

> - `external` indicates that transitive dependencies are themselves component jars
> - `embedded` indicates that transitive dependencies have been included inside the component jar, without modifying their packages
> - `shadowed` indicates that transitive dependencies have been included inside the component jar, under different packages to prevent conflicts

With that in mind, I think it would be a good idea to check, after evaluation, if the shadowJar task is relocating dependencies. If it is, use `SHADOWED`. And if it isn't, use `EMBEDDED`. This could be especially important since Shadow's default behavior is to not relocate packages.

Pull request:
- Closes #1767.

Adds a new property to the `ShadowJar` task that dictates the bundling type to be used for the consumable configuration. I'm adding the bundling attribute using `AttributeContainer#attributeProvider` as lazily-added attribute aren't computed until they're needed, which is after project evaluation.

Once again looking for help with tests and code cleanup like last time. Kotlin is not my primary language :sweat_smile:

---

```kotlin
tasks.shadowJar {
  bundlingAttribute = 'embedded'
}
```

---

- [x] [CHANGELOG](https://github.com/GradleUp/shadow/blob/main/docs/changes/README.md)'s "Unreleased" section has been updated, if applicable.

Patch:
diff --git a/api/shadow.api b/api/shadow.api
index f48941658..5e2efff0c 100644
--- a/api/shadow.api
+++ b/api/shadow.api
@@ -43,6 +43,7 @@ public final class com/github/jengelman/gradle/plugins/shadow/ShadowBasePlugin$C
 public abstract interface class com/github/jengelman/gradle/plugins/shadow/ShadowExtension {
 	public abstract fun getAddShadowVariantIntoJavaComponent ()Lorg/gradle/api/provider/Property;
 	public abstract fun getAddTargetJvmVersionAttribute ()Lorg/gradle/api/provider/Property;
+	public abstract fun getBundlingAttribute ()Lorg/gradle/api/provider/Property;
 }
 
 public abstract class com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin : org/gradle/api/Plugin {
diff --git a/docs/changes/README.md b/docs/changes/README.md
index d8412e4ab..616b359bf 100644
--- a/docs/changes/README.md
+++ b/docs/changes/README.md
@@ -21,6 +21,14 @@
 - Support relocating Kotlin module files. ([#1539](https://github.com/GradleUp/shadow/pull/1539))  
   The current implementation relocates all properties in `KotlinModuleMetadata` but `KmModule.optionalAnnotationClasses`
   due to very limited usage of it. See more discussion [here](https://github.com/GradleUp/shadow/pull/1539#discussion_r2344237151).
+- Allow overriding `BUNDLING_ATTRIBUTE` in GMM. ([#1773](https://github.com/GradleUp/shadow/pull/1773))  
+  The `org.gradle.dependency.bundling` in shadowed JAR's Gradle Module Metadata is set to `shadowed` by default.
+  You can override it for now by:
+  ```kotlin
+  shadow {
+    bundlingAttribute = Bundling.EMBEDDED
+  }
+  ```
 
 ### Changed
 
diff --git a/docs/publishing/README.md b/docs/publishing/README.md
index a2be4715c..bec717df9 100644
--- a/docs/publishing/README.md
+++ b/docs/publishing/README.md
@@ -127,6 +127,28 @@ you can disable this by setting the `addTargetJvmVersionAttribute` property in t
     }
     ```
 
+The BUNDLING attribute (`org.gradle.dependency.bundling`) of the shadowed variant is set to `shadowed` by default,
+it is useful for consumers to distinguish between normal and shadowed dependencies. You can override this attribute by
+setting the `bundlingAttribute` property in the `shadow` extension:
+
+=== "Kotlin"
+
+    ```kotlin
+    shadow {
+      // Per description of the attribute, you should set it to either `Bundling.SHADOWED` or `Bundling.EMBEDDED`.
+      bundlingAttribute = Bundling.EMBEDDED
+    }
+    ```
+
+=== "Groovy"
+
+    ```groovy
+    shadow {
+      // Per description of the attribute, you should set it to either `Bundling.SHADOWED` or `Bundling.EMBEDDED`.
+      bundlingAttribute = Bundling.EMBEDDED
+    }
+    ```
+
 ## Shadow Configuration and Publishing
 
 The Shadow plugin provides a custom configuration (`configurations.shadow`) to specify
diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowBasePlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowBasePlugin.kt
index 43c1efddd..4ed334530 100644
--- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowBasePlugin.kt
+++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowBasePlugin.kt
@@ -8,6 +8,7 @@ import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.attributes.Bundling
 import org.gradle.api.component.SoftwareComponentContainer
 import org.gradle.api.distribution.DistributionContainer
 import org.gradle.api.plugins.ExtensionContainer
@@ -22,6 +23,7 @@ public abstract class ShadowBasePlugin : Plugin<Project> {
     with(extensions.create(EXTENSION_NAME, ShadowExtension::class.java)) {
       addShadowVariantIntoJavaComponent.convention(true)
       addTargetJvmVersionAttribute.convention(true)
+      bundlingAttribute.convention(Bundling.SHADOWED)
     }
     @Suppress("EagerGradleConfiguration") // this should be created eagerly.
     configurations.create(CONFIGURATION_NAME)
diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowExtension.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowExtension.kt
index 74ef80658..b74387e8a 100644
--- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowExtension.kt
+++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowExtension.kt
@@ -1,5 +1,6 @@
 package com.github.jengelman.gradle.plugins.shadow
 
+import org.gradle.api.attributes.Bundling
 import org.gradle.api.attributes.java.TargetJvmVersion
 import org.gradle.api.provider.Property
 
@@ -19,4 +20,13 @@ public interface ShadowExtension {
    * Defaults to `true`.
    */
   public val addTargetJvmVersionAttribute: Property<Boolean>
+
+  /**
+   * The [Bundling] attribute to use for the Gradle Module Metadata.
+   *
+   * Per description of the attribute, you should set it to either [Bundling.SHADOWED] or [Bundling.EMBEDDED].
+   *
+   * Defaults to [Bundling.SHADOWED].
+   */
+  public val bundlingAttribute: Property<String>
 }
diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt
index 1f30c01b3..6863c6353 100644
--- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt
+++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.kt
@@ -61,7 +61,10 @@ public abstract class ShadowJavaPlugin @Inject constructor(
           LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
           objects.named(LibraryElements::class.java, LibraryElements.JAR),
         )
-        attrs.attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling::class.java, Bundling.SHADOWED))
+        attrs.attributeProvider(
+          Bundling.BUNDLING_ATTRIBUTE,
+          shadow.bundlingAttribute.map { attr -> objects.named(Bundling::class.java, attr) },
+        )
       }
       it.outgoing.artifact(tasks.shadowJar)
     }


Test patch:
diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt
index 58f3eb93c..c7c31371d 100644
--- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt
+++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/PublishingTest.kt
@@ -191,6 +191,33 @@ class PublishingTest : BasePluginTest() {
     )
   }
 
+  @Test
+  fun overrideBundlingAttrInGradleMetadata() {
+    projectScript.appendText(
+      publishConfiguration(
+        projectBlock = """
+          shadow {
+            bundlingAttribute = Bundling.EMBEDDED
+          }
+        """.trimIndent(),
+        shadowBlock = """
+          archiveClassifier = ''
+          archiveBaseName = 'maven-all'
+        """.trimIndent(),
+      ),
+    )
+
+    publish()
+
+    assertShadowVariantCommon(
+      gmm = gmmAdapter.fromJson(repoPath("my/maven-all/1.0/maven-all-1.0.module")),
+      variantAttrs = commonVariantAttrs + arrayOf(
+        Bundling.BUNDLING_ATTRIBUTE.name to Bundling.EMBEDDED,
+        Usage.USAGE_ATTRIBUTE.name to Usage.JAVA_RUNTIME,
+      ),
+    )
+  }
+
   @Test
   fun publishShadowJarInsteadOfJar() {
     projectScript.appendText(
diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt
index b407fc810..bda146dc6 100644
--- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt
+++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/ShadowPropertiesTest.kt
@@ -25,6 +25,7 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.shad
 import org.gradle.api.Named
 import org.gradle.api.Project
 import org.gradle.api.Task
+import org.gradle.api.attributes.Bundling
 import org.gradle.api.file.DuplicatesStrategy
 import org.gradle.api.plugins.ApplicationPlugin
 import org.gradle.api.plugins.JavaPlugin
@@ -59,6 +60,7 @@ class ShadowPropertiesTest {
     with(extensions.getByType(ShadowExtension::class.java)) {
       assertThat(addShadowVariantIntoJavaComponent.get()).isTrue()
       assertThat(addTargetJvmVersionAttribute.get()).isTrue()
+      assertThat(bundlingAttribute.get()).isEqualTo(Bundling.SHADOWED)
     }
   }