Skip to content

Commit bfcdbcc

Browse files
authored
fix: match SDKMAN's early-access identifiers in .sdkmanrc to the installed version (#88)
1 parent 8061bf2 commit bfcdbcc

7 files changed

Lines changed: 143 additions & 31 deletions

File tree

‎docs/e2e.md‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,14 @@ JDK, so `java` can only come from what vfox installs:
8888
(`21.0.80+1-tem`): vfox sorts installed versions as strings, descending,
8989
so `vfox list java` must show it first. `ParseLegacyFile` used to take it
9090
as the first one starting with the release, and, for the padded release,
91-
as the newest release of the major. On Windows the file is written with
92-
CRLF line endings.
91+
as the newest release of the major. Then SDKMAN's identifier of an
92+
early-access build of the pinned major, spelled as `sdk list java` shows
93+
OpenJDK's (`28.0.0.0+ea.20-open`), must select that build:
94+
`java=21.0.0.0+ea.1-tem` selects `21-ea+1-tem`, not the release, which
95+
`ParseLegacyFile` took while it understood only SDKMAN's older
96+
`21.ea.1-tem`. jdk.java.net removes each early-access build within weeks,
97+
so that install is faked: its `bin` holds a stand-in `java` that only
98+
prints the name. On Windows the file is written with CRLF line endings.
9399
5. After `vfox use --global`, the JDK on `PATH` reports the requested
94100
`java.version`, the runner's `os.arch` (`amd64` / `aarch64`, which also
95101
catches an x64 JDK emulated on Windows ARM64) and the distribution's

