maczikasz opened a new issue, #16131: URL: https://github.com/apache/grails-core/issues/16131
## Summary `GroovyPageCompiler` bakes the `.gsp` source file's modification time into every generated page class as a `static final long LAST_MODIFIED` constant. Because a fresh checkout gives each `.gsp` a new mtime, identical sources compiled on two machines produce different bytes. Any jar bundling precompiled GSPs therefore changes on every checkout, and Gradle's build cache cannot reuse anything downstream of it. The effect is invisible in the sense that matters: builds succeed and output is correct. They are just slower, on every fresh clone and every CI run. ## Why it is not hidden by classpath normalization Gradle's `COMPILE_CLASSPATH` normalization hashes only the ABI and ignores everything else. A `static final` constant is part of the ABI — constants are inlined into callers — so consumers re-key even under that mode. Archive-level reproducibility does not help either: the jars already use normalized entry timestamps (`1980-02-01`), and the divergence is inside the class bytes. ## Evidence Two CI builds of this project at the same commit (`01037bdf`), on the ASF Develocity instance: https://develocity.apache.org/c/kat5373gl55fw/lenhcqeavcokm/task-inputs For `:grails-fields:jar` and `:grails-spring-security:jar`, `compileGroovy`, `compileJava` and `processResources` are identical. The only diverging input is `build/gsp-classes/main`. Reproduced locally by changing only the source mtimes, with file content unchanged: | | mtimes | resulting class bytes | | --- | --- | --- | | before | 3 distinct | 3 distinct sets | | after a fixed `LAST_MODIFIED` | 3 distinct | byte-identical | A sibling closure class produced by the same task in the same run (`gsp_..._table_gsp$_run_closure1.class`), which carries no `LAST_MODIFIED` constant, is byte-identical in every run. That isolates the timestamp as the sole cause rather than general compiler nondeterminism. ## Measured cost Over 2026-07-29 → 2026-08-05 on `develocity.apache.org`, roughly 616 hours of avoidable CI task re-execution across this project trace to jars carrying precompiled GSPs, the largest single contributor being `:grails-test-examples-spring-security-ui-simple:assetCompile`. These are summed task re-executions rather than wall-clock, since much of the work runs in parallel. ## Note on the reload path `LAST_MODIFIED` is not unused. `GroovyPageMetaInfo.checkIfReloadableResourceHasChanged` reads the field directly and compares it against the live source timestamp to decide whether a precompiled page is stale, so any fix that fixes the value must also handle the "no timestamp recorded" case — otherwise every check reports a change. For GSPs in binary plugin jars the reload path is already unreachable (`DefaultGroovyPageLocator.resolveViewInBinaryPlugin` nulls the resource callable, and the jars ship no `.gsp` sources), but an application's own precompiled pages with reloading enabled would be affected. ## Environment - Branch: reproduced against `8.0.x`; the relevant sources are byte-identical on `7.0.x` - `GroovyPageCompiler.groovy:222`, `GroovyPageParser.java:905`, `GroovyPageMetaInfo.java:480` ## Attribution This was found while trialling Gradle's Build Caching Optimizer (https://develocity.ai/product/build-caching-optimizer/), an agent that locates avoidable build-cache misses from Develocity data. Generative tooling was used in preparing this report and the accompanying analysis; the findings were verified against the sources and build scans linked above. -- 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]
