Repository: GradleUp/shadow
Pull request #1441 (2025-05-27 21:06:59)

Allow using file trees of JARs together with the configuration cache
We are adding JARs from a directory that is built by a task to the `ShadowJar` by using `includedDependencies`. That doesn't seem to work when using the configuration cache, since the contents of the directory are evaluated at configuration time, causing them always to be empty.

This PR fixes that by doing the actual resolution as late as in the task action, not when the `includedZipTrees` provider is being stored in the configuration cache.

---

- [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 741150c7..596591c9 100644
--- a/api/shadow.api
+++ b/api/shadow.api
@@ -182,6 +182,7 @@ public abstract class com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar
 	protected fun createCopyAction ()Lorg/gradle/api/internal/file/copy/CopyAction;
 	public fun dependencies (Lorg/gradle/api/Action;)V
 	public fun getApiJars ()Lorg/gradle/api/file/ConfigurableFileCollection;
+	protected abstract fun getArchiveOperations ()Lorg/gradle/api/file/ArchiveOperations;
 	public fun getConfigurations ()Lorg/gradle/api/provider/SetProperty;
 	public fun getDependencyFilter ()Lorg/gradle/api/provider/Property;
 	public fun getEnableRelocation ()Lorg/gradle/api/provider/Property;
diff --git a/docs/changes/README.md b/docs/changes/README.md
index e1488af6..03e3164a 100644
--- a/docs/changes/README.md
+++ b/docs/changes/README.md
@@ -6,6 +6,10 @@
 
 - Update start script templates. ([#1419](https://github.com/GradleUp/shadow/pull/1419))
 
+**Fixed**
+
+- Allow using file trees of JARs together with the configuration cache. ([#1441](https://github.com/GradleUp/shadow/pull/1441))
+
 ## [9.0.0-beta13](https://github.com/GradleUp/shadow/releases/tag/9.0.0-beta13) - 2025-04-29
 
 **Changed**
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 96895ad9..cc24bb57 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -36,4 +36,4 @@ jetbrains-bcv = "org.jetbrains.kotlinx.binary-compatibility-validator:0.17.0"
 jetbrains-dokka = "org.jetbrains.dokka:2.0.0"
 mavenPublish = "com.vanniktech.maven.publish:0.32.0"
 pluginPublish = { id = "com.gradle.plugin-publish", version.ref = "pluginPublish" }
-spotless = "com.diffplug.spotless:7.0.3"
+spotless = "com.diffplug.spotless:7.0.4"
diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt
index 2e932073..dd00a5c4 100644
--- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt
+++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt
@@ -22,12 +22,14 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.ServiceFileTransf
 import java.io.File
 import java.io.IOException
 import java.util.jar.JarFile
+import javax.inject.Inject
 import kotlin.reflect.full.hasAnnotation
 import org.apache.tools.zip.Zip64Mode
 import org.apache.tools.zip.ZipOutputStream
 import org.gradle.api.Action
 import org.gradle.api.UncheckedIOException
 import org.gradle.api.artifacts.Configuration
+import org.gradle.api.file.ArchiveOperations
 import org.gradle.api.file.ConfigurableFileCollection
 import org.gradle.api.file.DuplicatesStrategy
 import org.gradle.api.internal.file.FileResolver
@@ -54,10 +56,6 @@ public abstract class ShadowJar :
   ShadowSpec {
   private val dependencyFilterForMinimize = MinimizeDependencyFilter(project)
 
-  private val includedZipTrees = project.provider {
-    includedDependencies.files.map { project.zipTree(it) }
-  }
-
   init {
     // https://github.com/gradle/gradle/blob/df5bc230c57db70aa3f6909403e5f89d7efde531/platforms/core-configuration/file-operations/src/main/java/org/gradle/api/internal/file/copy/DuplicateHandlingCopyActionDecorator.java#L55-L64
     duplicatesStrategy = DuplicatesStrategy.INCLUDE
@@ -164,6 +162,9 @@ public abstract class ShadowJar :
   @Input // Trigger task executions after excludes changed.
   override fun getExcludes(): MutableSet<String> = super.getExcludes()
 
+  @get:Inject
+  protected abstract val archiveOperations: ArchiveOperations
+
   /**
    * Enable [minimizeJar], this equals to `minimizeJar.set(true)`.
    */
@@ -311,7 +312,7 @@ public abstract class ShadowJar :
 
   @TaskAction
   override fun copy() {
-    from(includedZipTrees.get())
+    from(includedDependencies.files.map { archiveOperations.zipTree(it) })
     injectMultiReleaseAttrIfPresent()
     super.copy()
   }


Test patch:
diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginTest.kt
index 0d9960b3..188942df 100644
--- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginTest.kt
+++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/JavaPluginTest.kt
@@ -724,4 +724,44 @@ class JavaPluginTest : BasePluginTest() {
       }
     }
   }
+
+  @Issue(
+    "https://github.com/GradleUp/shadow/issues/1441",
+  )
+  @Test
+  fun includeFilesInTaskOutputDirectory() {
+    // Create a build that has a task with jars in the output directory
+    projectScriptPath.appendText(
+      """
+      def createJars = tasks.register('createJars') {
+        def artifactAJar = file('${artifactAJar.toUri().toURL().path}')
+        def artifactBJar = file('${artifactBJar.toUri().toURL().path}')
+        inputs.files(artifactAJar, artifactBJar)
+        def outputDir = file('${'$'}{buildDir}/jars')
+        outputs.dir(outputDir)
+        doLast {
+          artifactAJar.withInputStream { input ->
+              new File(outputDir, 'jarA.jar').withOutputStream { output ->
+                  output << input
+              }
+          }
+          artifactBJar.withInputStream { input ->
+              new File(outputDir, 'jarB.jar').withOutputStream { output ->
+                  output << input
+              }
+          }
+        }
+      }
+      $shadowJar {
+        includedDependencies.from(files(createJars).asFileTree)
+      }
+      """.trimIndent(),
+    )
+
+    run(shadowJarTask)
+
+    assertThat(outputShadowJar).useAll {
+      containsOnly(*entriesInAB, *manifestEntries)
+    }
+  }
 }