Repository: GradleUp/shadow (kotlin)

Issue:
ApacheNoticeResourceTransformer cannot be used without specifying project name
### Expected and Results

If my project shades Apache 2.0 dependencies and is under a different license, I want to merge NOTICE files without including my project in the final NOTICE.

```kts
tasks.named<ShadowJar>("shadowJar") {
    transform(ApacheNoticeResourceTransformer::class.java) {
        addHeader.set(false)
    }
}
```
This configuration fails with the following error:
```
java.lang.IllegalStateException: The value for property 'copyright' is final and cannot be changed any further.
```

### Related environment and versions

_No response_

### Reproduction steps

Here is an example test case you can add to [TransformersTest.kt](https://github.com/GradleUp/shadow/blob/700472a1803be43659a7538de1693fb685b7dcfa/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt) to reproduce this issue.

```kts
@Test
fun apacheNoticeTransformerWithRealDependencies() {
  projectScript.appendText(
    """
    dependencies {
      implementation 'org.apache.commons:commons-dbcp2:2.13.0'
      implementation 'org.apache.commons:commons-pool2:2.12.0'
    }
    $shadowJarTask {
      transform(${ApacheNoticeResourceTransformer::class.java.name}) {
        addHeader = false
      }
    }
  """.trimIndent(),
  )

  run(shadowJarPath) // This will fail
}
```

### Anything else?

The same configuration will not fail using the [maven-shade-plugin](https://github.com/apache/maven-shade-plugin).

For example:
```xml
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-dbcp2</artifactId>
    <version>2.13.0</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.12.0</version>
</dependency>
...
<transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
    <addHeader>false</addHeader>
</transformer>
```

Pull request description:
Closes #1626.

---

- [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/docs/changes/README.md b/docs/changes/README.md
index 216e570e6..bd72e42be 100644
--- a/docs/changes/README.md
+++ b/docs/changes/README.md
@@ -6,6 +6,7 @@
 ### Fixed
 
 - Fix missing space in `ApacheNoticeResourceTransformer` preamble causing malformed NOTICE header. ([#1623](https://github.com/GradleUp/shadow/pull/1623)).
+- Fix using `ApacheNoticeResourceTransformer` without `projectName`. ([#1627](https://github.com/GradleUp/shadow/pull/1627)).
 
 ## [9.0.1](https://github.com/GradleUp/shadow/releases/tag/9.0.1) - 2025-08-09
 
diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt
index 44a92f1eb..402d88135 100644
--- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt
+++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/ApacheNoticeResourceTransformer.kt
@@ -31,6 +31,11 @@ public open class ApacheNoticeResourceTransformer @Inject constructor(
   private val organizationEntries = mutableMapOf<String, MutableSet<String>>()
   private inline val charset get() = Charset.forName(charsetName.get())
 
+  /**
+   * Fallback [copyright] as the [Property] value can't be changed in execution phase.
+   */
+  private var fallbackCopyright: String? = null
+
   @get:Input
   public open val projectName: Property<String> = objectFactory.property("")
 
@@ -129,7 +134,7 @@ public open class ApacheNoticeResourceTransformer @Inject constructor(
         } else {
           val entry = sb.toString()
           if (entry.startsWith(projectName) && entry.contains("Copyright ")) {
-            copyright.set(entry)
+            fallbackCopyright = entry
           }
           if (currentOrg == null) {
             entries.add(entry)
@@ -156,7 +161,7 @@ public open class ApacheNoticeResourceTransformer @Inject constructor(
   override fun hasTransformedResource(): Boolean = true
 
   override fun modifyOutputStream(os: ZipOutputStream, preserveFileTimestamps: Boolean) {
-    val copyright = copyright.orNull
+    val copyright = copyright.orNull ?: fallbackCopyright
 
     os.putNextEntry(zipEntry(NOTICE_PATH, preserveFileTimestamps))
 


Test patch:
diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt
index b79aef879..2c3f4013e 100644
--- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt
+++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/TransformersTest.kt
@@ -211,6 +211,65 @@ class TransformersTest : BaseTransformerTest() {
     }
   }
 
+  @Issue(
+    "https://github.com/GradleUp/shadow/issues/1626",
+  )
+  @Test
+  fun useApacheNoticeTransformerWithoutProjectName() {
+    val noticeEntry = "META-INF/NOTICE"
+    val one = buildJarOne {
+      insert(
+        noticeEntry,
+        """
+        Apache Commons DBCP
+        Copyright 2001-2024 The Apache Software Foundation
+
+        This product includes software developed at
+        The Apache Software Foundation (https://www.apache.org/).
+        """.trimIndent(),
+      )
+    }
+    val two = buildJarTwo {
+      insert(
+        noticeEntry,
+        """
+        Apache Commons Pool
+        Copyright 2001-2025 The Apache Software Foundation
+
+        This product includes software developed at
+        The Apache Software Foundation (https://www.apache.org/).
+        """.trimIndent(),
+      )
+    }
+    projectScript.appendText(
+      transform<ApacheNoticeResourceTransformer>(
+        dependenciesBlock = implementationFiles(one, two),
+        transformerBlock = "addHeader = false",
+      ),
+    )
+
+    run(shadowJarPath)
+
+    assertThat(outputShadowedJar).useAll {
+      containsOnly(
+        noticeEntry,
+        *manifestEntries,
+      )
+      getContent(noticeEntry).transform { it.trim() }.isEqualTo(
+        """
+        Apache Commons Pool
+        Copyright 2001-2025 The Apache Software Foundation
+
+        This product includes software developed at
+        The Apache Software Foundation (https://www.apache.org/).
+
+        Apache Commons DBCP
+        Copyright 2001-2024 The Apache Software Foundation
+        """.trimIndent(),
+      )
+    }
+  }
+
   @ParameterizedTest
   @MethodSource("transformerConfigProvider")
   fun otherTransformers(pair: Pair<String, KClass<*>>) {
@@ -268,7 +327,6 @@ class TransformersTest : BaseTransformerTest() {
     @JvmStatic
     fun transformerConfigProvider() = listOf(
       "" to ApacheLicenseResourceTransformer::class,
-      "" to ApacheNoticeResourceTransformer::class,
       "" to ComponentsXmlResourceTransformer::class,
       "" to ManifestAppenderTransformer::class,
       "" to ManifestResourceTransformer::class,