‎hooks/parse_legacy_file.lua‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ local installed_version = require("installed_version")
2121
--- reads. SDKMAN calls JetBrains jbr (the plugin's jb) and puts fx in the
2222
--- version of JavaFX builds: 21.0.12.fx-zulu, or 21.0.12-fx+1.1-librca in its
2323
--- semverish identifiers (the plugin's 21.0.12-zulu-fx, 21.0.12+1.1-librca-fx).
24+
--- The semverish ones spell early-access builds 28.0.0+ea.19-open, padded or
25+
--- not (28.0.0.0+ea.19-open), where SDKMAN used to write the plugin's
26+
--- 28.ea.19-open (Foojay's 28-ea+19).
2427
--- Any other spelling, the plugin's own included, is returned unchanged.
2528
local function plugin_spelling(identifier)
2629
local version, distribution = identifier:match("^(.+)%-([^-]+)$")
@@ -30,11 +33,16 @@ local function plugin_spelling(identifier)
3033
if distribution == "jbr" then
3134
distribution = "jb"
3235
end
36+
local suffix = "-" .. distribution
3337
local release, build = version:match("^(%d[%d.]*)[.-]fx(.*)$")
3438
if release then
35-
return release .. build .. "-" .. distribution .. "-fx"
39+
version, suffix = release .. build, suffix .. "-fx"
3640
end
37-
return version .. "-" .. distribution
41+
local major, ea_build = version:match("^(%d+)[.0]*%+ea%.(%d+)$")
42+
if major then
43+
version = major .. ".ea." .. ea_build
44+
end
45+
return version .. suffix
3846
end
3947

4048
function PLUGIN:ParseLegacyFile(ctx)

‎tests/e2e/common/common.go‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,29 @@ func PaddedIdentifier(distribution, version, installed string) string {
308308
return version + buildNumber.FindString(installed) + "-" + sdkmanName(distribution)
309309
}
310310

311+
// EarlyAccess renders the installed name of an early-access build of the
312+
// pinned major (<major>-ea+1, e.g. 21-ea+1-tem, 25-ea+1 for OpenJDK).
313+
// jdk.java.net removes each early-access build within weeks, so the suite
314+
// fakes the install: a java-<version> directory whose bin holds a stand-in
315+
// java that only prints this name.
316+
func EarlyAccess(distribution, version string) string {
317+
return Spec(distribution, Major(version)+"-ea+1")
318+
}
319+
320+
// EarlyAccessIdentifier renders SDKMAN's identifier of that build, spelled
321+
// as `sdk list java` shows OpenJDK's (28.0.0.0+ea.20-open):
322+
// "<major>.0.0.0[-fx]+ea.1-<distribution>" (21.0.0.0+ea.1-tem,
323+
// 25.0.0.0-fx+ea.1-librca). ParseLegacyFile matched only SDKMAN's older
324+
// 21.ea.1-tem to it, so it took the newest release of the major instead
325+
// (vfox-java#86).
326+
func EarlyAccessIdentifier(distribution, version string) string {
327+
version = Major(version) + ".0.0.0"
328+
if JavaFX(distribution) {
329+
version += "-fx"
330+
}
331+
return version + "+ea.1-" + sdkmanName(distribution)
332+
}
333+
311334
// Alias renders the asdf-style spelling "<long-name>-<version>[-fx]" of an
312335
// installed version, which PreUse must rewrite back to installed
313336
// (temurin-21.0.8+9 to 21.0.8+9-tem, openjdk-25.0.2+10 to 25.0.2+10).

‎tests/e2e/common/common_test.go‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,23 @@ func TestPaddedIdentifier(t *testing.T) {
222222
}
223223
}
224224

225+
func TestEarlyAccess(t *testing.T) {
226+
for _, c := range []struct{ dist, version, installed, identifier string }{
227+
{"open", "25.0.2", "25-ea+1", "25.0.0.0+ea.1-open"},
228+
{"tem", "21.0.8", "21-ea+1-tem", "21.0.0.0+ea.1-tem"},
229+
{"librca-fx", "25.0.4", "25-ea+1-librca-fx", "25.0.0.0-fx+ea.1-librca"},
230+
{"jb", "21.0.5", "21-ea+1-jb", "21.0.0.0+ea.1-jbr"},
231+
{"graalce", "25.0.2", "25-ea+1-graalce", "25.0.0.0+ea.1-graalce"},
232+
} {
233+
if got := EarlyAccess(c.dist, c.version); got != c.installed {
234+
t.Errorf("EarlyAccess(%s, %s) = %q, want %q", c.dist, c.version, got, c.installed)
235+
}
236+
if got := EarlyAccessIdentifier(c.dist, c.version); got != c.identifier {
237+
t.Errorf("EarlyAccessIdentifier(%s, %s) = %q, want %q", c.dist, c.version, got, c.identifier)
238+
}
239+
}
240+
}
241+
225242
func TestAlias(t *testing.T) {
226243
for _, c := range []struct{ dist, installed, want string }{
227244
{"open", "25.0.2+10", "openjdk-25.0.2+10"},

‎tests/e2e/linux/verify.go‎

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,12 @@ func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, distribut
5656
// java=25.0.4.fx-librca), the one with a zero padding the release (e.g.
5757
// java=21.0.8.0+9-tem, see common.PaddedIdentifier) or the installed version
5858
// itself (e.g. java=21.0.8+9-tem), while vfox lists an empty install of
59-
// another release first (see common.Decoy). It runs before any `vfox use`,
60-
// so java can only come from the legacy file.
59+
// another release first (see common.Decoy). SDKMAN's identifier of an
60+
// early-access build (e.g. java=21.0.0.0+ea.1-tem) must then select a faked
61+
// install of that build (see common.EarlyAccess), not the release. It runs
62+
// before any `vfox use`, so java can only come from the legacy file.
6163
func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, distribution, version, installed, arch string) {
6264
t.Helper()
63-
const dir = "/tmp/e2e-legacy-sdkmanrc"
6465
// vfox counts a version as installed once its directory holds
6566
// java-<version>; the decoy holds nothing more, so selecting it leaves
6667
// no java on PATH.
@@ -75,16 +76,37 @@ func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, distr
7576
common.PaddedIdentifier(distribution, version, installed),
7677
installed,
7778
} {
78-
execOK(ctx, t, ctr, fmt.Sprintf("mkdir --parents %s && printf 'java=%%s\\n' %s > %s/.sdkmanrc",
79-
dir, shQuote(identifier), dir))
80-
// The probe activates inside its own exec, a fresh shell session, so no
81-
// `vfox env` state cached by an earlier call can answer for the file.
82-
out := execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, fmt.Sprintf(
83-
`cd %s && eval "$(vfox activate bash)" && eval "$(vfox env -s bash)" && %s`, dir, showSettings))
79+
out := legacyJava(ctx, t, ctr, identifier)
8480
common.CheckJava(t, out, distribution, version, arch, ".sdkmanrc java="+identifier)
8581
t.Logf("PASS .sdkmanrc java=%s selects java %s, not %s", identifier, version, decoy)
8682
}
8783
execOK(ctx, t, ctr, "rm --recursive "+decoyDir)
84+
85+
// The early-access build's java only prints its name, which the
86+
// release's java, selected instead, would not.
87+
ea := common.EarlyAccess(distribution, version)
88+
eaDir := `"${VFOX_HOME:-$HOME/.vfox}"/cache/java/` + shQuote("v-"+ea)
89+
bin := eaDir + "/" + shQuote("java-"+ea) + "/bin"
90+
execOK(ctx, t, ctr, fmt.Sprintf("mkdir --parents %s && printf '%%s' %s > %s/java && chmod +x %s/java",
91+
bin, shQuote("#!/bin/sh\necho "+shQuote(ea)+"\n"), bin, bin))
92+
identifier := common.EarlyAccessIdentifier(distribution, version)
93+
out := legacyJava(ctx, t, ctr, identifier)
94+
require.Containsf(t, out, ea, ".sdkmanrc java=%s must select %s, not %s", identifier, ea, installed)
95+
t.Logf("PASS .sdkmanrc java=%s selects %s, not %s", identifier, ea, installed)
96+
execOK(ctx, t, ctr, "rm --recursive "+eaDir)
97+
}
98+
99+
// legacyJava writes java=<identifier> to a .sdkmanrc and returns what java
100+
// prints next to it. The probe activates inside its own exec, a fresh shell
101+
// session, so no `vfox env` state cached by an earlier call can answer for
102+
// the file.
103+
func legacyJava(ctx context.Context, t *testing.T, ctr tc.Container, identifier string) string {
104+
t.Helper()
105+
const dir = "/tmp/e2e-legacy-sdkmanrc"
106+
execOK(ctx, t, ctr, fmt.Sprintf("mkdir --parents %s && printf 'java=%%s\\n' %s > %s/.sdkmanrc",
107+
dir, shQuote(identifier), dir))
108+
return execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, fmt.Sprintf(
109+
`cd %s && eval "$(vfox activate bash)" && eval "$(vfox env -s bash)" && %s`, dir, showSettings))
88110
}
89111

