Repository: GradleUp/shadow (kotlin)

Issue:
Configuring publishing before shadowJar publishes jar with wrong configuration
I discovered that my project was configuring publishing before the actual shadowJar, which was causing the wrong classifier to get picked up by the shadow plugin. Is this a reasonable situation that could be improved with lazy evaluation, or should I restructure my build file to configure shadowJar first?

### Shadow Version

8.1.4-8.1.7

### Gradle Version

8.8

### Expected Behavior

The archive published has the classifier that I specified in shadowJar archiveClassifier

### Actual Behavior

The archiveClassifier is `all`

### Gradle Build Script(s)

See this modified unit test in my fork, which is failing: https://github.com/gharris1727/shadow/blob/c08186d96fe9ce4799f67660c03ac34c65fc61c3/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PublishingSpec.groovy#L81-L127

### Content of Shadow JAR (`jar tf <jar file>` - post link to GIST if too long)

N/A

Pull request:
- Closes #945.
- Closes https://github.com/Goooler/shadow/issues/106.

---

- [x] [CHANGELOG](src/docs/changes/README.md)'s "Unreleased" section has been updated, if applicable.

Patch:
diff --git a/src/docs/changes/README.md b/src/docs/changes/README.md
index a9dfcd1d3..6d38e2b34 100644
--- a/src/docs/changes/README.md
+++ b/src/docs/changes/README.md
@@ -7,6 +7,8 @@
 
 **Changed**
 
