diff --git a/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovyLibraryExtension.groovy b/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovyLibraryExtension.groovy index e5eea8d1a5a..3a8d2322f13 100644 --- a/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovyLibraryExtension.groovy +++ b/build-logic/src/main/groovy/org/apache/groovy/gradle/GroovyLibraryExtension.groovy @@ -106,7 +106,8 @@ class GroovyLibraryExtension { Map mappingPatterns, Map> libraryExcludes, List allExcludes, - Map resources + Map resources, + Map> relocationExcludes = [:] ) { grooid.set(true) def grooidJar = tasks.register("grooidJar", RepackageJarTask) { @@ -125,6 +126,7 @@ class GroovyLibraryExtension { }.files } it.patterns = mappingPatterns + it.relocationExcludes = relocationExcludes it.excludesPerLibrary = libraryExcludes it.sourceExcludes = allExcludes it.generateOsgiManifest = false diff --git a/build-logic/src/main/groovy/org/apache/groovy/gradle/RepackageJarTask.groovy b/build-logic/src/main/groovy/org/apache/groovy/gradle/RepackageJarTask.groovy index e91a1f56537..169eee0010b 100644 --- a/build-logic/src/main/groovy/org/apache/groovy/gradle/RepackageJarTask.groovy +++ b/build-logic/src/main/groovy/org/apache/groovy/gradle/RepackageJarTask.groovy @@ -89,6 +89,23 @@ abstract class RepackageJarTask extends ShadowJar { @Input Map patterns = [:] + /** + * Classes left untouched by a relocation pattern, keyed by the pattern + * ({@code 'java.beans.**': ['java.beans.Transient']}): a type the JDK has but + * the repackaged replacement library does not, so rewriting the reference + * would point at a class that never exists (GROOVY-12391). + *

+ * Values are passed to Shadow's relocator exclusion as given. Shadow accepts + * the dotted class name used here as well as the slashed internal form and + * its {@code *} / {@code **} patterns, normalising all of them before + * matching. An exclusion that did not apply would not fail silently: the + * root build's {@code grooidJar} task checks the produced jar still + * references {@code java.beans.Transient}. + */ + @Input + @Optional + Map> relocationExcludes = [:] + @Input @Optional Map> excludesPerLibrary = [:] @@ -223,10 +240,11 @@ abstract class RepackageJarTask extends ShadowJar { // rewritten to groovyjarjaropenbeans the lookup no-ops. Type/descriptor // remapping is unaffected (skipStringConstants only gates LDC strings). // See GROOVY-12199 / Bucket 4a. - if (shouldSkipStringConstants(prefix)) { - relocate(prefix, destination) { it.skipStringConstants = true } - } else { - relocate(prefix, destination) + List untouched = relocationExcludes[pattern] ?: [] + boolean skipStrings = shouldSkipStringConstants(prefix) + relocate(prefix, destination) { relocator -> + if (skipStrings) relocator.skipStringConstants = true + untouched.each { relocator.exclude(it) } } } diff --git a/build.gradle b/build.gradle index 1448627bd1e..afcd4be07a2 100644 --- a/build.gradle +++ b/build.gradle @@ -56,10 +56,33 @@ groovyLibrary { ['META-INF/NOTICE', 'META-INF/INDEX.LIST'], [ (project.relativePath(rootProject.file('notices/NOTICE-GROOIDJARJAR'))): 'META-INF/NOTICE' + ], + [ + // openbeans predates java.beans.Transient (Java 7); rewriting the annotation + // would point at a class that does not exist and break the Verifier (GROOVY-12391) + 'java.beans.**': ['java.beans.Transient'] ] ) } +// GROOVY-12391: the exclusion above must hold in the produced jar, or the grooid jar +// cannot compile anything (the Verifier resolves the annotation class when it loads) +tasks.named('grooidJar') { + doLast { + def jar = new java.util.zip.ZipFile(archiveFile.get().asFile) + try { + ['groovy/lang/GroovyObjectSupport.class', 'org/codehaus/groovy/classgen/Verifier.class'].each { entry -> + def bytes = new String(jar.getInputStream(jar.getEntry(entry)).bytes, 'ISO-8859-1') + if (bytes.contains('groovyjarjaropenbeans/Transient') || !bytes.contains('java/beans/Transient')) { + throw new GradleException("$entry in the grooid jar must keep referencing java.beans.Transient") + } + } + } finally { + jar.close() + } + } +} + groovyCore { def supportClasses = ['Boolean','Char','Byte','Short','Int','Long','Float','Double'].collect { 'org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport$'+it+'ArrayStaticTypesHelper'