jamesfredley commented on PR #15557:
URL: https://github.com/apache/grails-core/pull/15557#issuecomment-4316529796

   ## Recent changes (this push)
   
   Four commits pushed on top of `8c5d1d182d` to get the PR's remaining CI 
failures unblocked. In diagnostic order:
   
   ### 1. `fe05ce8f3b` - CI workflow: unbrittle `groovy-joint-workflow`
   
   The `build_groovy` job was failing in 30s because the "Comment `include 
'groovy-all'`" step used a hardcoded `sed -i '72c\\ ... '` against Apache 
Groovy's `settings.gradle`. That line number was correct for `GROOVY_4_0_X` at 
the time of authoring but is wrong for `GROOVY_5_0_X` (the line moved). The sed 
command then silently rewrote the wrong line, breaking the downstream 
`./gradlew install` of Groovy itself, and everything else in that workflow 
failed as a consequence.
   
   Rewritten as a pattern-based `awk` transform that:
   
   - Finds any line containing `include 'groovy-all'` (quoted or unquoted, with 
or without leading whitespace) and comments it out in place.
   - Guards with an explicit check: if the pattern is never matched, the step 
fails loudly so that a future rename in Apache Groovy does not silently no-op 
the joint build again.
   - Was verified locally against both the `GROOVY_4_0_X` and `GROOVY_5_0_X` 
checkouts of `apache/groovy` to confirm the replacement is idempotent and 
correctly targets the one line.
   
   ### 2. `ba6c488b92` - build: bump Apache Groovy to `5.0.6-SNAPSHOT`
   
   Moved `'groovy.version'` in `dependencies.gradle` from `5.0.5` to 
`5.0.6-SNAPSHOT` so the PR can pull in three upstream fixes that landed on 
`GROOVY_5_0_X` after the 5.0.5 tag:
   
   - **GROOVY-11907** (`7bd24933c1`, "trait field reference transform 
restructure") - the ticket was opened from this migration. Its fix reworks how 
trait field-reference helpers generate getter/setter bytecode; eventually 
should let us revert the PR's `CommandLineHelper` static-fields workaround 
(commit `d2441fbcb5`).
   - **GROOVY-11899** (`e5647650c5`, "Static methods in Traits do not compile 
in Java/Groovy joint compilation") - relevant to Grails' joint stub generation 
path.
   - **GROOVY-11930** (`1497cf5adf`, "unwrap null into argument array for 
reference type") - fixes `ClassCastException` in indy dispatch when 
named-argument maps carry null values.
   
   The Apache snapshots repo and the `org.apache.groovy.*-SNAPSHOT` content 
filter were already wired in root `settings.gradle` (line 504), 
`grails-forge/settings.gradle`, and in every Forge-generated app via 
`GradleRepository.getDefaultRepositories("*-SNAPSHOT")`, so the snapshot 
resolves with no further build-script changes. Confirmed by running the failing 
Forge test against 5.0.6-SNAPSHOT and observing the banner print `Groovy: 
5.0.6-SNAPSHOT`.
   
   **Caveat**: the snapshot bump alone did not fix the scaffolding test. The 
actual fix is in commit 3 below. The bump is kept because its upstream fixes 
are directly relevant to this migration and let us reduce the number of 
downstream workarounds over time; it can be reverted to `5.0.5` independently 
if reviewers prefer to stay on a tagged release.
   
   ### 3. `325e2fee08` - fix: replace File truthy-check with explicit null 
checks in `TemplateRenderer`
   
   **Root cause of the scaffolding silent no-op** (`ScaffoldingSpec.test 
generate-controller command`). Three prior attempts mis-diagnosed this as a 
`@Delegate` / trait dispatch regression. The actual bug:
   
   `TemplateRendererImpl.render(Map)` and `render(Resource, File, Map, 
boolean)` both guarded with `if (template && destination) { ... }`. Groovy 
applies `DefaultGroovyMethods.asBoolean(File)` to that coercion, which is:
   
   ```java
   public static boolean asBoolean(File file) {
       return file.exists() && (file.isDirectory() || file.length() > 0);
   }
   ```
   
   For a newly-generated scaffolding file whose destination does not yet exist, 
`asBoolean(File)` returns `false`. The guard evaluates `true && false = false`, 
the method silently returns, and the command exits 0 with no files written and 
nothing on `System.err` - which is exactly what CI was reporting.
   
   Why this only surfaces on Groovy 5: Groovy 4's `@CompileStatic` compiled `if 