+- `ShadowExtension.component` has been deprecated, now you can use `component.shadow` instead. ([#956](https://github.com/GradleUp/shadow/pull/956))
+
 **Fixed**
 
 - Stop publishing Shadow self fat jar to Maven repository. ([#967](https://github.com/GradleUp/shadow/pull/967))
diff --git a/src/docs/publishing/README.md b/src/docs/publishing/README.md
index 66da927f0..16c4fb400 100644
--- a/src/docs/publishing/README.md
+++ b/src/docs/publishing/README.md
@@ -15,8 +15,8 @@ apply plugin: 'com.gradleup.shadow'
 
 publishing {
   publications {
-    shadow(MavenPublication) { publication ->
-      project.shadow.component(publication)
+    shadow(MavenPublication) {
+      components.shadow
     }
   }
   repositories {
diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowExtension.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowExtension.groovy
index 03a575d54..53fc8b38d 100644
--- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowExtension.groovy
+++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowExtension.groovy
@@ -1,74 +1,22 @@
 package com.github.jengelman.gradle.plugins.shadow
 
-import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
 import org.gradle.api.Project
-import org.gradle.api.artifacts.ProjectDependency
-import org.gradle.api.artifacts.SelfResolvingDependency
-import org.gradle.api.file.RegularFile
-import org.gradle.api.provider.Property
-import org.gradle.api.provider.Provider
-import org.gradle.api.publish.maven.MavenPom
+import org.gradle.api.component.SoftwareComponentContainer
 import org.gradle.api.publish.maven.MavenPublication
 
+@Deprecated
 class ShadowExtension {
-    private final Provider<Archive> archive
-    private final Provider<List<Dep>> allDependencies
+    private final SoftwareComponentContainer components
 
     ShadowExtension(Project project) {
-        archive = project.provider {
-            def archiveTask = project.tasks.withType(ShadowJar).getByName("shadowJar")
-            new Archive(archiveTask.archiveFile, archiveTask.archiveClassifier)
-        }
-        allDependencies = project.provider {
-            project.configurations.getByName("shadow").allDependencies.collect {
-                if ((it instanceof ProjectDependency) || !(it instanceof SelfResolvingDependency)) {
-                    new Dep(it.group, it.name, it.version)
-                }
-            }
-        }
+        components = project.components
     }
 
+    /**
+     * @deprecated configure publication using component.shadow directly.
+     */
+    @Deprecated
     void component(MavenPublication publication) {
-        publication.artifact([
-                source    : archive.get().file,
-                classifier: archive.get().classifier.get()
-        ])
-
-        // Don't inline this variable, it seems Groovy closure capturing is confused by the field instead of a local variable.
-        final def allDeps = allDependencies
-        publication.pom { MavenPom pom ->
-            pom.withXml { xml ->
-                def dependenciesNode = xml.asNode().get('dependencies') ?: xml.asNode().appendNode('dependencies')
-                allDeps.get().each {
-                    def dependencyNode = dependenciesNode.appendNode('dependency')
-                    dependencyNode.appendNode('groupId', it.group)
-                    dependencyNode.appendNode('artifactId', it.name)
-                    dependencyNode.appendNode('version', it.version)
-                    dependencyNode.appendNode('scope', 'runtime')
-                }
-            }
-        }
-    }
-
-    private class Archive {
-        Provider<RegularFile> file
-        Property<String> classifier
-
-        Archive(Provider<RegularFile> file, Property<String> classifier) {
-            this.file = file
-            this.classifier = classifier
-        }
-    }
-
-    private class Dep {
-        String group
-        String name
-        String version
-
-        Dep(String group, String name, String version) {
-            this.group = group
-            this.name = name
-            this.version = version
-        }
+        publication.from(components.findByName("shadow"))
     }
 }
diff --git a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy
index d3926974e..f1f2800e4 100644
--- a/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy
+++ b/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy
@@ -8,8 +8,9 @@ import org.gradle.api.attributes.Category
 import org.gradle.api.attributes.LibraryElements
 import org.gradle.api.attributes.Usage
 import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.component.AdhocComponentWithVariants
+import org.gradle.api.component.SoftwareComponentFactory
 import org.gradle.api.tasks.SourceSetContainer
-import org.gradle.configuration.project.ProjectConfigurationActionContainer
 import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin
 
 import javax.inject.Inject
@@ -19,11 +20,11 @@ class ShadowJavaPlugin implements Plugin<Project> {
     public static final String SHADOW_JAR_TASK_NAME = 'shadowJar'
     public static final String SHADOW_GROUP = 'Shadow'
 
-    private final ProjectConfigurationActionContainer configurationActionContainer
+    private final SoftwareComponentFactory softwareComponentFactory
 
     @Inject
-    ShadowJavaPlugin(ProjectConfigurationActionContainer configurationActionContainer) {
-        this.configurationActionContainer = configurationActionContainer
+    ShadowJavaPlugin(SoftwareComponentFactory softwareComponentFactory) {
+        this.softwareComponentFactory = softwareComponentFactory
     }
 
     @Override
@@ -54,6 +55,12 @@ class ShadowJavaPlugin implements Plugin<Project> {
             }
         }
 
+        AdhocComponentWithVariants shadow = softwareComponentFactory.adhoc("shadow")
+        project.components.add(shadow)
+        shadow.addVariantsFromConfiguration(project.configurations.shadowRuntimeElements) {
+            it.mapToMavenScope("runtime")
+        }
+
         project.plugins.withType(JavaGradlePluginPlugin).configureEach {
             // Remove the gradleApi so it isn't merged into the jar file.
             // This is required because 'java-gradle-plugin' adds gradleApi() to the 'api' configuration.


Test patch:
diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PublishingSpec.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PublishingSpec.groovy
index 450f2a698..bc6221a1b 100644
--- a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PublishingSpec.groovy
+++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/PublishingSpec.groovy
@@ -6,6 +6,7 @@ import groovy.json.JsonSlurper
 import groovy.xml.XmlSlurper
 import org.gradle.api.attributes.Bundling
 import org.gradle.api.attributes.Usage
+import spock.lang.Issue
 
 class PublishingSpec extends PluginSpecification {
 
@@ -41,8 +42,8 @@ class PublishingSpec extends PluginSpecification {
             
             publishing {
                publications {
-                   shadow(MavenPublication) { publication ->
-                       project.shadow.component(publication)
+                   shadow(MavenPublication) {
+                       from components.shadow
                        artifactId = 'maven-all'
                    }
                }
@@ -78,6 +79,10 @@ class PublishingSpec extends PluginSpecification {
         assert dependency.version.text() == '1.0'
     }
 
+    @Issue([
+            "https://github.com/GradleUp/shadow/issues/860",
+            "https://github.com/GradleUp/shadow/issues/945",
+    ])
     def "publish shadow jar with maven-publish plugin using custom classifier and extension"() {
         given:
         repo.module('shadow', 'a', '1.0')
@@ -97,12 +102,6 @@ class PublishingSpec extends PluginSpecification {
                shadow 'shadow:b:1.0'
             }
             
-            tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
-               archiveClassifier = 'my-classifier'
-               archiveExtension = 'my-ext'
-               archiveBaseName = 'maven-all'
-            }
-            
             publishing {
                publications {
                    shadow(MavenPublication) { publication ->
@@ -116,6 +115,12 @@ class PublishingSpec extends PluginSpecification {
                    }
                }
             }
+            
+            tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar) {
+               archiveClassifier = 'my-classifier'
+               archiveExtension = 'my-ext'
+               archiveBaseName = 'maven-all'
+            }
         """.stripIndent()
 
         when:
@@ -191,8 +196,8 @@ class PublishingSpec extends PluginSpecification {
             
             publishing {
                publications {
-                   shadow(MavenPublication) { publication ->
-                       project.shadow.component(publication)
+                   shadow(MavenPublication) {
+                       from components.shadow
                        artifactId = 'maven-all'
                    }
                }
@@ -250,6 +255,10 @@ class PublishingSpec extends PluginSpecification {
                    java(MavenPublication) {
                        from components.java
                    }
+                   shadow(MavenPublication) {
+                       from components.shadow
+                       artifactId = "maven-all"
+                   }
                }
                repositories {
                    maven {
@@ -316,5 +325,42 @@ class PublishingSpec extends PluginSpecification {
         shadowRuntimeVariant.dependencies.size() == 1
         shadowRuntimeVariant.dependencies.module as Set == ['b'] as Set
 
+        and: "verify shadow publication"
+        assertions {
+            shadowJar = publishingRepo.rootDir.file('com/acme/maven-all/1.0/maven-all-1.0-all.jar').canonicalFile
+            assert shadowJar.exists()
+            contains(shadowJar, ['a.properties', 'a2.properties'])
+        }
+
+        assertions {
+            pom = publishingRepo.rootDir.file('com/acme/maven-all/1.0/maven-all-1.0.pom').canonicalFile
+            assert pom.exists()
+            pomContents = new XmlSlurper().parse(pom)
+            assert pomContents.dependencies[0].dependency.size() == 1
+
+            dependency1 = pomContents.dependencies[0].dependency[0]
+            assert dependency1.groupId.text() == 'shadow'
+            assert dependency1.artifactId.text() == 'b'
+            assert dependency1.version.text() == '1.0'
+
+            dependency2 = pomContents.dependencies[0].dependency[1]
+            dependency2.groupId.text() == 'shadow'
+            dependency2.artifactId.text() == 'b'
+            dependency2.version.text() == '1.0'
+        }
+
+        assertions {
+            gmm = publishingRepo.rootDir.file('com/acme/maven-all/1.0/maven-all-1.0.module').canonicalFile
+            assert gmm.exists()
+            gmmContents = new JsonSlurper().parse(gmm)
+            assert gmmContents.variants.size() == 1
+            assert gmmContents.variants.name as Set == ['shadowRuntimeElements'] as Set
+
+            runtimeVariant = gmmContents.variants.find { it.name == 'shadowRuntimeElements' }
+            assert runtimeVariant.attributes[Usage.USAGE_ATTRIBUTE.name] == Usage.JAVA_RUNTIME
+            assert runtimeVariant.attributes[Bundling.BUNDLING_ATTRIBUTE.name] == Bundling.SHADOWED
+            assert runtimeVariant.dependencies.size() == 1
+            assert runtimeVariant.dependencies.module as Set == ['b'] as Set
+        }
     }
 }
diff --git a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/PluginSpecification.groovy b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/PluginSpecification.groovy
index eb7ffa2fa..9e5fc66f4 100644
--- a/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/PluginSpecification.groovy
+++ b/src/test/groovy/com/github/jengelman/gradle/plugins/shadow/util/PluginSpecification.groovy
@@ -150,6 +150,15 @@ abstract class PluginSpecification extends Specification {
         jar.close()
     }
 
+    /**
+     * Helper method to allow scoping variables into a closure in a spock test
+     * Prevents variable expansion
+     * When using this you *must* include explicit `assert` statements as Spock will not do it for you
+     */
+    void assertions(Closure closure) {
+        closure()
+    }
+
     AppendableJar buildJar(String path) {
         return new AppendableJar(file(path))
     }