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

Reply via email to