GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

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

@mtorp

@mtorp Martin Torp (mtorp) commented Oct 29, 2026 •
edited by cursor Bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Older Gradle and sbt versions refuse to start on newer JDKs. So a machine whose default JDK is newer than the project's pinned build tool fails to generate Socket facts, even when a suitable JDK is installed next to it. Managed reachability scans want to move their default JDK to 25, and Gradle 8 projects are where that breaks.

When facts generation runs a Gradle wrapper or sbt build, the CLI now reads the version the project pins and checks it against the JDK in JAVA_HOME (or the java on PATH). If the build tool doesn't run on that JDK, the build runs on the newest installed JDK it does run on, and the CLI logs which one:

ℹ Running gradle 8.14.3 on Java 21 at /usr/lib/jvm/temurin-21, since it does not run on Java 25 at /usr/lib/jvm/temurin-25.
  • Versions: the Gradle version comes from gradle/wrapper/gradle-wrapper.properties and the sbt version from project/build.properties. A Gradle wrapper given as the bin, such as ../gradlew or an absolute path, is read from the gradle/wrapper folder next to it. A Gradle on PATH, any other custom bin and Maven keep the current JDK.
  • Supported ranges: Gradle follows its compatibility matrix. Gradle 9 needs at least 17. sbt needs 1.3 for Java 17 and 1.9 for Java 21 and 25, which I measured (table below). A version newer than the table knows is allowed on any JDK.
  • Discovery: JDKs are found by their release file in the children of /usr/lib/jvm, /usr/lib64/jvm, /usr/java and /opt/java, in SDKMAN, in ~/.gradle/jdks and ~/.jdks, in macOS JavaVirtualMachines bundles, and through the JAVA_HOME_<release>_<arch> variables GitHub and Azure runners set.
  • What stays the same: a javaHome from socket.json is used as is. If no installed JDK fits, the build runs on the current one, as it did before.

Where each build tool started and resolved a Guava dependency (Temurin in Docker):

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:

  • A retry on a recognisable failure, for example a toolchain languageVersion that isn't installed.
  • Per-project JDK hints such as .sdkmanrc, .tool-versions and actions/setup-java.
  • A port to 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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Martin Torp (mtorp) added 5 commits October 29, 2026 08:02
@mtorp
fix(manifest): run Gradle and sbt on a JDK they support
2e1d6d4
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.
@mtorp
refactor(manifest): simplify the build JDK picker
69ddd81
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.
@mtorp
fix(manifest): find the Gradle wrapper behind a bin
31c5a31
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.
@mtorp
Merge v1.x into martin/rea-1043-pick-jdk-per-build
e69b664
Assisted-by: Claude Code:claude-sonnet-5-5
@mtorp
refactor(manifest): share the Gradle wrapper name
8b2e827
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.
@mtorp
Martin Torp (mtorp) marked this pull request as ready for review October 30, 2026 12:07
@mtorp
Martin Torp (mtorp) requested a review from Jeppe Fredsgaard Blaabjerg (jfblaa) October 30, 2026 12:07
jfblaa
Jeppe Fredsgaard Blaabjerg (jfblaa) approved these changes Oct 30, 2026
View reviewed changes

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/commands/manifest/scripts/java-home.mts
if (javaHome) {
return await readJavaInstallation(javaHome)
}
const java = await whichBin('java', { nothrow: true, path: env['PATH'] })

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) Oct 30, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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().

Suggested change
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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/commands/manifest/scripts/java-home.mts
Comment on lines +144 to +149
return children
.sort()
.flatMap(child => [
path.join(dir, child),
path.join(dir, child, 'Contents', 'Home'),
])

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) Oct 30, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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.

Suggested change
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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/commands/manifest/scripts/java-home.mts
const fits = ({ feature }: JavaInstallation) =>
feature >= range.min && feature <= range.max
const current = await currentJavaInstallation(opts.env)
if (current && fits(current)) {

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) Oct 30, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/commands/manifest/scripts/java-home.mts
Comment on lines +80 to +82
// 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])) {

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) Oct 30, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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.

Suggested change
// 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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/commands/manifest/scripts/java-home.test.mts
Comment on lines +65 to +66
it('runs a Gradle newer than it knows of on any Java from 17', () => {
expect(javaRange('gradle', '9.4.0')).toEqual({

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) Oct 30, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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.

Suggested change
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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/commands/manifest/scripts/java-home.mts
env: NodeJS.ProcessEnv,
searchDirs: readonly string[] = defaultJavaSearchDirs(),
): Promise<JavaInstallation[]> {
const runnerHomes = Object.entries(env).flatMap(([name, value]) =>

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) Oct 30, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[agent] Nit: same array-destructuring convention.

Suggested change
const runnerHomes = Object.entries(env).flatMap(([name, value]) =>
const runnerHomes = Object.entries(env).flatMap(({ 0: name, 1: value }) =>

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/commands/manifest/scripts/java-home.mts
],
needsJava17From: '9.0.0',
},
// LTS Java releases only. The sbt version is the project's, which the

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) Oct 30, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/commands/manifest/scripts/java-home.mts
// launcher downloads and runs.
sbt: {
runsOnJavaFrom: [
[11, '0.13.0'],

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) Oct 30, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/commands/manifest/scripts/run.test.mts
}

describe('build JDK', () => {
it('runs the Gradle wrapper on a JDK it supports', async () => {

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) Oct 30, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread src/commands/manifest/scripts/java-home.mts

type JavaRange = { min: number; max: number }

type BuildJavaHome = {

@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) Oct 30, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
jdalton
John-David Dalton (jdalton) reviewed Nov 1, 2026
View reviewed changes

@jdalton John-David Dalton (jdalton) left a comment •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for 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.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@jdalton John-David Dalton (jdalton) jdalton left review comments
@jfblaa Jeppe Fredsgaard Blaabjerg (jfblaa) jfblaa approved these changes

At least 1 approving review is required to merge this pull request.

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@mtorp @jdalton @jfblaa
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.