This is an automated email from the ASF dual-hosted git repository. paulk-asert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 9ab4e19a9494b31dcbb8dc28977bf7e1220210d9 Author: Paul King <[email protected]> AuthorDate: Sun May 17 10:04:45 2026 +1000 AI readiness: make it clearer around selecting java and groovy versions --- .agents/skills/groovy-build/SKILL.md | 44 ++++++++++++++++++++++++-- .agents/skills/groovy-internals/SKILL.md | 6 +++- .agents/skills/groovy-reproducer/SKILL.md | 51 +++++++++++++++++++++++++++++-- ARCHITECTURE.md | 16 ++++++++++ CONTRIBUTING.md | 47 ++++++++++++++++++++++++++-- README.adoc | 2 ++ 6 files changed, 159 insertions(+), 7 deletions(-) diff --git a/.agents/skills/groovy-build/SKILL.md b/.agents/skills/groovy-build/SKILL.md index 02f593fd8c..a28d9cf9a3 100644 --- a/.agents/skills/groovy-build/SKILL.md +++ b/.agents/skills/groovy-build/SKILL.md @@ -130,6 +130,11 @@ changes: compiles fine but blows up at runtime. After any repackaging change, run `./gradlew :groovy-binary:installGroovy` and exercise `groovy` / `groovyc` against a non-trivial script. + On a machine with SDKMAN, `unset GROOVY_HOME` first — else the + launcher silently runs a different Groovy (or fails with + `ClassNotFoundException: org.codehaus.groovy.tools.GroovyStarter`) + and the smoke test proves nothing. See + ["Running your local build"](../../../CONTRIBUTING.md#running-your-local-build). 5. **Editing generated ANTLR sources to "fix" a parser problem.** `build/generated/sources/antlr4/...` is regenerated on every @@ -163,6 +168,34 @@ changes: intent and is rejected. See the "what *not* to do" list in [`AGENTS.md`](../../../AGENTS.md). +11. **Downloading a JDK without asking.** `sdk install java + <ver>` (or toolchain auto-provisioning) pulls a large + artifact onto the user's machine. AI tooling reaches for it + to make a version-specific build or test "just work." Try + the JDK that launched Gradle first, then a locally-installed + candidate (`sdk list java`, `~/.sdkman/candidates/java/`); + only if none fits, surface that the JDK is missing and let + the contributor decide. The download is a side effect with + network, disk, and time cost — it is the contributor's call, + like the dev@ discussion in *Adding a new runtime dependency + without flagging dev@ discussion* above. + +12. **Leaving a machine-specific JDK override in a tracked + file.** To build or test against a specific JDK the + supported knob is the `-Ptarget.java.home=<java-home>` + Gradle property (command line, or `~/.gradle/gradle.properties`) + — see + ["Building and testing against a specific JDK"](../../../CONTRIBUTING.md#building-and-testing-against-a-specific-jdk) + and the three-knobs bullet in + [Build infrastructure](../../../ARCHITECTURE.md#build-infrastructure). + AI tooling instead edits `gradle.properties` or a + `build.gradle` toolchain block to force a JDK and forgets to + revert it, committing a machine-specific path or a changed + `targetJavaVersion` / `groovyTargetBytecodeVersion`. Those + properties are the single source of truth and move per Groovy + version; don't pin them to investigate. If a tracked file + must change to reproduce something, revert before hand-back. + ## Procedure 1. **Classify the scope.** Convention plugin (cross-cutting), @@ -203,7 +236,9 @@ changes: ./gradlew :groovy-binary:installGroovy ``` - See + `unset GROOVY_HOME` first on SDKMAN machines, or the launcher + exercises a different Groovy / fails with the `GroovyStarter` + `ClassNotFoundException`. See ["Running your local build"](../../../CONTRIBUTING.md#running-your-local-build). 7. **For API-affecting changes, run binary compatibility @@ -232,6 +267,10 @@ Before declaring the change ready: project-wide) — see [Build infrastructure](../../../ARCHITECTURE.md#build-infrastructure). - [ ] No hard-coded versions in subproject build files. +- [ ] No machine-specific JDK path or `targetJavaVersion` / + `groovyTargetBytecodeVersion` override left in a tracked + file; JDK targeting used `-Ptarget.java.home`, not a + tracked-file edit. No JDK was downloaded without asking. - [ ] Dependency changes are reflected in `gradle/verification-metadata.xml`; only the affected entries are touched. @@ -240,7 +279,8 @@ Before declaring the change ready: change. - [ ] If repackaging or distribution changed: `:groovy-binary:installGroovy` produced a working install, - exercised against a non-trivial script. + exercised against a non-trivial script with `GROOVY_HOME` + unset so the local build actually ran. - [ ] `./gradlew :<subproject>:check` passes locally. - [ ] `./gradlew build` passes — including `:binary-compatibility:check` — or any API breakage is diff --git a/.agents/skills/groovy-internals/SKILL.md b/.agents/skills/groovy-internals/SKILL.md index d3253a289b..b82332129f 100644 --- a/.agents/skills/groovy-internals/SKILL.md +++ b/.agents/skills/groovy-internals/SKILL.md @@ -174,7 +174,11 @@ When making a compiler/runtime change: 6. **For end-to-end behaviour, exercise an installed build** — `./gradlew :groovy-binary:installGroovy` and run a script against it. Tests verify code; an installed build verifies - the `groovy` / `groovyc` launchers see the change. See + the `groovy` / `groovyc` launchers see the change. + `unset GROOVY_HOME` first on SDKMAN machines, or the launcher + runs a different Groovy / fails with `ClassNotFoundException: + org.codehaus.groovy.tools.GroovyStarter` and the check proves + nothing. See ["Running your local build"](../../../CONTRIBUTING.md#running-your-local-build). ## Hand-back to a human diff --git a/.agents/skills/groovy-reproducer/SKILL.md b/.agents/skills/groovy-reproducer/SKILL.md index 388232454d..58bf7c8b7d 100644 --- a/.agents/skills/groovy-reproducer/SKILL.md +++ b/.agents/skills/groovy-reproducer/SKILL.md @@ -197,6 +197,23 @@ reproducers: for the project-side context. Add the new import per that mapping; don't classify as `still-fails-different` or `cannot-run-environment`. +16. **Running script reproducers with `GROOVY_HOME` pointing at a + SDKMAN install.** Shapes A and G run via the built `groovy` + launcher. On a machine with SDKMAN (or anything that exports + `GROOVY_HOME`), the launcher uses *that* Groovy, not the one + you just built — so a `passes` / `fixed-on-master` verdict + can be against an entirely different version, or the launcher + fails outright with `ClassNotFoundException: + org.codehaus.groovy.tools.GroovyStarter`. Both are + environment artefacts, not the reporter's bug — and distinct + from *Reproducer-stale-due-to-API-evolution treated as a bug* + above, which is a `ClassNotFoundException` on *import*, not + at launcher start. `unset GROOVY_HOME` before any launcher + run — see + ["Running your local build"](../../../CONTRIBUTING.md#running-your-local-build). + This is the highest-blast-radius false-`fixed-on-master` + trap in a campaign: every script-shape verdict is wrong if + it fires. ## Reproducer shape taxonomy @@ -207,7 +224,10 @@ recipe for each is the meat of this skill. the description or a comment, with imports and a top-level expression or `main`. Recipe: save to a scratch `.groovy`, build a current distribution (`./gradlew :installDist` on the relevant subproject), -and run with the built `groovy` binary. Or, for many cases, adapt as +and run with the built `groovy` binary (`unset GROOVY_HOME` first +on SDKMAN machines — see *Running script reproducers with +`GROOVY_HOME` pointing at a SDKMAN install* above). Or, for many +cases, adapt as a `@Test` per [`groovy-tests`](../groovy-tests/SKILL.md) and run targeted — but be aware of *Treating `@Test` adaptation as equivalent to a script run* above (script vs `@Test` semantics). @@ -365,12 +385,35 @@ For each reproducer: Don't write under the Groovy checkout. - **Working tree:** clean between reproducers. The added-and-then-removed `@Test` is the most common leak source. +- **`GROOVY_HOME`:** for any script-shape (A/G) run via the + built `groovy` launcher, `unset GROOVY_HOME` (or point it at + the local install) first — otherwise the launcher runs a + different Groovy or fails with the `GroovyStarter` + `ClassNotFoundException`. See *Running script reproducers with + `GROOVY_HOME` pointing at a SDKMAN install* in Top failure + modes. - **Grape cache:** for a campaign with many `@Grab` reproducers, consider `-Dgrape.root=<scratch>` so the user's `~/.groovy/grapes/` stays clean. - **JDK selection:** record the JDK used. For verdicts where it matters (`passes`, `fixed-on-master`), retry on the - originally-affected JDK via Gradle toolchains where reasonable. + originally-affected JDK — but the mechanism depends on how the + reproducer is being run: + - **`@Test`-shaped (shape B, or shape A adapted as a `@Test`), + run through Gradle:** pass + `-Ptarget.java.home=<java-home>`; the build runs the tests + on that JVM and auto-selects the matching toolchain. See + ["Building and testing against a specific JDK"](../../../CONTRIBUTING.md#building-and-testing-against-a-specific-jdk). + - **Script-shaped (shape A run directly, shape G), run with + the built `groovy` launcher:** `-Ptarget.java.home` does + *not* apply — it only governs the Gradle test JVM. The + launcher runs on whatever `JAVA_HOME` (or the environment + manager's active JDK) points at; set that to the target JDK + before invoking `groovy`. + + Either way, use a JDK already on the machine; don't download + one without asking (see *Downloading a JDK without asking* in + [`groovy-build`](../groovy-build/SKILL.md)). ## Cross-family probes (AI-tooling pattern) @@ -497,6 +540,10 @@ Before recording a verdict: before any "passes" verdict was claimed. - [ ] For `passes`: the run environment is qualified; the verdict doesn't over-claim "fixed" from a single environment. +- [ ] For script-shape (A/G) runs: `GROOVY_HOME` was unset (or + pointed at the local install) so the built Groovy actually + ran — not a SDKMAN one, and no `GroovyStarter` + `ClassNotFoundException`. - [ ] Working tree was reset (no leftover scratch test class). - [ ] Evidence package was written before the next issue started. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 216e303c4c..a6534b926a 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -326,6 +326,22 @@ A few build-side conventions: - **Versions flow through `gradle.properties` and the shared `Versions` type**, not as `'group:artifact:1.2.3'` literals in subproject builds. Ad-hoc version pins drift. +- **JDK and bytecode targeting are three separate knobs.** + `targetJavaVersion` sets the Java `source`/`target` + compatibility level for the project's Java sources and + Javadoc; `groovyTargetBytecodeVersion` sets the bytecode level + the forked Groovy compiler emits (passed as + `-Dgroovy.target.bytecode`). Both are pinned in + `gradle.properties` and read through `SharedConfiguration` — + that file is the single source of truth, so docs and skills + cite the property names, never the values (they change per + Groovy version). Neither controls *which* JVM runs the build + or tests: the build compiles with whatever JDK launched + Gradle, and tests run on that same JDK unless + `-Ptarget.java.home=<java-home>` overrides the test JVM (the + build then auto-derives the matching toolchain from that JDK's + `release` file). See [`CONTRIBUTING.md`](CONTRIBUTING.md) for + the recipe. - **Dependency changes require regenerating `gradle/verification-metadata.xml`.** The build runs Gradle dependency verification; an unverified artifact fails the diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4495071e91..b9b7979f7b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,8 +64,51 @@ to run a script against the build you just produced. On Unix-like systems, if you have SDKMAN (or any other tool that sets `GROOVY_HOME` to a fixed installation), `unset GROOVY_HOME` before running the launchers — otherwise they pick up that -environment variable instead of using the local build, and your -changes appear not to take effect. +environment variable instead of using the local build. Depending +on what `GROOVY_HOME` points at, the symptom is either that your +changes appear not to take effect (it ran a different Groovy), or +some launchers fail outright with: + +``` +Error: Could not find or load main class org.codehaus.groovy.tools.GroovyStarter +Caused by: java.lang.ClassNotFoundException: org.codehaus.groovy.tools.GroovyStarter +``` + +This is not a broken build and is not worth a JIRA issue — it is +a stale or mismatched `GROOVY_HOME`. `unset GROOVY_HOME` (or point +it at the local install) and re-run. + +### Building and testing against a specific JDK + +Three things determine "which Java" is involved, and they are +independent: + +- **The JDK that builds the code** — whatever JVM launched + `./gradlew` (subject to the minimum stated under *Building and + testing* above). Use an environment manager (SDKMAN, `jenv`, + Mise, asdf) or `JAVA_HOME` to pick it; this repository + deliberately ships **no** `.sdkmanrc`, so the build does not + pin a JDK for you. +- **The bytecode the artifacts target** — `targetJavaVersion` + (Java sources) and `groovyTargetBytecodeVersion` (Groovy + sources) in `gradle.properties`. These are the single source + of truth and move with the Groovy version; change them there, + never inline. Changing them changes what the produced jars + target, not the JDK you build with. See + [`COMPATIBILITY.md`](COMPATIBILITY.md) for the supported range. +- **The JDK the tests run on** — the build JDK by default. To + run tests on a *different* JDK without changing the build JDK, + pass its home directory: + + ``` + ./gradlew :test --tests <FQN> -Ptarget.java.home=/path/to/jdk + ``` + + The build prints `Using <java> to run tests` once per JVM so + you can confirm the override took effect, and it selects a + matching Gradle toolchain automatically. Set the property once + in `~/.gradle/gradle.properties` if you always test on the + same alternate JDK. ## Tests diff --git a/README.adoc b/README.adoc index b068fbd6da..a1b706a383 100644 --- a/README.adoc +++ b/README.adoc @@ -132,6 +132,8 @@ To build you will need: * {jdk}[JDK 17+] +The build compiles with whichever JDK runs `gradlew`. To target a different bytecode level, or to run tests on a different JDK, see the "Building and testing against a specific JDK" section in `CONTRIBUTING.md`. + To build everything using Gradle, use the following command (`./gradlew` on Unix-like systems): gradlew clean dist