(destination)` using a direct reference-null check at bytecode level, so 
`File.asBoolean` was never invoked. Groovy 5's `@CompileStatic` invokes the 
full DGM dispatch, which correctly honours `asBoolean(File)`. The bug has 
therefore been latent in this code since Groovy added the `File.asBoolean` DGM, 
but only surfaced now.
   
   **The fix**:
   
   - `TemplateRendererImpl.render(Map<String, Object>)`: use 
`Map.get('template')` / `Map.get('destination')` instead of dynamic property 
access (the dynamic-property path also reached `!File.asBoolean` through the 
`!map?.destination` coercion); throw `IllegalArgumentException` with a clear 
message when the map is non-null but carries a null `template` or `destination` 
entry (was: silent return); preserve `Resource` and `File` inputs instead of 
re-routing them back through `template(Object)` / `file(Object)`, which under 
Groovy 5 `@CompileStatic` dispatch we now know we cannot rely on to be a no-op 
for already-normalised inputs.
   - `TemplateRendererImpl.render(Resource, File, Map, boolean)`: replace `if 
(template && destination) { ... }` with explicit `if (template == null)` and 
`if (destination == null)` checks; throw `TemplateException` / 
`IllegalArgumentException` on null inputs (was: silent no-op); flatten the 
nested else chain with early returns.
   - `TemplateRendererImpl.render(CharSequence, File, Map, boolean)` and 
`render(File, File, Map, boolean)`: same `template && destination` trap 
replaced with explicit null checks. These overloads preserve the old 
silent-return-on-null behaviour (they are less critical), but they no longer 
trip over `File.asBoolean`.
   - `GenerateControllerCommand`: extracted a private `generateFile(Resource, 
Model, String, String, boolean)` helper that calls the explicit 
`templateRenderer.render(Resource, File, Model, boolean)` overload directly and 
asserts non-null template/destination before dispatch. This is defence-in-depth 
- it bypasses the named-arg -> `render(Map)` -> `@Delegate`-bridge path 
entirely, so any future dispatch regression in that chain cannot silently break 
scaffolding again, and surfaces failures loudly instead of relying on 
`render(Map)`'s guards.
   
   **The original PR description's `@Delegate in traits silently return null` 
claim** (section 3 of the Groovy 5 workarounds) is *also* true - that is a 
separate Groovy 5 behaviour and converting `GrailsApplicationCommand` from 
trait to class was correct and still needed. But it was not the cause of the 
scaffolding test failure. That was the `File.asBoolean` trap on top of it.
   
   ### 4. `39b5e58de6` - style: single-quoted Strings for non-interpolated 
exceptions
   
   CodeNarc `UnnecessaryGString` violations introduced by the two 
non-interpolated exception messages added in commit 3 (lines 57 and 193 of 
`TemplateRendererImpl`). Switched those two literal Strings from `"..."` to 
`'...'`. Trivial follow-up.
   
   ## Verification
   
   Against Corretto 21.0.10 on Linux, with `core.autocrlf=false` (the local env 
here defaults to `true`, which creates false-positive Spotless and 
`./gradlew`-CRLF failures unrelated to this PR):
   
   | Build | Invocation | Result |
   |---|---|---|
   | `grails-gradle` | `./gradlew build --continue --stacktrace --rerun-tasks 
-PskipCodeStyle` | **SUCCESS** (~3m 35s, 41 tasks) |
   | Scaffolding test (the original failure) | `cd grails-forge && ./gradlew 
:test-core:test --tests 
'org.grails.forge.features.scaffolding.ScaffoldingSpec.test generate-controller 
command'` | **SUCCESS** (all 4 scaffolded files generated) |
   | `grails-core` | `./gradlew build :grails-shell-cli:installDist groovydoc 
--continue --stacktrace -PonlyCoreTests -PskipCodeStyle` (CI invocation) | 
Scaffolding path passes. Remaining local failures are pre-existing Spring Boot 
4 `pluginManagerPostProcessor` bean-wire issues in `grails-test-suite-uber` 
(`BeanCreationException: grails/transaction/TransactionManagerAware`); CI does 
not hit these. |
   | `grails-forge` | `./gradlew build --continue --rerun-tasks --stacktrace 
-PgrailsIndy=false -PskipCodeStyle` (CI invocation) | Scaffolding path passes. 
Remaining local failures are pre-existing `core.autocrlf=true` CRLF issues in 
Spotless and in the forge-cli tests' generated `./gradlew`; CI does not hit 
these. |
   
   The three Forge matrix jobs on CI (`Build Grails Forge Java 21 
indy=false/true, Java 25 indy=false`) and the `build_groovy` job are the four 
failures this push targets.
   


-- 
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]

Reply via email to