90112
// verifyToolchain runs java and javac through the activate hook and checks

‎tests/e2e/windows/verify.go‎

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,12 @@ func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, box slotB
5959
// java=21.0.8.0+9-tem, see common.PaddedIdentifier) or the installed version
6060
// itself (e.g. java=21.0.8+9-tem), written by Set-Content with Windows line
6161
// endings, while vfox lists an empty install of another release first (see
62-
// common.Decoy). It runs before any `vfox use`, so java can only come from
63-
// the legacy file.
62+
// common.Decoy). SDKMAN's identifier of an early-access build (e.g.
63+
// java=21.0.0.0+ea.1-tem) must then select a faked install of that build
64+
// (see common.EarlyAccess), not the release. It runs before any `vfox use`,
65+
// so java can only come from the legacy file.
6466
func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, distribution, version, installed, arch string) {
6567
t.Helper()
66-
const dir = `C:\e2e-legacy-sdkmanrc`
6768
// vfox counts a version as installed once its directory holds
6869
// java-<version>; the decoy holds nothing more, so selecting it leaves
6970
// no java on PATH.
@@ -79,25 +80,46 @@ func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, box s
7980
common.PaddedIdentifier(distribution, version, installed),
8081
installed,
8182
} {
82-
execOK(ctx, t, ctr, fmt.Sprintf(
83-
"New-Item -ItemType Directory -Force -Path %s | Out-Null; Set-Content -Path %s -Value %s",
84-
pwshQuote(dir), pwshQuote(dir+`\.sdkmanrc`), pwshQuote("java="+identifier)), box.vars...)
85-
// The probe activates its own PowerShell process. Activation pins
86-
// __VFOX_PID, which scopes the `vfox env` state cache, but Windows may
87-
// reuse a just-exited PID and legacy files are not among the cache's
88-
// tracked inputs, so the cache file is removed before the export.
89-
script := fmt.Sprintf(
90-
"Invoke-Expression -Command (vfox activate pwsh | Out-String); "+
91-
"Set-Location -LiteralPath %s; "+
92-
"if ($env:__VFOX_CURTMPPATH) { Remove-Item -LiteralPath (Join-Path $env:__VFOX_CURTMPPATH 'env-state.json') -Force -ErrorAction SilentlyContinue }; "+
93-
"$__vfoxExport = vfox env -s pwsh | Out-String; "+
94-
"if ($__vfoxExport.Trim()) { Invoke-Expression -Command $__vfoxExport }; "+
95-
"java %s", pwshQuote(dir), showSettings)
96-
out := execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, script, box.vars...)
83+
out := legacyJava(ctx, t, ctr, box, identifier)
9784
common.CheckJava(t, out, distribution, version, arch, ".sdkmanrc java="+identifier)
9885
t.Logf("PASS .sdkmanrc java=%s selects java %s, not %s", identifier, version, decoy)
9986
}
10087
execOK(ctx, t, ctr, fmt.Sprintf("Remove-Item -LiteralPath %s -Recurse -Force", decoyDir), box.vars...)
88+
89+
// The early-access build's java (a java.cmd) only prints its name, which
90+
// the release's java, selected instead, would not.
91+
ea := common.EarlyAccess(distribution, version)
92+
eaDir := fmt.Sprintf("(Join-Path $env:VFOX_HOME %s)", pwshQuote(`cache\java\v-`+ea))
93+
execOK(ctx, t, ctr, fmt.Sprintf(
94+
"$bin = Join-Path %s %s; New-Item -ItemType Directory -Force -Path $bin | Out-Null; "+
95+
"Set-Content -Path (Join-Path $bin 'java.cmd') -Value %s",
96+
eaDir, pwshQuote(`java-`+ea+`\bin`), pwshQuote("@echo "+ea)), box.vars...)
97+
identifier := common.EarlyAccessIdentifier(distribution, version)
98+
out := legacyJava(ctx, t, ctr, box, identifier)
99+
require.Containsf(t, out, ea, ".sdkmanrc java=%s must select %s, not %s", identifier, ea, installed)
100+
t.Logf("PASS .sdkmanrc java=%s selects %s, not %s", identifier, ea, installed)
101+
execOK(ctx, t, ctr, fmt.Sprintf("Remove-Item -LiteralPath %s -Recurse -Force", eaDir), box.vars...)
102+
}
103+
104+
// legacyJava writes java=<identifier> to a .sdkmanrc and returns what java
105+
// prints next to it. The probe activates its own PowerShell process.
106+
// Activation pins __VFOX_PID, which scopes the `vfox env` state cache, but
107+
// Windows may reuse a just-exited PID and legacy files are not among the
108+
// cache's tracked inputs, so the cache file is removed before the export.
109+
func legacyJava(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, identifier string) string {
110+
t.Helper()
111+
const dir = `C:\e2e-legacy-sdkmanrc`
112+
execOK(ctx, t, ctr, fmt.Sprintf(
113+
"New-Item -ItemType Directory -Force -Path %s | Out-Null; Set-Content -Path %s -Value %s",
114+
pwshQuote(dir), pwshQuote(dir+`\.sdkmanrc`), pwshQuote("java="+identifier)), box.vars...)
115+
script := fmt.Sprintf(
116+
"Invoke-Expression -Command (vfox activate pwsh | Out-String); "+
117+
"Set-Location -LiteralPath %s; "+
118+
"if ($env:__VFOX_CURTMPPATH) { Remove-Item -LiteralPath (Join-Path $env:__VFOX_CURTMPPATH 'env-state.json') -Force -ErrorAction SilentlyContinue }; "+
119+
"$__vfoxExport = vfox env -s pwsh | Out-String; "+
120+
"if ($__vfoxExport.Trim()) { Invoke-Expression -Command $__vfoxExport }; "+
121+
"java %s", pwshQuote(dir), showSettings)
122+
return execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, script, box.vars...)
101123
}
102124

