GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — fix(manifest): run Gradle and sbt on a JDK they support by mtorp · Pull Request #1665 · SocketDev/socket-cli Conversation
|
| Build tool | 8 | 11 | 17 | 21 | 25 |
|---|---|---|---|---|---|
| Gradle 4.10.3, 5.6.4 | ✅ | ✅ | |||
| Gradle 6.9.4 | ✅ | ❌ | |||
| Gradle 7.6.4 | ✅ | ✅ | ❌ | ||
| Gradle 8.5, 8.14.3 | ✅ | ✅ | ❌ | ||
| Gradle 9.0.0 | ❌ | ✅ | ✅ | ||
| sbt 0.13.18 | ✅ | ✅ | ✅ | ||
| sbt 1.2.8 | ✅ | ❌ | |||
| sbt 1.3.13, 1.5.8 | ✅ | ❌ | ❌ | ||
| sbt 1.8.3 | ❌ | ❌ | |||
| sbt 1.9.0, 1.9.9, 1.10.11 | ✅ |
Testing: unit tests for version parsing, ranges, discovery and selection use real temp-dir JDK layouts. A run.mts test checks that the spawned build gets the picked JAVA_HOME and that a configured one is kept. End to end, I ran the built CLI in an image with JAVA_HOME set to Temurin 25 and all five JDKs installed. socket manifest gradle on a Gradle 8.14.3 wrapper project ran on 21, and socket manifest scala on an sbt 1.5.8 project ran on 17. Both wrote facts containing the dependency. Without this change both builds fail to start on 25.
Follow-ups, not in this PR:
languageVersion that isn't installed..sdkmanrc, .tool-versions and actions/setup-java.main.Expected reviewer effort: High-level design decisions
Note
Medium Risk
Changes which JDK runs Gradle/sbt manifest builds when the default is incompatible; wrong compatibility tables or discovery could pick a bad JDK, though explicit javaHome/JAVA_HOME still wins.
Overview
When generating Socket facts via a Gradle wrapper or pinned sbt build, the CLI now detects a JDK/JAVA_HOME mismatch with the project's Gradle or sbt version and, if needed, runs the build on the newest installed JDK that version supports—logging the switch—instead of failing on a default JDK that is too new or too old.
A new java-home helper reads wrapper/build.properties versions, applies Gradle and sbt Java compatibility ranges, discovers JDKs under common install paths and CI JAVA_HOME_* variables, and runManifestScript / enumerateWorkspaces set JAVA_HOME on the spawned process when auto-selection applies. Maven, non-wrapper Gradle, and an explicit JAVA_HOME in options (including socket.json javaHome) are unchanged; if no suitable JDK is found, behavior stays as before.
isBuildToolWrapper was added so Gradle version detection only runs for wrapper invocations. Unit and integration-style tests cover parsing, ranges, discovery, selection, and spawned env.
Reviewed by Cursor Bugbot for commit 8b2e827. Configure here.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Gradle before 9.1 and sbt before 1.9 do not start on Java 25, and sbt before 1.3 does not start on Java 17. A machine whose default JDK is newer than the project's pinned build tool supports therefore failed to generate Socket facts, even with a suitable JDK installed. Facts generation now reads the Gradle wrapper or sbt version the project pins and, when the current JDK is outside the range that version runs on, runs the build on the newest installed JDK inside it. Installed JDKs are found under the usual JVM directories, SDKMAN, Gradle- and IDE-provisioned JDKs, macOS bundles, and the JAVA_HOME_<release>_<arch> variables CI runners set. A javaHome configured in socket.json still wins, and Maven builds keep the current JDK.
The Gradle and sbt Java ranges now come from one table and one function, compared with semver. JDK discovery reads its search directories and candidates in parallel, and the two test files share their fake JDK and Gradle wrapper fixtures.
A Gradle wrapper set as the bin in socket.json, or passed as a path like ../gradlew, now gets a JDK it supports too. The wrapper version is read from the gradle/wrapper folder next to the script, wherever that script lives. Also reads files through safeReadFile, drops two sbt table rows the lookup never reached, and states the Java 17 threshold as a version like the rest of the table.
Assisted-by: Claude Code:claude-sonnet-5-5
Version detection now asks build-tool.mts whether a bin is the Gradle wrapper, so the wrapper name lives in one place. Also stops exporting three types nothing imports and finds the newest supported Java with findLast.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Approving on the understanding that the four Before merge threads are fixed before this merges; the rest are optional.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| if (javaHome) { | ||
| return await readJavaInstallation(javaHome) | ||
| } | ||
| const java = await whichBin('java', { nothrow: true, path: env['PATH'] }) |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Before merge: whichBin rejects even with nothrow: true when nothing is found: it calls resolveBinPathSync(null) and fails with ENOENT … realpath 'null'. With JAVA_HOME unset and no java on PATH, the whole facts run aborts, in exactly the setup where discovery (e.g. JAVA_HOME_17_X64 on a runner) should have picked a JDK. A test for it: expect(await currentJavaInstallation({ PATH: path.join(root, 'empty') })).toBeUndefined().
| const java = await whichBin('java', { nothrow: true, path: env['PATH'] }) | |
| // whichBin rejects on a miss despite nothrow. | |
| const java = await whichBin('java', { | |
| nothrow: true, | |
| path: env['PATH'], | |
| }).catch(() => undefined) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| return children | ||
| .sort() | ||
| .flatMap(child => [ | ||
| path.join(dir, child), | ||
| path.join(dir, child, 'Contents', 'Home'), | ||
| ]) |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Before merge: Gradle-provisioned JDKs mostly sit one level deeper than this looks, e.g. ~/.gradle/jdks/eclipse_adoptium-17-aarch64-os_x.2/jdk-17.0.12+7/ and oracle_corporation-21-aarch64-os_x/jdk-21.0.2.jdk/Contents/Home. Only some vendors (bellsoft, azul) unpack flat. This descends one level into children that aren't JDKs themselves. To test it, add fakeJdk(path.join(jvm, 'eclipse_adoptium-11-x64-linux', 'jdk-11.0.24+8'), '11.0.24') to the discovery test. It lands after jdk17, because the default symlink sorts first.
| return children | |
| .sort() | |
| .flatMap(child => [ | |
| path.join(dir, child), | |
| path.join(dir, child, 'Contents', 'Home'), | |
| ]) | |
| // Gradle provisions into <vendor-version-arch-os>/<jdk-x.y.z>/. | |
| const nested = await Promise.all( | |
| children.sort().map(async child => { | |
| if (existsSync(path.join(dir, child, 'bin'))) { | |
| return [child] | |
| } | |
| const grandchildren = await fs | |
| .readdir(path.join(dir, child)) | |
| .catch((): string[] => []) | |
| return [ | |
| child, | |
| ...grandchildren | |
| .sort() | |
| .map(grandchild => path.join(child, grandchild)), | |
| ] | |
| }), | |
| ) | |
| return nested | |
| .flat() | |
| .flatMap(child => [ | |
| path.join(dir, child), | |
| path.join(dir, child, 'Contents', 'Home'), | |
| ]) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| const fits = ({ feature }: JavaInstallation) => | ||
| feature >= range.min && feature <= range.max | ||
| const current = await currentJavaInstallation(opts.env) | ||
| if (current && fits(current)) { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Before merge: A current JDK we can't identify is treated as one that doesn't fit, so we switch away from a JDK that probably works, and the log says the tool "does not run on" it. On macOS without JAVA_HOME, /usr/bin/java is a launcher stub, not a symlink, so current is always undefined there and every Gradle wrapper or sbt run switches JDK. The same happens when JAVA_HOME points at a JDK without a release file. I'd switch only when the current JDK is known not to fit, or when there is no Java at all (no JAVA_HOME, nothing on PATH), and keep it when a Java exists but can't be identified. For the macOS case, /usr/libexec/java_home gives the real home.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| // Past the table's newest entry the tool may support newer Java than we know | ||
| // of, so any Java counts as supported there. | ||
| if (semver.gt(parsed, runsOnJavaFrom[runsOnJavaFrom.length - 1]![1])) { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Before merge: This makes every release after the newest table entry unbounded, not just new majors. Gradle 9.1.0 caps at 25, but 9.1.1 or 9.2 runs on anything, and likewise sbt 1.9.0 versus 1.9.1. Once a JDK 26 default arrives, a Gradle 9.2 project won't be moved off it, which is the failure this PR fixes. One option: only a newer major is unbounded, and later releases in the same line keep the newest known limit. The cost is an occasional unnecessary switch to an older installed JDK until the table catches up, which seems the safer direction. The test change is in the thread on java-home.test.mts:65.
| // Past the table's newest entry the tool may support newer Java than we know | |
| // of, so any Java counts as supported there. | |
| if (semver.gt(parsed, runsOnJavaFrom[runsOnJavaFrom.length - 1]![1])) { | |
| // A major release past the table's newest entry may support newer Java than | |
| // we know of, so any Java counts as supported there. | |
| const { 1: newest } = runsOnJavaFrom[runsOnJavaFrom.length - 1]! | |
| if (semver.major(parsed) > semver.major(newest)) { |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| it('runs a Gradle newer than it knows of on any Java from 17', () => { | ||
| expect(javaRange('gradle', '9.4.0')).toEqual({ |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Before merge (goes with the javaRange thread at java-home.mts:82): the test update if you take that suggestion.
| it('runs a Gradle newer than it knows of on any Java from 17', () => { | |
| expect(javaRange('gradle', '9.4.0')).toEqual({ | |
| it('runs a later Gradle 9 on what Gradle 9.1 runs on', () => { | |
| expect(javaRange('gradle', '9.4.0')).toEqual({ min: 17, max: 25 }) | |
| }) | |
| it('runs a Gradle major newer than it knows of on any Java from 17', () => { | |
| expect(javaRange('gradle', '10.0.0')).toEqual({ |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| env: NodeJS.ProcessEnv, | ||
| searchDirs: readonly string[] = defaultJavaSearchDirs(), | ||
| ): Promise<JavaInstallation[]> { | ||
| const runnerHomes = Object.entries(env).flatMap(([name, value]) => |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Nit: same array-destructuring convention.
| const runnerHomes = Object.entries(env).flatMap(([name, value]) => | |
| const runnerHomes = Object.entries(env).flatMap(({ 0: name, 1: value }) => |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| ], | ||
| needsJava17From: '9.0.0', | ||
| }, | ||
| // LTS Java releases only. The sbt version is the project's, which the |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Nit: "LTS Java releases only" reads as if every LTS has a row, so a reader will wonder whether 21 was forgotten. It's covered because 21 and 25 share the same first sbt release, which may be worth saying.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| // launcher downloads and runs. | ||
| sbt: { | ||
| runsOnJavaFrom: [ | ||
| [11, '0.13.0'], |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Nit: The table caps 0.13.x at 11, but your own measurements show 0.13.18 starting on 17. Since 1.0 to 1.2 don't, a monotonic table can't express that. It's harmless (at worst an unneeded switch to 11), but the comment could mention it so nobody "fixes" the table later.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| } | ||
|
|
||
| describe('build JDK', () => { | ||
| it('runs the Gradle wrapper on a JDK it supports', async () => { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Nit: This test stubs process.env, so discovery also scans the host's real JDK folders. That's why it can only assert "not 25" and has to parse the picked release file. Passing searchDirs through (or an explicit javaHome option, as in the run.mts:184 thread) would let it assert toBe(jdk21).
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
|
||
| type JavaRange = { min: number; max: number } | ||
|
|
||
| type BuildJavaHome = { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
[agent] Nit: current and toolVersion are only used for the log message in withBuildJavaHome. Returning just the picked installation, with the message built here, would drop this comment, the nested ternary in the log, and a test that pins the log's inputs as API.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the current head. The unresolved inline threads identify the remaining material issues. 👀
Sorry, something went wrong.
There was an error while loading. Please reload this page.
At least 1 approving review is required to merge this pull request.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.