diff --git a/docs/e2e.md b/docs/e2e.md index 1f058ee..a579e57 100644 --- a/docs/e2e.md +++ b/docs/e2e.md @@ -80,12 +80,16 @@ JDK, so `java` can only come from what vfox installs: without the build number selects the JDK through `ParseLegacyFile`: `java=21.0.8-tem`, `java=21.0.5-jbr` (SDKMAN's name for JetBrains) or `java=25.0.4.fx-librca` (its spelling of JavaFX builds). So does one - holding the installed version (`java=21.0.8+9-tem`). Both run next to an - empty install of another release whose version starts with the pinned - one (`21.0.80+1-tem`): vfox sorts installed versions as strings, - descending, so `vfox list java` must show it first, and - `ParseLegacyFile` used to take it as the first one starting with the - release. On Windows the file is written with CRLF line endings. + padding the release with a zero, as `sdk list java` shows many: + `java=21.0.8.0+9-tem`, `java=25.0.4.0-fx+9-librca` (Foojay, and so the + installed name, has no such zero). So does one holding the installed + version (`java=21.0.8+9-tem`). All run next to an empty install of + another release whose version starts with the pinned one + (`21.0.80+1-tem`): vfox sorts installed versions as strings, descending, + so `vfox list java` must show it first. `ParseLegacyFile` used to take it + as the first one starting with the release, and, for the padded release, + as the newest release of the major. On Windows the file is written with + CRLF line endings. 5. After `vfox use --global`, the JDK on `PATH` reports the requested `java.version`, the runner's `os.arch` (`amd64` / `aarch64`, which also catches an x64 JDK emulated on Windows ARM64) and the distribution's diff --git a/hooks/parse_legacy_file.lua b/hooks/parse_legacy_file.lua index a9132b2..3cdf5c0 100644 --- a/hooks/parse_legacy_file.lua +++ b/hooks/parse_legacy_file.lua @@ -65,7 +65,12 @@ function PLUGIN:ParseLegacyFile(ctx) return {} end - local version = strings.split(fullVersion, "+")[1] + --- SDKMAN pads the release with zeros that Foojay, and so the installed + --- name, leaves out: 27.0.0+36 is 27+36, 25.0.1.0+8 is 25.0.1+8. + local version, build = fullVersion:match("^([^+]*)(.*)$") + while version:find("%.0$") do + version = version:sub(1, -3) + end local majorVersion = strings.split(version, ".")[1] --- Installed versions end in - (OpenJDK's in nothing), then -fx for @@ -81,14 +86,16 @@ function PLUGIN:ParseLegacyFile(ctx) installed[v] = true end - --- The version itself (21.0.5+63130-jb), else the newest build of the - --- release, as SDKMAN's build numbers count its own rebuilds + --- The version itself (21.0.5+63130-jb), else the release itself or its + --- newest build, as SDKMAN's build numbers count its own rebuilds --- (21.0.12+1.1-tem), else the newest installed release of the major - --- (21.0.12+8-tem for 21.0.3-tem). - local name = fullVersion .. suffix + --- (21.0.12+8-tem for 21.0.3-tem). A bare major (21-tem) skips the + --- release, taking the newest one as vfox use java@21-tem does, while + --- SDKMAN's 21.0.0 still names the release 21 (21+35-tem). + local name = version .. build .. suffix return { version = installed[name] and name - or installed_version.find(installed, version, suffix) - or installed_version.newest(installed, majorVersion .. ".", "^[%d.+]*$", suffix), + or not fullVersion:match("^%d+$") and installed_version.release(installed, version, suffix) + or installed_version.major(installed, majorVersion, suffix), } end diff --git a/lib/installed_version.lua b/lib/installed_version.lua index 05d048e..74d8233 100644 --- a/lib/installed_version.lua +++ b/lib/installed_version.lua @@ -62,6 +62,26 @@ function installed_version.newest(installed, prefix, rest, suffix) return best end +--- Returns the installed version of a release given without a build number, +--- or nil: the release itself (17.0.12-graal), else its newest build +--- (21.0.8+9-tem for 21.0.8, 27+36-librca for 27), but never another release +--- (not 21.0.12.1+1 for 21.0.12, nor 21.0.12+8 for 21.0.1). +function installed_version.release(installed, release, suffix) + local name = release .. suffix + if installed[name] then + return name + end + return installed_version.newest(installed, release, "^%+%d+$", suffix) +end + +--- Returns the newest installed release of a major, or nil: any release of it +--- (21.0.12.1+1-tem for 21), the first one too (27+36-librca, 27-oracle for +--- 27), but no EA build (26-ea+13 for 26). +function installed_version.major(installed, major, suffix) + local name = major .. suffix + return installed_version.newest(installed, major, "^[.+][%d.+]*$", suffix) or installed[name] and name +end + --- Returns the installed version that version .. suffix names, or nil. The --- input may leave out the build number PreInstall found, so, like PreInstall, --- take the newest build of a release (21.0.8-tem selects 21.0.8+9-tem) and @@ -72,11 +92,10 @@ function installed_version.find(installed, version, suffix) if installed[name] then return name end - -- A bare major takes any release of it (21.0.12.1+1 for 21) but no EA build - -- (26-ea+13 for 26); other versions take only a build number (not - -- 21.0.12.1+1 for 21.0.12, nor 21.0.12+8 for 21.0.1). - local rest = version:match("^%d+$") and "^[.+][%d.+]*$" or "^%+%d+$" - return installed_version.newest(installed, version, rest, suffix) + if version:match("^%d+$") then + return installed_version.major(installed, version, suffix) + end + return installed_version.release(installed, version, suffix) end return installed_version diff --git a/tests/e2e/common/common.go b/tests/e2e/common/common.go index 26f46ca..9b2d29d 100644 --- a/tests/e2e/common/common.go +++ b/tests/e2e/common/common.go @@ -273,19 +273,39 @@ func Major(version string) string { return strings.SplitN(version, ".", 2)[0] } +// sdkmanName returns SDKMAN's name for the distribution, which calls +// JetBrains "jbr". +func sdkmanName(distribution string) string { + if name := Base(distribution); name != "jb" { + return name + } + return "jbr" +} + // LegacyIdentifier renders the SDKMAN identifier a .sdkmanrc holds for a // release: "-", without the build number, in SDKMAN's // spelling: JetBrains is "jbr" and JavaFX builds are // ".fx-" (21.0.5-jbr, 25.0.4.fx-librca). func LegacyIdentifier(distribution, version string) string { - name := Base(distribution) - if name == "jb" { - name = "jbr" - } if JavaFX(distribution) { version += ".fx" } - return version + "-" + name + return version + "-" + sdkmanName(distribution) +} + +// PaddedIdentifier renders the SDKMAN identifier of an installed version +// with a zero padding the release, as `sdk list java` shows many +// (25.0.1.0+8-open, 21.0.9.0+116386-jbr): +// ".0[-fx][+]-" with the installed build +// (21.0.8.0+9-tem, 25.0.4.0-fx+9-librca). Foojay, and so the installed +// name, leaves the zero out; ParseLegacyFile did not, so it found no such +// release and fell back to the newest one of the major (vfox-java#80). +func PaddedIdentifier(distribution, version, installed string) string { + version += ".0" + if JavaFX(distribution) { + version += "-fx" + } + return version + buildNumber.FindString(installed) + "-" + sdkmanName(distribution) } // Alias renders the asdf-style spelling "-[-fx]" of an diff --git a/tests/e2e/common/common_test.go b/tests/e2e/common/common_test.go index c4c390f..04f34b3 100644 --- a/tests/e2e/common/common_test.go +++ b/tests/e2e/common/common_test.go @@ -208,6 +208,20 @@ func TestSpecs(t *testing.T) { } } +func TestPaddedIdentifier(t *testing.T) { + for _, c := range []struct{ dist, version, installed, want string }{ + {"open", "25.0.2", "25.0.2+10", "25.0.2.0+10-open"}, + {"tem", "21.0.8", "21.0.8+9-tem", "21.0.8.0+9-tem"}, + {"librca-fx", "25.0.4", "25.0.4+9-librca-fx", "25.0.4.0-fx+9-librca"}, + {"jb", "21.0.5", "21.0.5+79248-jb", "21.0.5.0+79248-jbr"}, + {"graalce", "25.0.2", "25.0.2-graalce", "25.0.2.0-graalce"}, + } { + if got := PaddedIdentifier(c.dist, c.version, c.installed); got != c.want { + t.Errorf("PaddedIdentifier(%s, %s, %s) = %q, want %q", c.dist, c.version, c.installed, got, c.want) + } + } +} + func TestAlias(t *testing.T) { for _, c := range []struct{ dist, installed, want string }{ {"open", "25.0.2+10", "openjdk-25.0.2+10"}, diff --git a/tests/e2e/linux/verify.go b/tests/e2e/linux/verify.go index cc2f504..bb93a6d 100644 --- a/tests/e2e/linux/verify.go +++ b/tests/e2e/linux/verify.go @@ -53,10 +53,11 @@ func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, distribut // verifyLegacyFile proves ParseLegacyFile selects the installed JDK from a // .sdkmanrc (PLUGIN.legacyFilenames) holding the SDKMAN identifier without // the build number (e.g. java=21.0.8-tem, java=21.0.5-jbr, -// java=25.0.4.fx-librca) or the installed version itself (e.g. -// java=21.0.8+9-tem), while vfox lists an empty install of another release -// first (see common.Decoy). It runs before any `vfox use`, so java can only -// come from the legacy file. +// java=25.0.4.fx-librca), the one with a zero padding the release (e.g. +// java=21.0.8.0+9-tem, see common.PaddedIdentifier) or the installed version +// itself (e.g. java=21.0.8+9-tem), while vfox lists an empty install of +// another release first (see common.Decoy). It runs before any `vfox use`, +// so java can only come from the legacy file. func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, distribution, version, installed, arch string) { t.Helper() const dir = "/tmp/e2e-legacy-sdkmanrc" @@ -69,7 +70,11 @@ func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, distr listed := common.ListedVersions(execOK(ctx, t, ctr, "vfox list java")) require.Equalf(t, []string{decoy, installed}, listed, "vfox list java must list the decoy %s first", decoy) - for _, identifier := range []string{common.LegacyIdentifier(distribution, version), installed} { + for _, identifier := range []string{ + common.LegacyIdentifier(distribution, version), + common.PaddedIdentifier(distribution, version, installed), + installed, + } { execOK(ctx, t, ctr, fmt.Sprintf("mkdir --parents %s && printf 'java=%%s\\n' %s > %s/.sdkmanrc", dir, shQuote(identifier), dir)) // The probe activates inside its own exec, a fresh shell session, so no diff --git a/tests/e2e/windows/verify.go b/tests/e2e/windows/verify.go index 0509754..00394c1 100644 --- a/tests/e2e/windows/verify.go +++ b/tests/e2e/windows/verify.go @@ -55,9 +55,10 @@ func verifySearch(ctx context.Context, t *testing.T, ctr tc.Container, box slotB // verifyLegacyFile proves ParseLegacyFile selects the installed JDK from a // .sdkmanrc (PLUGIN.legacyFilenames) holding the SDKMAN identifier without // the build number (e.g. java=21.0.8-tem, java=21.0.5-jbr, -// java=25.0.4.fx-librca) or the installed version itself (e.g. -// java=21.0.8+9-tem), written by Set-Content with Windows line endings, -// while vfox lists an empty install of another release first (see +// java=25.0.4.fx-librca), the one with a zero padding the release (e.g. +// java=21.0.8.0+9-tem, see common.PaddedIdentifier) or the installed version +// itself (e.g. java=21.0.8+9-tem), written by Set-Content with Windows line +// endings, while vfox lists an empty install of another release first (see // common.Decoy). It runs before any `vfox use`, so java can only come from // the legacy file. func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, distribution, version, installed, arch string) { @@ -73,7 +74,11 @@ func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, box s listed := common.ListedVersions(execOK(ctx, t, ctr, "vfox list java", box.vars...)) require.Equalf(t, []string{decoy, installed}, listed, "vfox list java must list the decoy %s first", decoy) - for _, identifier := range []string{common.LegacyIdentifier(distribution, version), installed} { + for _, identifier := range []string{ + common.LegacyIdentifier(distribution, version), + common.PaddedIdentifier(distribution, version, installed), + installed, + } { execOK(ctx, t, ctr, fmt.Sprintf( "New-Item -ItemType Directory -Force -Path %s | Out-Null; Set-Content -Path %s -Value %s", pwshQuote(dir), pwshQuote(dir+`\.sdkmanrc`), pwshQuote("java="+identifier)), box.vars...) diff --git a/tests/parse_legacy_file_test.lua b/tests/parse_legacy_file_test.lua index 8daf8c3..9403ce0 100644 --- a/tests/parse_legacy_file_test.lua +++ b/tests/parse_legacy_file_test.lua @@ -78,6 +78,26 @@ local cases = { { "java=21.0.12-zulu\n", jb_and_zulu_fx, nil }, { "java=21.0.12.fx-zulu\n", { "21.0.12+7-zulu" }, nil }, { "java=21.0.8.fx-zulu\n", { "21.0.12+7-zulu", "21.0.12+7-zulu-fx" }, "21.0.12+7-zulu-fx" }, + -- SDKMAN pads releases with zeros that Foojay, and so the installed name, + -- leaves out (vfox-java#80). + { "java=27.0.0+36-librca\n", { "27+36-librca" }, "27+36-librca" }, + { "java=27.0.0-fx+36-librca\n", { "27+36-librca", "27+36-librca-fx" }, "27+36-librca-fx" }, + { "java=27.0.0+35-zulu\n", { "27+35-zulu" }, "27+35-zulu" }, + { "java=27.0.0.0+35-tem\n", { "27+35-tem" }, "27+35-tem" }, + { "java=27.0.0+35-open\n", { "27+35" }, "27+35" }, + { "java=26.0.0.0+35-open\n", { "26+35" }, "26+35" }, + { "java=25.0.1.0+8-open\n", { "25.0.1+8", "25.0.2+10" }, "25.0.1+8" }, + { "java=21.0.0.0-graal\n", { "21-graal", "21.0.12-graal" }, "21-graal" }, + { "java=21.0.12.0-sapmchn\n", { "21.0.12-sapmchn", "21.0.12.1-sapmchn" }, "21.0.12-sapmchn" }, + { "java=27.0.0.0+33-amzn\n", { "27-amzn" }, "27-amzn" }, + { "java=21.0.10-tem\n", { "21.0.1+12-tem", "21.0.10+7-tem" }, "21.0.10+7-tem" }, + -- A padded major names its first release, a bare major the newest one. + { "java=26.0.0.0+35-open\n", { "26+34", "26.0.2+10" }, "26+34" }, + { "java=26-open\n", { "26+34", "26.0.2+10" }, "26.0.2+10" }, + -- Without the release, the first release of the major is the newest one too. + { "java=25.0.1.0+8-open\n", { "25+36" }, "25+36" }, + { "java=26.0.1-oracle\n", { "26-oracle" }, "26-oracle" }, + { "java=26.0.2-oracle\n", { "26-oracle", "26.0.1-oracle" }, "26.0.1-oracle" }, -- No java line at all selects nothing. { "maven=3.9.9\n", { "21.0.8+9-tem" }, nil }, }