103125
// verifyToolchain runs java and javac through the activate + env hooks and

‎tests/parse_legacy_file_test.lua‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,20 @@ local cases = {
9898
{ "java=25.0.1.0+8-open\n", { "25+36" }, "25+36" },
9999
{ "java=26.0.1-oracle\n", { "26-oracle" }, "26-oracle" },
100100
{ "java=26.0.2-oracle\n", { "26-oracle", "26.0.1-oracle" }, "26.0.1-oracle" },
101+
-- SDKMAN's semverish identifiers write early-access builds 28.0.0+ea.19,
102+
-- padded or not, where it used to write 28.ea.19 (vfox-java#86).
103+
{ "java=28.ea.19-open\n", { "28-ea+19" }, "28-ea+19" },
104+
{ "java=28.0.0+ea.19-open\n", { "28-ea+19" }, "28-ea+19" },
105+
{ "java=28.0.0.0+ea.19-open\n", { "28-ea+18", "28-ea+19", "27+36" }, "28-ea+19" },
106+
{ "java=26.0.0+ea.13-graal\n", { "26-ea+2-graal", "26-ea+13-graal" }, "26-ea+13-graal" },
107+
{ "java=28.0.0.0+ea.19-jbr\n", { "28-ea+19-jb" }, "28-ea+19-jb" },
108+
{ "java=28.0.0.0-fx+ea.19-zulu\n", { "28-ea+19-zulu", "28-ea+19-zulu-fx" }, "28-ea+19-zulu-fx" },
109+
-- A build not installed takes the newest one installed, as 28.ea.20 does.
110+
{ "java=28.ea.20-open\n", { "28-ea+18", "28-ea+19" }, "28-ea+19" },
111+
{ "java=28.0.0.0+ea.20-open\n", { "28-ea+18", "28-ea+19" }, "28-ea+19" },
112+
-- Never a release for an early-access build, nor the other way round.
113+
{ "java=28.0.0.0+ea.19-open\n", { "28+36", "28.0.1+3" }, nil },
114+
{ "java=28.0.0+36-open\n", { "28-ea+19" }, nil },
101115
-- No java line at all selects nothing.
102116
{ "maven=3.9.9\n", { "21.0.8+9-tem" }, nil },
103117
}

0 commit comments

Comments
 (0)