jamesfredley opened a new pull request, #15557: URL: https://github.com/apache/grails-core/pull/15557
## Canary build: how far are we from Groovy 5? This is a **canary branch** built to answer one question: how far are we from being able to ship Grails 8 on Groovy 5 + Spring Boot 4 together? It combines three in-flight workstreams on top of \`spring-boot-4\` and patches the gaps that show up when you try to actually compile and test the result as one unit. **Draft / do-not-merge-as-is.** Individual commits, groups of commits, or the whole stack can be cherry-picked into \`groovy-5\` (or elsewhere) if any of them are useful. Nothing here is meant to land on \`8.0.x\` as a single PR. ## What went in and where it came from On top of \`origin/spring-boot-4\` (Spring Boot 4.0.5 + Spring Framework 7 + Hibernate 5.6-jakarta), the branch adds 4 commits: | Commit | Source | What it does | |---|---|---| | \`4fe600e\` | **merge** \`origin/groovy-5\` | Brings in matrei's Groovy 5 work (Groovy 5.0.5, Spock 2.4-groovy-5.0, groovysh API migration, various Groovy 5 compat fixes). Conflict resolution: keep Groovy 5.0.5 GA and Jackson 2.21.2. | | \`4a27159\` | **cherry-pick** of \`9574fe8\` | Picks up the 66-file Groovy 5 compatibility commit from jamesfredley's earlier branch. Conflict resolution: keep Spring Boot 4 branch's \`LoggingTransformer\` (manual log field injection is more robust than relying on \`LogASTTransformation.visit()\` in Groovy 5); drop direct Spock feature-method invocation in \`TransactionalTransformSpec\` (not allowed in Spock 2.x/Groovy 5); drop \`spock-core transitive=false\` + explicit byte-buddy dep (handled project-wide, see below). | | \`5fc3bda\` | **new** | Architectural hardening of the cherry-picked workarounds, per an Oracle-style review: \`Validateable\` and \`BeanPropertyAccessorFactory\` now use Java reflection with tightened error handling to dispatch to the implementing class's \`defaultNullable()\`; \`BoundPromise\` stores \`Object\` internally (matching \`SynchronousPromise\`) to remove an unsound \`(T) e\` cast. | | \`bfe1492\` | **new** | Pins Groovy to 5.0.3 and resolves everything downstream. This is the commit that makes the full tree actually compile. See 'Outstanding gaps' below. | ## Why Groovy 5.0.3 and not 5.0.5 Groovy 5.0.4 bumped bundled ASM to 9.9.1 ([GROOVY-11821](https://issues.apache.org/jira/browse/GROOVY-11821)). That ASM version throws \`java.lang.AssertionError\` in \`groovyjarjarasm.asm.Frame.putAbstractType\` when compiling the grails-geb testFixtures source set. Reproduced on 5.0.4 and 5.0.5; not present on 5.0.3. Bisected and confirmed by decompiling the generated bytecode. The grails-geb testFixtures matter for CI because \`ContainerGebSpec\` is referenced by ~26 downstream \`grails-test-examples-*\` integration-test modules; if it can't compile, the whole build blocks. **Tradeoff of pinning 5.0.3**: \`org.apache.groovy.groovysh.Main.start(Map)\` was added in 5.0.4 ([GROOVY-11839](https://issues.apache.org/jira/browse/GROOVY-11839)) and is used by grails-console. Mitigation: introduced \`GroovyshStarter\`, a reflective invoker, so the call compiles on 5.0.3 and still works on any user-provided 5.0.4+ runtime. ## Outstanding workarounds (what is still a patch, not a fix) | Workaround | Location | Upstream bug | |---|---|---| | Pin Groovy to 5.0.3 | \`dependencies.gradle\` | Groovy/ASM 9.9.1 frame bug, should be reported upstream | | Reflective \`Main.start(Map)\` call | \`grails-console/.../GroovyshStarter.groovy\` | Workaround for the 5.0.3 pin, not a Groovy bug | | \`BoundPromise.then()\` reads value into a local \`Object\` before \`instanceof\` | \`grails-async/core/.../BoundPromise.groovy\` | Groovy 5.0.3 @CompileStatic+invokedynamic emits spurious \`cast:(Object) -> Throwable\` before \`instanceof Throwable\`. Bytecode confirmed. | | Manual log field injection instead of \`LogASTTransformation.visit()\` | \`grails-logging/.../LoggingTransformer.java\` | Groovy 5 \`VariableScopeVisitor\` NPEs when the transform is invoked programmatically with a null \`SourceUnit\` | | \`metaClass.invokeStaticMethod\` replaced with Java reflection for \`defaultNullable()\` | \`Validateable\`, \`BeanPropertyAccessorFactory\` | Groovy 5 \`TraitReceiverTransformer\` routes \`this.defaultNullable()\` from a static trait method to the trait helper, losing the implementing-class override | | \`ConfigObject\` conversion helpers before feeding config to Groovy trees | \`NavigableMap\`, \`GroovyConfigPropertySourceLoader\` | Groovy 5 \`ConfigObject\` recursion changes (pre-existing on groovy-5) | | Try/catch guards around \`VariableScopeVisitor\` | \`AstUtils\`, \`AbstractMethodDecoratingTransformation\` | Groovy 5 VariableScopeVisitor NPE on certain AST shapes (pre-existing on groovy-5) | | \`byte-buddy\` + \`objenesis\` as global testRuntimeOnly | \`gradle/test-config.gradle\` | Spock 2.4 needs both to mock concrete classes (e.g. \`PrintStream\` in \`GrailsConsoleSpec\`); neither is transitive from \`spock-core\` in this config | | Workaround in \`TransactionalTransformSpec\` | \`grails-datamapping-core/src/test/.../TransactionalTransformSpec.groovy\` | Spock 2.x/Groovy 5 doesn't allow invoking feature methods outside test context; test now only verifies method signatures exist | | \`PropertyResolver.getProperty(String, Class)\` overload in test | \`DatastoreUtilsSpec\` | Groovy 5 routes \`resolver.getProperty('grails.foo')\` through GroovyObject metaclass, not Spring; typed overload disambiguates | ## Breaking changes / behavior changes in this branch 1. **Groovy pinned to 5.0.3** (was 5.0.5 on \`groovy-5\`). Any feature added in 5.0.4+ (e.g. \`Main.start(Map)\`, GROOVY-11815 STC flow typing fixes, GROOVY-11817 indy regressions) is not available at compile time. 2. **\`BoundPromise<T>\` field type** changed from \`T value\` to \`Object value\`. Public API (\`BoundPromise(T value)\`, \`get()\`, \`accept(T)\`) unchanged. An overloaded \`BoundPromise(Throwable error)\` constructor was added for the error path; existing call sites that passed a \`Throwable\` via the raw \`BoundPromise\` ctor now bind to the more specific overload. 3. **\`Validateable.resolveDefaultNullable\` added as a private helper.** Not public API, but visible to subclasses of the trait in reflection. 4. **Global testRuntimeOnly deps added**: \`net.bytebuddy:byte-buddy\`, \`org.objenesis:objenesis\`. These are runtime-only and only affect test classpaths. 5. **\`LoggingTransformer\` no longer runs \`LogASTTransformation\`.** It still produces an equivalent \`log\` field via direct AST injection. 6. **\`GroovyshApplicationContext\` / \`GroovyshWebApplicationContext\`** now delegate to \`GroovyshStarter\`. Behavior change: if the runtime classpath has only Groovy 5.0.3, \`startConsole()\` throws \`IllegalStateException\` with a clear message instead of silently failing; on 5.0.4+ it behaves identically to before. ## Build status | Project | Command | Result | |---|---|---| | grails-core (compile + test-compile) | \`./gradlew classes testClasses -PskipTests\` | **575 tasks PASS** | | grails-gradle | \`./gradlew build -PskipCodeStyle\` | **PASS** | | grails-forge | \`./gradlew build -PskipTests -PgrailsIndy=false\` | **PASS** (spotless style excluded) | | grails-core core unit tests | \`./gradlew :grails-async-core:test :grails-bootstrap:test :grails-datastore-core:test :grails-validation:test :grails-core:test :grails-fields:test\` | **PASS** | | grails-geb testFixtures | \`./gradlew :grails-geb:compileTestFixturesGroovy\` | **PASS** (was failing on \`groovy-5\` with ASM bug) | For context, the upstream \`groovy-5\` branch's CI is currently all-red on \`Build Grails-Core\`, \`Build Grails Forge\`, and every functional-test job. Tests that still fail on this canary are a subset of what already fails on \`groovy-5\`, plus the known mergeTestReports cascade when \`-PonlyCoreTests\` skips integrationTest (pre-existing plugin behavior). ## Still-broken on this branch (and on groovy-5) - **\`grails-views-gson:test\`**: \`ClassCastException\` between \`groovy.json.StreamingJsonBuilder\$StreamingJsonDelegate\` and \`grails.plugin.json.builder.StreamingJsonBuilder\$StreamingJsonDelegate\` during HAL/Template rendering. Needs \`cloneDelegateAndGetContent\` to be overridden in Grails' \`StreamingJsonBuilder.java\`. Out of scope for a canary. - **\`grails-test-examples-*:mergeTestReports\`** (14 modules): The \`TestReport\` task in \`TestPhasesGradlePlugin\` fails when \`integrationTest/binary\` doesn't exist, which happens whenever \`-PonlyCoreTests\` is set. Pre-existing plugin behavior. - **\`grails-forge\` spotless checks**: Line-ending violations; unrelated to Groovy 5. ## How to use this - If the whole thing is useful: cherry-pick the 4 commits onto \`groovy-5\`. - If only some pieces are useful: individual commits are reasonably self-contained. \`5fc3bda\` and \`bfe1492\` can be split further by subsystem (the commit messages call out each change site). - If you want to see the bytecode-level ASM bug, checkout this branch, bump \`groovy.version\` to 5.0.4 in \`dependencies.gradle\`, and run \`./gradlew :grails-geb:compileTestFixturesGroovy\`. It will fail with the AssertionError shown above. Assisted-by: Claude Code <[email protected]> -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
