Repository: GradleUp/shadow (kotlin)

Issue:
Relocated Groovy extensions are not working
`import groovy.text.SimpleTemplateEngine

class SimpleTemplateEngineDateUtil {

    public static void main(args) {
        SimpleTemplateEngineTest()
    }

    static void SimpleTemplateEngineTest() {
        println new SimpleTemplateEngine(true).createTemplate('<%df = "dd.MM.yyyy" \ntf = "dd.MM.yyyy HH:mm:ss" %><% out << "Date:${new Date().format(tf)}"%>') .make([:]).toString()
    }

}`

run as shadow jar return this error:

`Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: java.util.Date.format() is applicable for argument types: (String) values: [dd.MM.yyyy HH:mm:ss]
Possible solutions: from(java.time.Instant), getAt(java.lang.String), parse(java.lang.String), print(java.lang.Object), print(java.io.PrintWriter)
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:70)
	at org.codehaus.groovy.runtime.callsite.PojoMetaClassSite.call(PojoMetaClassSite.java:46)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:127)
	at SimpleTemplateScript1.run(SimpleTemplateScript1.groovy:3)
	at groovy.text.SimpleTemplateEngine$SimpleTemplate$1.writeTo(SimpleTemplateEngine.java:176)
	at groovy.text.SimpleTemplateEngine$SimpleTemplate$1.toString(SimpleTemplateEngine.java:188)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.codehaus.groovy.runtime.callsite.PlainObjectMetaMethodSite.doInvoke(PlainObjectMetaMethodSite.java:43)
	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:211)
	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:119)
	at SimpleTemplateEngineDateUtil.SimpleTemplateEngineTest(SimpleTemplateEngineDateUtil.groovy:10)
	at SimpleTemplateEngineDateUtil.main(SimpleTemplateEngineDateUtil.groovy:6)`

Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: java.util.Date.format() is applicable for argument types: (String) values: [dd.MM.yyyy HH:mm:ss]
Possible solutions: from(java.time.Instant), getAt(java.lang.String), parse(java.lang.String), print(java.lang.Object), print(java.io.PrintWriter)
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:70)
	at org.codehaus.groovy.runtime.callsite.PojoMetaClassSite.call(PojoMetaClassSite.java:46)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:127)
	at SimpleTemplateScript1.run(SimpleTemplateScript1.groovy:3)
	at groovy.text.SimpleTemplateEngine$SimpleTemplate$1.writeTo(SimpleTemplateEngine.java:176)
	at groovy.text.SimpleTemplateEngine$SimpleTemplate$1.toString(SimpleTemplateEngine.java:188)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.codehaus.groovy.runtime.callsite.PlainObjectMetaMethodSite.doInvoke(PlainObjectMetaMethodSite.java:43)
	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:211)
	at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:119)
	at SimpleTemplateEngineDateUtil.SimpleTemplateEngineTest(SimpleTemplateEngineDateUtil.groovy:10)
	at SimpleTemplateEngineDateUtil.main(SimpleTemplateEngineDateUtil.groovy:6)`

org.apache.groovy.dateutil.extensions (groovy-dateutil and groovytime)is part of the jar Archives

The execution without shadowJar is error-free.

Pull request description:
Closes #467.

---

- [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 8c9389a31..2a417fbae 100644
--- a/docs/changes/README.md
+++ b/docs/changes/README.md
@@ -5,6 +5,7 @@
 
 ### Added
 
+- Support relocating Groovy extensions in Module descriptors. ([#1705](https://github.com/GradleUp/shadow/pull/1705))
 - Add extensions for `Iterable<Relocator>`. ([#1710](https://github.com/GradleUp/shadow/pull/1710))
 
 ### Changed
diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformer.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformer.kt
index 064b9b3dc..ea7ef9b6c 100644
--- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformer.kt
+++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformer.kt
@@ -2,6 +2,7 @@ package com.github.jengelman.gradle.plugins.shadow.transformers
 
 import com.github.jengelman.gradle.plugins.shadow.internal.inputStream
 import com.github.jengelman.gradle.plugins.shadow.internal.zipEntry
+import com.github.jengelman.gradle.plugins.shadow.relocation.relocateClass
 import java.util.Properties
 import org.apache.tools.zip.ZipOutputStream
 import org.gradle.api.file.FileTreeElement
@@ -45,8 +46,13 @@ public open class GroovyExtensionModuleTransformer : ResourceTransformer {
         }
         KEY_EXTENSION_CLASSES,
         KEY_STATIC_EXTENSION_CLASSES,
-        -> handle(key, value as String) { existingValue ->
-          module.setProperty(key, "$existingValue,$value")
+        -> {
+          val relocatedClasses = (value as String).split(',').joinToString(",") { className ->
+            context.relocators.relocateClass(className)
+          }
+          handle(key, relocatedClasses) { existingValue ->
+            module.setProperty(key, "$existingValue,$relocatedClasses")
+          }
         }
       }
     }


Test patch:
diff --git a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt
index 898b8df69..2abdc7d52 100644
--- a/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt
+++ b/src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/transformers/GroovyExtensionModuleTransformerTest.kt
@@ -10,9 +10,11 @@ import com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionMo
 import com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionModuleTransformer.Companion.MERGED_MODULE_VERSION
 import com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionModuleTransformer.Companion.PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR
 import com.github.jengelman.gradle.plugins.shadow.transformers.GroovyExtensionModuleTransformer.Companion.PATH_LEGACY_GROOVY_EXTENSION_MODULE_DESCRIPTOR
+import com.github.jengelman.gradle.plugins.shadow.util.JarPath
 import com.github.jengelman.gradle.plugins.shadow.util.getContent
 import java.nio.file.Path
 import kotlin.io.path.appendText
+import org.junit.jupiter.api.Test
 import org.junit.jupiter.params.ParameterizedTest
 import org.junit.jupiter.params.provider.Arguments
 import org.junit.jupiter.params.provider.MethodSource
@@ -63,6 +65,35 @@ class GroovyExtensionModuleTransformerTest : BaseTransformerTest() {
     commonAssertions()
   }
 
+  @Test
+  fun groovyExtensionModuleTransformerWithRelocation() {
+    projectScript.appendText(
+      """
+        dependencies {
+          ${implementationFiles(buildJarFoo(), buildJarBar())}
+        }
+        $shadowJarTask {
+          relocate('com.acme', 'com.example.shaded.acme')
+          mergeGroovyExtensionModules()
+        }
+      """.trimIndent(),
+    )
+
+    run(shadowJarPath)
+
+    val properties = outputShadowedJar.extensionModuleProperties
+
+    assertThat(properties.getProperty(KEY_MODULE_NAME)).isEqualTo(MERGED_MODULE_NAME)
+    assertThat(properties.getProperty(KEY_MODULE_VERSION)).isEqualTo(MERGED_MODULE_VERSION)
+    assertThat(properties.getProperty(KEY_EXTENSION_CLASSES))
+      .isEqualTo(
+        "com.example.shaded.acme.foo.FooExtension,com.example.shaded.acme.foo.BarExtension," +
+          "com.example.shaded.acme.bar.SomeExtension,com.example.shaded.acme.bar.AnotherExtension",
+      )
+    assertThat(properties.getProperty(KEY_STATIC_EXTENSION_CLASSES))
+      .isEqualTo("com.example.shaded.acme.foo.FooStaticExtension,com.example.shaded.acme.bar.SomeStaticExtension")
+  }
+
   private fun buildJarFoo(
     entry: String = PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR,
   ): Path = buildJar("foo.jar") {
@@ -92,7 +123,7 @@ class GroovyExtensionModuleTransformerTest : BaseTransformerTest() {
   }
 
   private fun commonAssertions() {
-    val properties = outputShadowedJar.use { it.getContent(PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR) }.toProperties()
+    val properties = outputShadowedJar.extensionModuleProperties
 
     assertThat(properties.getProperty(KEY_MODULE_NAME)).isEqualTo(MERGED_MODULE_NAME)
     assertThat(properties.getProperty(KEY_MODULE_VERSION)).isEqualTo(MERGED_MODULE_VERSION)
@@ -108,6 +139,10 @@ class GroovyExtensionModuleTransformerTest : BaseTransformerTest() {
     const val STATIC_EXTENSION_CLASSES_FOO = "com.acme.foo.FooStaticExtension"
     const val STATIC_EXTENSION_CLASSES_BAR = "com.acme.bar.SomeStaticExtension"
 
+    val JarPath.extensionModuleProperties get() = use {
+      it.getContent(PATH_GROOVY_EXTENSION_MODULE_DESCRIPTOR).toProperties()
+    }
+
     @JvmStatic
     fun resourcePathProvider() = listOf(
       Arguments.of(PATH_LEGACY_GROOVY_EXTENSION_MODULE_DESCRIPTOR, PATH_LEGACY_GROOVY_EXTENSION_MODULE_DESCRIPTOR),