diff --git a/docs/changes.md b/docs/changes.md index d5ae316..8d5f3f4 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -13,6 +13,7 @@ What changed in each release, as a user notices it. GitHub's release pages list - Oracle GraalVM and GraalVM Community go by GraalVM's own version, which Foojay has given as `distribution_version` since October 2026. Release 0.6.2 reads `java_version`, which now holds the JDK's version. So it installs the feature release 25.4.4.1.1 for `java@25.0.4.1-graal`, and for `java@25-graal` whenever a feature release is out on the JDK of the newest 25.0 build, and `vfox search java graal` lists no feature release. Now `java@25-graal` installs the newest build of the 25.0 line (`25.0.5-graal` on 2026-10-20), `java@25.0.4.1-graal` installs GraalVM 25.0.4.1, and the feature release is `java@25.4.4.1.1-graal`. `java@25-graalce` installs `25.4.4.1.1-graalce` from Oracle's GraalVM Download Service, where 0.6.2 installed the same file from GitHub as `25.4.4.1-graalce` ([#105](https://github.com/version-fox/vfox-java/issues/105)). - A 25.0 name that release 0.6.2 installed after Foojay's change can hold a feature release: `25.0.4.1-graal` is GraalVM 25.4.4.1.1, `25.0.4-graal` is 25.2.4 and `25.0.3-graal` is 25.1.3, and the same goes for these names with `-graalce`. vfox does not install a name it already has, so if the GraalVM version that `java -version` prints differs from the name, `vfox uninstall` it and install it again. `25.0.4.1-graalce`, `25.0.4-graalce` and `25.0.3-graalce` no longer install at all, as GraalVM Community's 25.0 line ends with `25.0.2`. - A bare major such as `java@21-tem` asks Foojay what `vfox search` asks and installs the newest release of the major that the search lists. Release 0.6.2 has Foojay pick it (`latest=available`), and as Foojay caches each question on its own, for half an hour after a release that can install a build `vfox search` does not list yet. Dragonwell 11 on Windows x64 and on Linux x64 with musl now installs the Extended build of 11.0.32, as `java@11.0.32-albba` does, where 0.6.2 installs the Standard one for `java@11-albba` ([#113](https://github.com/version-fox/vfox-java/issues/113)). +- `vfox use java@25-graal` and a `.sdkmanrc` holding `java=25-graal` select the newest installed build of Oracle GraalVM's 25.0 line, the build `vfox install java@25-graal` installs. Release 0.6.2 selects the newest installed release of 25, which is a monthly feature release as soon as one is installed: with `25.0.5-graal` and `25.4.4.1.1-graal` installed, `vfox use java@25-graal` selected the second, while `vfox install java@25-graal` reported the first as installed. A feature release is selected by its full version (`java@25.4.4.1.1-graal`), or by the bare major where nothing else of 25 is installed. A `.sdkmanrc` naming a 25.0 release that is not installed, such as `java=25.0.3-graal`, falls back the same way. `java=25.4.4+1-graal`, which names a feature release as SDKMAN does, still takes the newest installed release of 25. GraalVM Community is not affected, as its feature releases are what it still ships ([#110](https://github.com/version-fox/vfox-java/issues/110)). ## 0.6.2 (2026-10-18) diff --git a/docs/development.md b/docs/development.md index c10041b..42de0d2 100644 --- a/docs/development.md +++ b/docs/development.md @@ -44,7 +44,7 @@ A few contracts that cost time to find: ## Names -Installed names are `[-][-fx]`, with no short name for OpenJDK, Foojay's `distribution_version` in place of `java_version` for Mandrel and Liberica NIK, and GraalVM's own version for `graal` and `graalce` ([versions.md](versions.md#installed-names)). `lib/distribution_version.lua` reads a spec into a version, a distribution and the JavaFX flag, in either order (`21-tem`, `tem-21`). `lib/installed_version.lua` turns a version the user typed into an installed name: the name itself first. A bare major then takes the newest installed release of it, and any other version takes the release itself or its newest build. `PreUse` stops there. `ParseLegacyFile` goes one step further and falls back to the newest installed release of the major. +Installed names are `[-][-fx]`, with no short name for OpenJDK, Foojay's `distribution_version` in place of `java_version` for Mandrel and Liberica NIK, and GraalVM's own version for `graal` and `graalce` ([versions.md](versions.md#installed-names)). `lib/distribution_version.lua` reads a spec into a version, a distribution and the JavaFX flag, in either order (`21-tem`, `tem-21`). `lib/installed_version.lua` turns a version the user typed into an installed name: the name itself first. A bare major then takes the newest installed release of it, and any other version takes the release itself or its newest build. `PreUse` stops there. `ParseLegacyFile` goes one step further and falls back to the newest installed release of the major. Both leave out Oracle GraalVM's feature releases when they take the newest release of a major, unless nothing else of it is installed (`bare_major_skips`, see below). SDKMAN spells some things differently (`jbr`, `21.0.12.fx-zulu`, `21.0.12-fx+1.1-librca`, `28.0.0+ea.19-open`), and `plugin_spelling` in `hooks/parse_legacy_file.lua` rewrites them into the plugin's before parsing. For the distributions SDKMAN no longer lists, `sdkman_flavours` there turns the virtual machine or JDK in SDKMAN's version into the short name (`22.3.3.r17-grl` is `22.3.3-grl17`). For Mandrel and Liberica NIK (`sdkman_jdks`) the JDK is dropped (`25.0.4.r25-nik` is `25.0.4-nik`). An identifier with the older JDK of a release that comes for two, which `older_jdk` returns, selects nothing (`22.1.r11-mandrel`). `installed_version.unpad` drops the zeros SDKMAN pads releases with (`27.0.0+36` is `27+36`). Where the short names differ from SDKMAN's is in [distributions.md](distributions.md#short-names-and-sdkman). @@ -59,7 +59,7 @@ SDKMAN spells some things differently (`jbr`, `21.0.12.fx-zulu`, `21.0.12-fx+1.1 - `fixes` in `lib/foojay.lua` drops packages before the hooks see them: the JREs, installer bundles and RHEL 6 builds that Foojay lists among OJDKBuild's Windows JDKs, and the JDK 11 builds of Gluon GraalVM's 22.x releases, which share their version with the JDK 17 ones. A bare major picks its release from what they keep. - `Available` lists a name once where Foojay has two records for it. - Foojay marks GraalVM Community's monthly feature releases (`25.1.3` and on) as LTS where it lists them the old way round, as it did Oracle GraalVM's. `is_feature_release` in `lib/distribution_version.lua` recognises them, by the minor number, and `Available` drops the note. It is for `graal` and `graalce` only: Mandrel and Liberica NIK carry GraalVM's older numbers up to 24, whose minor is no feature release (`mandrel` `23.1.12` is LTS), and the JDK's from 25 on. -- For a bare major of Oracle GraalVM (`graal`) only, `PreInstall` skips the feature releases, so the 25.0 line is installed. A major that has nothing but feature releases keeps them. GraalVM Community is not filtered, because its feature releases are what it still ships. +- For a bare major of Oracle GraalVM (`graal`) only, `PreInstall` skips the feature releases, so the 25.0 line is installed. A major that has nothing but feature releases keeps them. GraalVM Community is not filtered, because its feature releases are what it still ships. `bare_major_skips` in `lib/distribution_version.lua` holds that rule. `PreUse` and `ParseLegacyFile` apply it to the installed versions as well, so that `vfox use` and a `.sdkmanrc` select the build `PreInstall` installs: the newest installed release of the major that is no feature release, else the newest feature release. `ParseLegacyFile` applies it to the fallback for a release that is not installed too, except when the file names a feature release (`25.4.4+1-graal`, as SDKMAN spells one), which takes the newest release of the major as before. - The CPU names are `arm64` to `aarch64`, and on Windows `amd64` to `x64`. `darwin` becomes `macos`. On Linux `ldd --version` decides between glibc and musl. ## Tests diff --git a/docs/distributions.md b/docs/distributions.md index 2479434..4d0ad96 100644 --- a/docs/distributions.md +++ b/docs/distributions.md @@ -170,7 +170,7 @@ GraalVM CE 8 to 19, Gluon GraalVM before 23, and Mandrel and Liberica NIK before - Releases are named after GraalVM's own version, which Foojay gives as `distribution_version`. Foojay's `java_version` is the JDK's version, which a feature release shares with the 25.0 line: `25.4.4.1.1-graal`, `25.3.4.1-graal`, `25.0.4.1.1-graal` and `25.0.4.1-graal` all have the `java_version` 25.0.4.1. - Foojay lists GraalVM Community's feature releases under GraalVM's version too, as it did before October 2026, with downloads from GitHub. So `java@25.4.4.1-graalce`, the name release 0.6.2 gives the 25.4.4.1.1 build, still installs it from GitHub, while `java@25.4.4.1.1-graalce` gets the same file from Oracle's GraalVM Download Service. - Unreleased: release 0.6.2 names releases after `java_version`, so `java@25.0.4.1-graal` installs the feature release 25.4.4.1.1, `java@25-graal` installs a feature release whenever one is out on the JDK of the newest 25.0 build, and `vfox search java graal` lists no feature release. -- `java@25-graal` installs the newest build of the 25.0 line (`25.0.5-graal` on 2026-10-20), the line Oracle's own `25/latest` download follows. A feature release needs its full version: `java@25.4.4.1.1-graal`. +- `java@25-graal` installs the newest build of the 25.0 line (`25.0.5-graal` on 2026-10-20), the line Oracle's own `25/latest` download follows. A feature release needs its full version: `java@25.4.4.1.1-graal`. `vfox use java@25-graal` and `java=25-graal` in a `.sdkmanrc` select the newest installed build of that line too, and a feature release only where nothing else of 25 is installed. Unreleased: release 0.6.2 selects the newest installed release of 25, which is a feature release as soon as one is installed. - GraalVM Community's 25.0 line ended with `25.0.2`, and the feature releases are all it still ships. `java@25-graalce` installs the newest of them, `25.4.4.1.1-graalce` on 2026-10-20. - The plugin sets `JAVA_HOME` and `PATH`, and no `GRAALVM_HOME`. A `GRAALVM_HOME` that points at a GraalVM of your own stays as it is, and the native-image build plugins fall back to `JAVA_HOME` when it is unset. - There is no Windows ARM64 and no Alpine build, and Intel Macs get nothing after 25.0.1. See [platforms.md](platforms.md#graalvm). diff --git a/docs/e2e.md b/docs/e2e.md index 8c1af20..4a406f4 100644 --- a/docs/e2e.md +++ b/docs/e2e.md @@ -95,6 +95,15 @@ JDK, so `java` can only come from what vfox installs: GA 25 is left out, as vfox would find `25-graal` itself installed without asking `PreInstall`. On Linux the install must also ask Foojay what the search asked, as at the end of item 4. + + With those builds installed, `vfox use java@25-graal` and a `.sdkmanrc` + holding `java=25-graal` must select the build `vfox install` names. + `PreUse` and `ParseLegacyFile` took the newest installed release of 25, + which is a feature release as soon as one is installed + ([#110](https://github.com/version-fox/vfox-java/issues/110)). Each faked + build holds a stand-in `java` that only prints its name, as in item 6. + Once the 25.0 line is removed, `vfox use` and the `.sdkmanrc` must both + select the newest feature release. 4. On the Temurin rows, which run on every runner, the distributions the suite checks by name only (`adpt` to `semcrt`, `nameOnly` in `tests/e2e/common`) go by their short names where Foojay has packages of diff --git a/docs/sdkmanrc.md b/docs/sdkmanrc.md index 151d620..b8f76bf 100644 --- a/docs/sdkmanrc.md +++ b/docs/sdkmanrc.md @@ -40,6 +40,8 @@ The plugin matches the identifier against the JDKs that are installed, in this o Step 3 does not check that the release is at least the one named. With only `21.0.8+9-tem` installed, `java=21.0.12-tem` selects it, and so does `java=21.0.3-tem`. A build number that is not installed falls back to the newest installed build of the release (`21.0.8+7-tem` selects `21.0.8+9-tem`). If the major is not installed at all, nothing is selected. +For Oracle GraalVM, step 3 leaves out the monthly feature releases (`25.4.4.1.1-graal`), as `vfox use java@25-graal` does, so `java=25-graal` and `java=25.0.3-graal` select the newest installed build of the 25.0 line. If nothing else of 25 is installed, a feature release is taken. If the file names a feature release (SDKMAN spells one `25.4.4+1-graal`), the newest installed release of 25 is taken, as before. Unreleased: release 0.6.2 takes the newest installed release of 25 for `java=25-graal` and `java=25.0.3-graal` too, which is a feature release as soon as one is installed. + It never crosses to another distribution, between a JavaFX build and a plain one, or between an early-access build and a release. ## SDKMAN's spellings diff --git a/docs/versions.md b/docs/versions.md index 3e0ebb6..af837a0 100644 --- a/docs/versions.md +++ b/docs/versions.md @@ -76,6 +76,8 @@ vfox uninstall java@21.0.8+9-tem If you name a build or a release, it does not cross over to another one (`21.0.12-tem` does not select `21.0.12.1+1-tem`). It does not cross to another distribution, between JavaFX and plain, or between early-access and GA either. A name that matches nothing is reported as `java@99-tem is not installed`. +With Oracle GraalVM, `vfox use` of a bare major selects the build that installing it installs: `25-graal` selects the newest installed build of the 25.0 line (`25.0.5-graal`), not a monthly feature release such as `25.4.4.1.1-graal`, which it takes only where nothing else of 25 is installed. A feature release is selected by its full version. If `25-graal` itself is installed, the GA release 25 that release 0.6.1 installed, that one is selected, as for any installed name. Unreleased: release 0.6.2 selects the newest installed release of 25, which is a feature release as soon as one is installed. + vfox's own match for `21` goes by the version alone, so it also takes GraalVM CE 8 to 19, Gluon GraalVM, Mandrel and Liberica NIK, whose versions are GraalVM's: `21.3.1-grl11` runs Java 11, and `vfox use java@21` selects it over `21.0.12.1+1-tem`. See [distributions.md](distributions.md#versions-numbered-after-graalvm). Pick the scope with `vfox use`'s flags: `--global`, `--project` or `--session`, the default. In a shell without vfox's hook, Windows switches to the global scope with a warning, while Linux and macOS stop with `vfox requires hook support`. `--project` writes a `.vfox.toml` that holds the installed name (`java = "17.0.10+7-tem"`). A `.sdkmanrc` is read as well, see [sdkmanrc.md](sdkmanrc.md). diff --git a/docs/zh-CN/changes.md b/docs/zh-CN/changes.md index 1b16dd2..c862e2f 100644 --- a/docs/zh-CN/changes.md +++ b/docs/zh-CN/changes.md @@ -13,6 +13,7 @@ - Oracle GraalVM 和 GraalVM 社区版改按 GraalVM 自己的版本号命名和查找。从 2026 年 10 月起,Foojay 把这个版本号放在 `distribution_version` 里。0.6.2 仍然读 `java_version`,而它现在是 JDK 的版本号,所以 `java@25.0.4.1-graal` 装上的是特性版本 25.4.4.1.1;只要 25.0 系列最新构建所用的 JDK 上已经出了特性版本,`java@25-graal` 也会装上特性版本;`vfox search java graal` 里也一个特性版本都没有。现在 `java@25-graal` 装的是 25.0 系列的最新构建(2026-10-20 是 `25.0.5-graal`),`java@25.0.4.1-graal` 装的就是 GraalVM 25.0.4.1,那个特性版本要写成 `java@25.4.4.1.1-graal`。`java@25-graalce` 装的是 `25.4.4.1.1-graalce`,从 Oracle 的 GraalVM Download Service 下载;0.6.2 从 GitHub 下载同一个文件,起名 `25.4.4.1-graalce`([#105](https://github.com/version-fox/vfox-java/issues/105))。 - 在 Foojay 改格式之后用 0.6.2 装的 25.0 版本名,里面可能是特性版本:`25.0.4.1-graal` 是 GraalVM 25.4.4.1.1,`25.0.4-graal` 是 25.2.4,`25.0.3-graal` 是 25.1.3,这几个名字换成 `-graalce` 也一样。名字已经装过的,vfox 不会重装,所以如果 `java -version` 打印的 GraalVM 版本和名字对不上,先 `vfox uninstall` 再重新安装。`25.0.4.1-graalce`、`25.0.4-graalce` 和 `25.0.3-graalce` 现在根本装不上了,因为 GraalVM 社区版的 25.0 系列到 `25.0.2` 为止。 - 只写大版本号(如 `java@21-tem`)时,向 Foojay 发的请求和 `vfox search` 相同,装的是搜索列出的这个大版本里最新的版本。0.6.2 让 Foojay 来挑(`latest=available`),而 Foojay 按请求分别缓存结果,新版本发布后的半小时里,可能装上 `vfox search` 还没列出的构建。Windows x64 和 musl 的 Linux x64 上,Dragonwell 11 现在装 11.0.32 的 Extended 构建,和 `java@11.0.32-albba` 一样;0.6.2 的 `java@11-albba` 装的是 Standard 构建([#113](https://github.com/version-fox/vfox-java/issues/113))。 +- `vfox use java@25-graal` 和写着 `java=25-graal` 的 `.sdkmanrc`,选中的是 Oracle GraalVM 25.0 系列里已安装的最新构建,也就是 `vfox install java@25-graal` 装的那个。0.6.2 选的是 25 里已安装的最新版本,所以一旦装了特性版本,选中的就是它:装了 `25.0.5-graal` 和 `25.4.4.1.1-graal` 时,`vfox use java@25-graal` 选中后者,而 `vfox install java@25-graal` 报告前者已经安装。现在特性版本要写全版本号才会被选中(`java@25.4.4.1.1-graal`),只写大版本号时,只有 25 里没有装别的版本才会选它。`.sdkmanrc` 里写了没装的 25.0 版本(例如 `java=25.0.3-graal`),退到同一大版本时也这样挑。`java=25.4.4+1-graal` 是 SDKMAN 指明特性版本的写法,仍然选 25 里已安装的最新版本。GraalVM 社区版不受影响,因为特性版本就是它现在发布的全部([#110](https://github.com/version-fox/vfox-java/issues/110))。 ## 0.6.2(2026-10-18) diff --git a/docs/zh-CN/development.md b/docs/zh-CN/development.md index 3598c29..e52d1e1 100644 --- a/docs/zh-CN/development.md +++ b/docs/zh-CN/development.md @@ -44,7 +44,7 @@ vfox 加载 `metadata.lua`,命令需要时调用相应的钩子,并向钩子 ## 命名 -安装后的版本名是 `[-<简称>][-fx]`,OpenJDK 没有简称,Mandrel 和 Liberica NIK 用 Foojay 的 `distribution_version` 代替 `java_version`,`graal` 和 `graalce` 用 GraalVM 自己的版本号([versions.md](versions.md#安装后的版本名))。`lib/distribution_version.lua` 把一个写法解析成版本、发行版和 JavaFX 标志,两种顺序都接受(`21-tem`、`tem-21`)。`lib/installed_version.lua` 把用户输入的版本变成已安装的版本名:先看这个名字本身。只写大版本号时,取这个大版本里已安装的最新版本;其他写法则取这个版本本身,或它的最新构建。`PreUse` 到此为止。`ParseLegacyFile` 还会再退一步,取同一大版本里已安装的最新版本。 +安装后的版本名是 `[-<简称>][-fx]`,OpenJDK 没有简称,Mandrel 和 Liberica NIK 用 Foojay 的 `distribution_version` 代替 `java_version`,`graal` 和 `graalce` 用 GraalVM 自己的版本号([versions.md](versions.md#安装后的版本名))。`lib/distribution_version.lua` 把一个写法解析成版本、发行版和 JavaFX 标志,两种顺序都接受(`21-tem`、`tem-21`)。`lib/installed_version.lua` 把用户输入的版本变成已安装的版本名:先看这个名字本身。只写大版本号时,取这个大版本里已安装的最新版本;其他写法则取这个版本本身,或它的最新构建。`PreUse` 到此为止。`ParseLegacyFile` 还会再退一步,取同一大版本里已安装的最新版本。这两处取一个大版本里的最新版本时,都会跳过 Oracle GraalVM 的特性版本,除非这个大版本里没有装别的版本(`bare_major_skips`,见下文)。 SDKMAN 有些写法不同(`jbr`、`21.0.12.fx-zulu`、`21.0.12-fx+1.1-librca`、`28.0.0+ea.19-open`),`hooks/parse_legacy_file.lua` 里的 `plugin_spelling` 会在解析之前把它们改写成插件自己的写法。对 SDKMAN 已经不再提供的发行版,同一文件里的 `sdkman_flavours` 把 SDKMAN 写在版本号里的虚拟机或 JDK 版本换成简称(`22.3.3.r17-grl` 就是 `22.3.3-grl17`)。对 `sdkman_jdks` 里的 Mandrel 和 Liberica NIK,则是去掉 JDK 版本(`25.0.4.r25-nik` 就是 `25.0.4-nik`)。一个版本同时为两个 JDK 发布时,写了较旧 JDK(由 `older_jdk` 给出)的标识符什么都不选中(`22.1.r11-mandrel`)。SDKMAN 给版本补的零由 `installed_version.unpad` 去掉(`27.0.0+36` 就是 `27+36`)。简称与 SDKMAN 不同的地方见 [distributions.md](distributions.md#简称与-sdkman-的不同)。 @@ -59,7 +59,7 @@ SDKMAN 有些写法不同(`jbr`、`21.0.12.fx-zulu`、`21.0.12-fx+1.1-librca` - `lib/foojay.lua` 里的 `fixes` 会在钩子拿到结果之前去掉一些包:Foojay 列在 OJDKBuild 的 Windows JDK 里的 JRE、installer bundle 包和 RHEL 6 构建,以及 Gluon GraalVM 22.x 的 JDK 11 构建,它们和 JDK 17 构建共用一个版本号。只写大版本号时,从去掉之后剩下的包里挑。 - Foojay 里同一个名字有两条记录时,`Available` 只列一次。 - GraalVM 社区版每月发布的特性版本(`25.1.3` 起),Foojay 按老办法列的那些标成了 LTS,以前 Oracle GraalVM 的也是这样。`lib/distribution_version.lua` 里的 `is_feature_release` 按次版本号识别它们,`Available` 去掉它们的备注。它只适用于 `graal` 和 `graalce`:Mandrel 和 Liberica NIK 在 24 及以前沿用 GraalVM 的旧版本号,次版本号并不表示特性版本(`mandrel` 的 `23.1.12` 就是 LTS),从 25 起和 JDK 的版本号一致。 -- 只有 Oracle GraalVM(`graal`)在只写大版本号时,`PreInstall` 才会跳过特性版本,所以装的是 25.0 系列。一个大版本里只有特性版本时,则保留它们。GraalVM 社区版不做这个过滤,因为特性版本就是它现在发布的全部。 +- 只有 Oracle GraalVM(`graal`)在只写大版本号时,`PreInstall` 才会跳过特性版本,所以装的是 25.0 系列。一个大版本里只有特性版本时,则保留它们。GraalVM 社区版不做这个过滤,因为特性版本就是它现在发布的全部。这条规则放在 `lib/distribution_version.lua` 的 `bare_major_skips` 里。`PreUse` 和 `ParseLegacyFile` 也用它过滤已安装的版本,这样 `vfox use` 和 `.sdkmanrc` 选中的就是 `PreInstall` 装的那个构建:这个大版本里已安装的、不是特性版本的最新版本,没有的话才取最新的特性版本。没装的版本在 `ParseLegacyFile` 里退到同一大版本的最新版本时,也适用这条规则,除非文件里写的本来就是特性版本(SDKMAN 写作 `25.4.4+1-graal`),这时仍然像以前一样取这个大版本里已安装的最新版本。 - CPU 名字:`arm64` 改成 `aarch64`,Windows 上 `amd64` 改成 `x64`;`darwin` 改成 `macos`。在 Linux 上,由 `ldd --version` 决定是 glibc 还是 musl。 ## 测试 diff --git a/docs/zh-CN/distributions.md b/docs/zh-CN/distributions.md index 8264f72..79c7802 100644 --- a/docs/zh-CN/distributions.md +++ b/docs/zh-CN/distributions.md @@ -170,7 +170,7 @@ GraalVM CE 8 到 19、23 以前的 Gluon GraalVM,以及 25 以前的 Mandrel - 版本名用的是 GraalVM 自己的版本号,也就是 Foojay 的 `distribution_version`。Foojay 的 `java_version` 是 JDK 的版本号,特性版本会和 25.0 系列的某个构建共用同一个值:`25.4.4.1.1-graal`、`25.3.4.1-graal`、`25.0.4.1.1-graal` 和 `25.0.4.1-graal` 的 `java_version` 都是 25.0.4.1。 - GraalVM 社区版的特性版本,Foojay 还像 2026 年 10 月以前那样,按 GraalVM 的版本号另列了一份,下载地址在 GitHub。所以 0.6.2 给 25.4.4.1.1 构建起的名字 `java@25.4.4.1-graalce` 仍然能从 GitHub 装上这个构建,而 `java@25.4.4.1.1-graalce` 是从 Oracle 的 GraalVM Download Service 下载同一个文件。 - 未发布:0.6.2 按 `java_version` 命名,所以 `java@25.0.4.1-graal` 装上的是特性版本 25.4.4.1.1;只要 25.0 系列最新构建所用的 JDK 上已经出了特性版本,`java@25-graal` 装上的也是特性版本;`vfox search java graal` 里也没有特性版本。 -- `java@25-graal` 装的是 25.0 系列的最新构建(2026-10-20 是 `25.0.5-graal`),Oracle 官方的 `25/latest` 下载链接跟的也是这个系列。特性版本要写全版本号:`java@25.4.4.1.1-graal`。 +- `java@25-graal` 装的是 25.0 系列的最新构建(2026-10-20 是 `25.0.5-graal`),Oracle 官方的 `25/latest` 下载链接跟的也是这个系列。特性版本要写全版本号:`java@25.4.4.1.1-graal`。`vfox use java@25-graal` 和 `.sdkmanrc` 里的 `java=25-graal` 选中的也是这个系列里已安装的最新构建,只有 25 里没有装别的版本时才会选特性版本。未发布:0.6.2 选中的是 25 里已安装的最新版本,所以一旦装了特性版本,选中的就是它。 - GraalVM 社区版的 25.0 系列到 `25.0.2` 为止,现在只发布特性版本。`java@25-graalce` 装其中最新的一个,截至 2026-10-20 是 `25.4.4.1.1-graalce`。 - 插件只设置 `JAVA_HOME` 和 `PATH`,不设置 `GRAALVM_HOME`。你设置的、指向自己的 GraalVM 的 `GRAALVM_HOME` 保持原样;没有设置时,native-image 的构建插件会回退到 `JAVA_HOME`。 - 没有 Windows ARM64 和 Alpine 的构建,Intel Mac 上 25.0.1 之后的版本也没有。见 [platforms.md](platforms.md#graalvm)。 diff --git a/docs/zh-CN/e2e.md b/docs/zh-CN/e2e.md index f477df1..dbf5e77 100644 --- a/docs/zh-CN/e2e.md +++ b/docs/zh-CN/e2e.md @@ -33,6 +33,8 @@ Windows 容器的覆盖(`tests/e2e/windows`)无法在开发容器里运行 1. 从指定的渠道安装 vfox,要么是最新发布版(`latest`),要么从源码构建(`main`),然后从当前检出的代码注册本地的 `java` 插件。在 Windows 上,runner 会在 `latest` 组合运行期间把 `main` 构建一次,`main` 的容器由 e2e 镜像加上这个 `vfox.exe` 启动。接着,`vfox info java` 打印的 Notes 要链接到 README,README 里列了各个简称,也写了怎样搜索某个发行版。以前的 Notes 只给了五个简称,没说怎样列出它们的版本([vfox#390](https://github.com/version-fox/vfox/issues/390))。 2. 只写发行版不写版本,无论单独写还是跟在空版本后面(`java@tem`、`java@-tem`),`vfox install` 和 `vfox use` 都应失败,并提示补上版本。以前,`java@tem` 会让钩子报 Lua 错误;`java@-tem` 则让 `PreInstall` 把空版本发给 Foojay,Foojay 列出所有版本,结果装上了最新的。没有任何发行版发布过的版本(`<大版本>.0.99`)应该安装失败,并且 `PreInstall` 要指出它搜索的平台(`linux/amd64`、`windows/arm64` 等),以此确认向 Foojay 查询的就是 runner 所在的平台。已固定版本里从未发布过的构建(`<版本>+99`,例如 `21.0.8+99-tem`)也一样,这证明查询保留了构建号:Foojay 会把没编码的 `+` 当成空格并丢掉构建号,于是会装上这个版本的最新构建。 3. 在 `graalce` 这一行里,Oracle GraalVM 只写大版本号的 `25-graal` 应该装 25.0 系列的最新构建(2026-10-20 是 `25.0.5-graal`),也就是 Oracle 官方 `25/latest` 下载跟的那个系列,而不是列在它前面的某个每月特性版本(比如 `25.4.4.1.1-graal`)。`vfox search java graal` 必须按 GraalVM 自己的版本号列出这些特性版本,25 的其他版本备注要一致,同第 5 条。从 2026 年 10 月起,Foojay 给特性版本标的是 JDK 的版本号,和 25.0 系列共用。插件以前按这个版本号命名,结果搜索里一个特性版本都没有,`25.0.4.1-graal` 也没有 LTS 备注,装上的却是 25.4.4.1.1。搜索列出的 25 的各构建会先被模拟成已安装,这样 `vfox install` 报出的就是 `PreInstall` 选中的那个,而且不会下载任何东西。GA 版本 25 不放进去,因为那样 vfox 会发现 `25-graal` 已安装,根本不会去问 `PreInstall`。在 Linux 上,安装向 Foojay 发的请求还必须和搜索的相同,同第 4 条末尾。 + + 装着这些构建时,`vfox use java@25-graal` 和写着 `java=25-graal` 的 `.sdkmanrc` 必须选中 `vfox install` 报出的那个构建。`PreUse` 和 `ParseLegacyFile` 以前选的是 25 里已安装的最新版本,所以一旦装了特性版本,选中的就是它([#110](https://github.com/version-fox/vfox-java/issues/110))。模拟出的每个构建里都放着一个只打印名字的替身 `java`,同第 6 条。把 25.0 系列的构建删掉之后,`vfox use` 和 `.sdkmanrc` 都必须选中最新的特性版本。 4. 在 `tem` 这几行里(这几行在每个 runner 上都会运行),只按名字检查的发行版(`adpt` 到 `semcrt`,即 `tests/e2e/common` 里的 `nameOnly`),只要 Foojay 在这个 runner 上有它的包,都要能用简称找到:`vfox search java <简称>` 能列出它;把它列出的最新版本模拟成已安装后,`vfox install java@<大版本>-<简称>`(`22-grl17`)要报告这个版本已经安装,所以不会下载任何东西。以前没有这些简称时,这两条命令分别报 `Unsupported distribution` 和 `Could not extract a valid distribution`。 同样在这几行里,Mandrel 和 Liberica NIK 要按它们沿用的 GraalVM 版本号来(`tests/e2e/common` 里的 `ownNumbers`;Windows arm64 上两者都没有):`vfox search java mandrel` 要列出 `23.1.12-mandrel`(Liberica NIK 是 `23.1.8-nik`);把它和搜索结果里最新的 23.x 模拟成已安装后,`vfox install java@mandrel-23.1.12` 和 `vfox install java@23-mandrel` 要报告这两个版本已经安装。2026 年 10 月 Foojay 把这个版本号挪到了 `distribution_version`,插件改读它之前,搜索结果每个 Java 大版本只列一个名字,找不到 `23.1.12-mandrel`,`java@23-mandrel` 装上的是为 Java 23 构建的 Mandrel 24.1.2。 diff --git a/docs/zh-CN/sdkmanrc.md b/docs/zh-CN/sdkmanrc.md index 47c91f0..f43b518 100644 --- a/docs/zh-CN/sdkmanrc.md +++ b/docs/zh-CN/sdkmanrc.md @@ -40,6 +40,8 @@ vfox 挂载到 Shell 之后,进入目录时会读取这个文件,并选中 第 3 步不检查已装的版本是否低于你写的版本。只装了 `21.0.8+9-tem` 时,`java=21.0.12-tem` 会选中它,`java=21.0.3-tem` 也一样。写了没装的构建号,会退到这个版本已安装的最新构建(`21.0.8+7-tem` 选中 `21.0.8+9-tem`)。如果这个大版本根本没装,就什么也不选。 +对 Oracle GraalVM,第 3 步会跳过每月发布的特性版本(`25.4.4.1.1-graal`),和 `vfox use java@25-graal` 一样,所以 `java=25-graal` 和 `java=25.0.3-graal` 选中的是 25.0 系列里已安装的最新构建。如果 25 里没有装别的版本,就选特性版本。如果文件里写的是特性版本(SDKMAN 写作 `25.4.4+1-graal`),仍然选 25 里已安装的最新版本,和以前一样。未发布:0.6.2 对 `java=25-graal` 和 `java=25.0.3-graal` 选中的也是 25 里已安装的最新版本,所以一旦装了特性版本,选中的就是它。 + 它不会跨发行版,不会在带 JavaFX 和不带 JavaFX 的构建之间串用,也不会在 EA 构建和 GA 版本之间串用。 ## SDKMAN 的写法 diff --git a/docs/zh-CN/versions.md b/docs/zh-CN/versions.md index ec9089f..002bef8 100644 --- a/docs/zh-CN/versions.md +++ b/docs/zh-CN/versions.md @@ -76,6 +76,8 @@ vfox uninstall java@21.0.8+9-tem 写明了构建或版本时,不会跳到别的构建或版本(`21.0.12-tem` 不会选中 `21.0.12.1+1-tem`)。也不会跨发行版,不会在带 JavaFX 和不带 JavaFX 的构建之间串用,不会在 EA 和 GA 之间串用。什么都匹配不上时,报 `java@99-tem is not installed`。 +Oracle GraalVM 只写大版本号时,`vfox use` 选的和 `vfox install` 装的是同一个构建:`vfox use java@25-graal` 选中 25.0 系列里已安装的最新构建(`25.0.5-graal`),而不是每月发布的特性版本(比如 `25.4.4.1.1-graal`);只有 25 里没有装别的版本时,才会选特性版本。特性版本要写全版本号。如果 `25-graal` 本身已经安装(0.6.1 装的 GA 版本 25),选中的就是它,和任何已安装的版本名一样。未发布:0.6.2 选中的是 25 里已安装的最新版本,所以一旦装了特性版本,选中的就是它。 + vfox 自己匹配 `21` 时只看版本号,所以 GraalVM CE 8 到 19、Gluon GraalVM、Mandrel 和 Liberica NIK 也会被它选中,它们的版本号是 GraalVM 的:`21.3.1-grl11` 运行的是 Java 11,`vfox use java@21` 却会选中它,而不是 `21.0.12.1+1-tem`。见 [distributions.md](distributions.md#按-graalvm-编号的版本)。 作用域用 `vfox use` 的参数选择:`--global`、`--project` 或 `--session`,默认是会话作用域。如果 Shell 没有挂载 vfox,Windows 会警告并切到全局作用域,Linux 和 macOS 则报 `vfox requires hook support` 后退出。`--project` 会写出 `.vfox.toml`,里面记录的是安装后的版本名(`java = "17.0.10+7-tem"`)。`.sdkmanrc` 也会被读取,见 [sdkmanrc.md](sdkmanrc.md)。 diff --git a/hooks/parse_legacy_file.lua b/hooks/parse_legacy_file.lua index 5788a56..415981d 100644 --- a/hooks/parse_legacy_file.lua +++ b/hooks/parse_legacy_file.lua @@ -163,11 +163,19 @@ function PLUGIN:ParseLegacyFile(ctx) --- (21.0.12+1.1-tem), else the newest installed release of the major --- (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). + --- SDKMAN's 21.0.0 still names the release 21 (21+35-tem). Where installing + --- a bare major skips releases (Oracle GraalVM's feature releases), so does + --- this step: java=25-graal and java=25.0.3-graal select 25.0.5-graal, + --- unless nothing else of the major is installed or the file names a + --- feature release itself (java=25.4.4+1-graal). + local skip = distribution_version_parser.bare_major_skips(distributionVersion.distribution) + if skip and skip(fullVersion) then + skip = nil + end local name = version .. build .. suffix return { version = installed[name] and name or not fullVersion:match("^%d+$") and installed_version.release(installed, version, suffix) - or installed_version.major(installed, majorVersion, suffix), + or installed_version.major(installed, majorVersion, suffix, skip), } end diff --git a/hooks/pre_install.lua b/hooks/pre_install.lua index b76b661..1b06db2 100644 --- a/hooks/pre_install.lua +++ b/hooks/pre_install.lua @@ -74,11 +74,13 @@ function PLUGIN:PreInstall(ctx) -- For Oracle GraalVM, the monthly feature releases (25.4.4.1.1) come -- before the 25.0 line, which Oracle's own 25/latest download tracks; a - -- bare major takes the newest 25.0 build instead. - if distribution_version.distribution.name == "graalvm" and distribution_version.version:match("^%d+$") then + -- bare major takes the newest 25.0 build instead. PreUse and + -- ParseLegacyFile skip the same releases among the installed ones. + local skip = distribution_version_parser.bare_major_skips(distribution_version.distribution) + if skip and distribution_version.version:match("^%d+$") then local releases = {} for _, candidate in ipairs(filtered_jdks) do - if not distribution_version_parser.is_feature_release(candidate.java_version) then + if not skip(candidate.java_version) then table.insert(releases, candidate) end end diff --git a/hooks/pre_use.lua b/hooks/pre_use.lua index 565aa00..1188255 100644 --- a/hooks/pre_use.lua +++ b/hooks/pre_use.lua @@ -40,9 +40,13 @@ function PLUGIN:PreUse(ctx) end -- vfox uses the returned version as is, so return the installed one it -- names or, without a match, the name for vfox to report as not installed. + -- A bare major leaves out the releases that installing leaves out + -- (Oracle GraalVM's feature releases), unless they are all there is. local name = distribution_version.version .. suffix + local skip = distribution_version_parser.bare_major_skips(distribution_version.distribution) return { - version = installed_version.find(ctx.installedSdks or {}, distribution_version.version, suffix) or name, + version = installed_version.find(ctx.installedSdks or {}, distribution_version.version, suffix, skip) + or name, } else return { diff --git a/lib/distribution_version.lua b/lib/distribution_version.lua index 7453642..c667481 100644 --- a/lib/distribution_version.lua +++ b/lib/distribution_version.lua @@ -120,6 +120,23 @@ function distribution_version.is_feature_release(version) return version:match("^%d+%.[1-9]") ~= nil end +--- Returns what the bare major of a distribution (25-graal) skips, as a +--- function taking a Java version, or nil where it skips nothing. Oracle +--- GraalVM's skips the monthly feature releases (25.4.4.1.1), so it takes the +--- newest build of the 25.0 line, which Oracle's own 25/latest download +--- follows. GraalVM Community's skips none: its 25.0 line ended with 25.0.2, +--- and the feature releases are what it still ships. Installing, selecting +--- with vfox use and reading a .sdkmanrc all go by this, so that they name the +--- same build. +--- @param distribution table The distribution of parse_version +--- @return (fun(version: string): boolean)|nil +function distribution_version.bare_major_skips(distribution) + if distribution.name == "graalvm" then + return distribution_version.is_feature_release + end + return nil +end + function distribution_version.parse_version(arg) local version_parts = strings.split(arg, "-") local version diff --git a/lib/installed_version.lua b/lib/installed_version.lua index e54d93d..aecffbd 100644 --- a/lib/installed_version.lua +++ b/lib/installed_version.lua @@ -45,8 +45,9 @@ function installed_version.newer(a, b) end --- Returns the newest installed version ending in suffix whose Java version is ---- prefix followed by a rest that the pattern rest matches, or nil. -function installed_version.newest(installed, prefix, rest, suffix) +--- prefix followed by a rest that the pattern rest matches, or nil. A version +--- does not count if the function skip, if given, is true for its Java version. +function installed_version.newest(installed, prefix, rest, suffix, skip) local best, best_version for candidate in pairs(installed) do local java_version = candidate:sub(1, #candidate - #suffix) @@ -54,6 +55,7 @@ function installed_version.newest(installed, prefix, rest, suffix) java_version .. suffix == candidate and java_version:sub(1, #prefix) == prefix and java_version:sub(#prefix + 1):find(rest) + and not (skip and skip(java_version)) and (not best or installed_version.newer(java_version, best_version)) then best, best_version = candidate, java_version @@ -76,10 +78,16 @@ 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) +--- 27), but no EA build (26-ea+13 for 26). The releases for which the function +--- skip, if given, is true come last: they are taken only where no other +--- release of the major is installed (25.0.5-graal for 25, and 25.4.4.1.1-graal +--- only without it). +function installed_version.major(installed, major, suffix, skip) local name = major .. suffix - return installed_version.newest(installed, major, "^[.+][%d.+]*$", suffix) or installed[name] and name + local rest = "^[.+][%d.+]*$" + return installed_version.newest(installed, major, rest, suffix, skip) + or installed[name] and name + or skip and installed_version.newest(installed, major, rest, suffix) end --- Returns a Java version without the zeros SDKMAN pads releases with, which @@ -97,16 +105,19 @@ end --- 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 --- the newest release of a bare major (21-tem selects 21.0.12.1+1-tem), unless ---- the name itself is installed. It may also pad the release with zeros, which ---- PreInstall passes on to Foojay: 27.0.0-librca installs, and so selects, the ---- release 27 (27+36-librca), not the newest one of the major. -function installed_version.find(installed, version, suffix) +--- the name itself is installed. In a bare major the releases for which the +--- function skip, if given, is true come last, as PreInstall skips them for +--- Oracle GraalVM: 25-graal selects 25.0.5-graal, not 25.4.4.1.1-graal. The +--- input may also pad the release with zeros, which PreInstall passes on to +--- Foojay: 27.0.0-librca installs, and so selects, the release 27 +--- (27+36-librca), not the newest one of the major. +function installed_version.find(installed, version, suffix, skip) local name = version .. suffix if installed[name] then return name end if version:match("^%d+$") then - return installed_version.major(installed, version, suffix) + return installed_version.major(installed, version, suffix, skip) end return installed_version.release(installed, installed_version.unpad(version), suffix) end diff --git a/tests/e2e/common/common.go b/tests/e2e/common/common.go index 726c84c..2bc8eca 100644 --- a/tests/e2e/common/common.go +++ b/tests/e2e/common/common.go @@ -86,7 +86,10 @@ var distributions = map[string]distribution{ // must install the newest build of the 25.0 line, the line Oracle's own // 25/latest download follows (vfox-java#97). Since 2026-10 Foojay lists the // feature releases under the JDK's version, which the 25.0 line has too, so -// that name took a feature release (vfox-java#105). +// that name took a feature release (vfox-java#105). Of the builds installed, +// `vfox use` and a .sdkmanrc must select the same one, the newest of the 25.0 +// line, where they took the newest release of the major, which is a feature +// release as soon as one is installed (vfox-java#110). const OracleGraalVMSpec = "25-graal" // ChecksOracleGraalVM reports whether a combo also checks Oracle GraalVM's @@ -652,8 +655,8 @@ var featureRelease = regexp.MustCompile(`^\d+\.[1-9]`) // BareMajorBuilds picks out of the versions `vfox search` lists, newest // first, the builds of a bare major spec's major and distribution // (25.4.4.1.1-graal ... 25.0.1-graal for 25-graal), and the one the spec must -// install: the newest that is no feature release (25.0.4.1.1-graal). The -// build named like the spec (25-graal, GA 25) is left out: vfox reports a +// install and select: the newest that is no feature release (25.0.4.1.1-graal). +// The build named like the spec (25-graal, GA 25) is left out: vfox reports a // spec naming an installed version as installed without calling PreInstall. func BareMajorBuilds(searched []string, spec string) (builds []string, want string) { major, distribution, _ := strings.Cut(spec, "-") @@ -670,6 +673,19 @@ func BareMajorBuilds(searched []string, spec string) (builds []string, want stri return builds, want } +// FeatureReleases picks the feature releases (25.4.4.1.1-graal) out of the +// builds BareMajorBuilds returned, in their order. A bare major selects the +// newest of them where they are all there is installed. +func FeatureReleases(builds []string) []string { + var features []string + for _, build := range builds { + if featureRelease.MatchString(build) { + features = append(features, build) + } + } + return features +} + // nameOnly maps the distributions the suite checks by name only, by short // name, to the runner platforms Foojay lists packages of them for // (2026-10-20). Only Red Hat's Linux x64 builds still download, which the diff --git a/tests/e2e/common/common_test.go b/tests/e2e/common/common_test.go index f8f8b29..37840c7 100644 --- a/tests/e2e/common/common_test.go +++ b/tests/e2e/common/common_test.go @@ -546,6 +546,11 @@ func TestBareMajorBuilds(t *testing.T) { if want != "25.0.4.1.1-graal" { t.Errorf("want = %q, want 25.0.4.1.1-graal", want) } + // The feature releases are what a bare major selects when + // they are all that is installed. + if features := FeatureReleases(builds); !slices.Equal(features, []string{"25.4.4.1.1-graal", "25.1.3-graal"}) { + t.Errorf("FeatureReleases = %q, want the two feature releases", features) + } }) } // GraalVM Community's versions are not Oracle GraalVM's. diff --git a/tests/e2e/linux/setup.go b/tests/e2e/linux/setup.go index 5fc0d72..4127dc6 100644 --- a/tests/e2e/linux/setup.go +++ b/tests/e2e/linux/setup.go @@ -17,6 +17,7 @@ package linux import ( "context" "fmt" + "slices" "strings" "testing" @@ -113,7 +114,9 @@ func checkMissingVersionRejected(ctx context.Context, t *testing.T, ctr tc.Conta // common.CheckGraalVMSearch). The builds of the major it lists are faked as // installed first (see common.BareMajorBuilds), so `vfox install` names the // one PreInstall picked and downloads nothing. The install must ask Foojay -// what the search asked (see common.AsksAsSearch). +// what the search asked (see common.AsksAsSearch). Of the same builds, `vfox +// use` and a .sdkmanrc must then select that one too, and, once only the +// feature releases are left installed, the newest of them. func checkGraalVMBareMajor(ctx context.Context, t *testing.T, ctr tc.Container) { t.Helper() spec := common.OracleGraalVMSpec @@ -122,20 +125,34 @@ func checkGraalVMBareMajor(ctx context.Context, t *testing.T, ctr tc.Container) common.CheckGraalVMSearch(t, search, out) builds, want := common.BareMajorBuilds(common.SearchedVersions(out), spec) require.NotEmptyf(t, want, "vfox search java graal lists no %s build but feature releases\n--- output ---\n%s", spec, out) - var dirs, javaDirs []string - for _, build := range builds { - dir := `"${VFOX_HOME:-$HOME/.vfox}"/cache/java/` + shQuote("v-"+build) - dirs = append(dirs, dir) - javaDirs = append(javaDirs, dir+"/"+shQuote("java-"+build)) - } - execOK(ctx, t, ctr, "mkdir --parents "+strings.Join(javaDirs, " ")) + features := common.FeatureReleases(builds) + require.NotEmptyf(t, features, "vfox search java graal lists no feature release of %s\n--- output ---\n%s", spec, out) + fakeInstalls(ctx, t, ctr, builds) out, installLookups := asked(ctx, t, ctr, "vfox install java@"+spec) require.NoErrorf(t, common.AsksAsSearch(searchLookups, installLookups), "vfox install java@%s must ask what %s asks", spec, search) require.Containsf(t, common.StripANSI(out), "java@"+want+" is already installed", "vfox install java@%s must take %s, the newest build that is no feature release", spec, want) - execOK(ctx, t, ctr, "rm --recursive "+strings.Join(dirs, " ")) - t.Logf("PASS java@%s takes %s out of %s", spec, want, strings.Join(builds, ", ")) + checkBareMajorSelected(ctx, t, ctr, spec, want, builds) + // Without the 25.0 line, the feature releases are all there is to select. + line := slices.DeleteFunc(slices.Clone(builds), func(build string) bool { return slices.Contains(features, build) }) + removeInstalls(ctx, t, ctr, line) + checkBareMajorSelected(ctx, t, ctr, spec, features[0], builds) + removeInstalls(ctx, t, ctr, features) + t.Logf("PASS java@%s takes %s out of %s, and %s where only %s are installed", spec, want, + strings.Join(builds, ", "), features[0], strings.Join(features, ", ")) +} + +// checkBareMajorSelected proves `vfox use java@` and a .sdkmanrc +// holding spec both select want among the installed builds, which must be +// faked (see fakeInstalls), and nothing is downloaded. +func checkBareMajorSelected(ctx context.Context, t *testing.T, ctr tc.Container, spec, want string, builds []string) { + t.Helper() + out := execOK(ctx, t, ctr, activated("vfox use java@"+spec)) + require.Containsf(t, common.StripANSI(out), "Now using java@"+want+".", + "vfox use java@%s must select %s\n--- output ---\n%s", spec, want, out) + out = legacyRun(ctx, t, ctr, spec, printJava) + common.CheckSelects(t, out, common.LegacyCase{Identifier: spec, Selects: want}, builds) } // checkNameOnly proves the distributions checked by name only go by their diff --git a/tests/e2e/linux/verify.go b/tests/e2e/linux/verify.go index dd08adb..acd69d9 100644 --- a/tests/e2e/linux/verify.go +++ b/tests/e2e/linux/verify.go @@ -120,19 +120,46 @@ func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, distr func verifyOwnNumberLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container) { t.Helper() cases, installs := common.OwnNumberIdentifiers() - var dirs []string - for _, name := range installs { - dir := `"${VFOX_HOME:-$HOME/.vfox}"/cache/java/` + shQuote("v-"+name) - bin := dir + "/" + shQuote("java-"+name) + "/bin" - dirs = append(dirs, dir) + fakeInstalls(ctx, t, ctr, installs) + for _, c := range cases { + out := legacyRun(ctx, t, ctr, c.Identifier, printJava) + common.CheckSelects(t, out, c, installs) + } + removeInstalls(ctx, t, ctr, installs) +} + +// printJava is the command that runs the stand-in java of a faked install, +// which prints the install's name, or does nothing where no install is +// selected. +const printJava = "if command -v java >/dev/null; then java; fi" + +// installDir renders the directory vfox keeps the install of name in, as a +// shell word. +func installDir(name string) string { + return `"${VFOX_HOME:-$HOME/.vfox}"/cache/java/` + shQuote("v-"+name) +} + +// fakeInstalls fakes the installs of names, each with a stand-in java that only +// prints its name (see common.CheckSelects). +func fakeInstalls(ctx context.Context, t *testing.T, ctr tc.Container, names []string) { + t.Helper() + for _, name := range names { + bin := installDir(name) + "/" + shQuote("java-"+name) + "/bin" execOK(ctx, t, ctr, fmt.Sprintf("mkdir --parents %s && printf '%%s' %s > %s/java && chmod +x %s/java", bin, shQuote("#!/bin/sh\necho "+shQuote(name)+"\n"), bin, bin)) } - for _, c := range cases { - out := legacyRun(ctx, t, ctr, c.Identifier, "if command -v java >/dev/null; then java; fi") - common.CheckSelects(t, out, c, installs) +} + +// removeInstalls removes the installs of names that fakeInstalls made. +func removeInstalls(ctx context.Context, t *testing.T, ctr tc.Container, names []string) { + t.Helper() + var dirs []string + for _, name := range names { + dirs = append(dirs, installDir(name)) + } + if len(dirs) > 0 { + execOK(ctx, t, ctr, "rm --recursive "+strings.Join(dirs, " ")) } - execOK(ctx, t, ctr, "rm --recursive "+strings.Join(dirs, " ")) } // legacyJava writes java= to a .sdkmanrc and returns what java diff --git a/tests/e2e/windows/e2e_test.go b/tests/e2e/windows/e2e_test.go index 595c058..86621af 100644 --- a/tests/e2e/windows/e2e_test.go +++ b/tests/e2e/windows/e2e_test.go @@ -247,7 +247,7 @@ func runOne(t *testing.T, ctx context.Context, image, platform, arch string, c c checkVersionlessRejected(ctx, t, ctr, box, activation, c.Distribution) checkMissingVersionRejected(ctx, t, ctr, box, c.Distribution, version, arch) if common.ChecksOracleGraalVM(c.Distribution) { - checkGraalVMBareMajor(ctx, t, ctr, box) + checkGraalVMBareMajor(ctx, t, ctr, box, activation) } if common.ChecksNameOnly(c.Distribution) { checkNameOnly(ctx, t, ctr, box, platform) diff --git a/tests/e2e/windows/setup.go b/tests/e2e/windows/setup.go index 517faa3..c7c0836 100644 --- a/tests/e2e/windows/setup.go +++ b/tests/e2e/windows/setup.go @@ -21,6 +21,7 @@ import ( "os/exec" "path/filepath" "runtime" + "slices" "strings" "sync" "testing" @@ -331,27 +332,42 @@ func checkMissingVersionRejected(ctx context.Context, t *testing.T, ctr tc.Conta // search java graal` must list under their own versions (see // common.CheckGraalVMSearch). The builds of the major it lists are faked as // installed first (see common.BareMajorBuilds), so `vfox install` names the -// one PreInstall picked and downloads nothing. -func checkGraalVMBareMajor(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox) { +// one PreInstall picked and downloads nothing. Of the same builds, `vfox use` +// and a .sdkmanrc must then select that one too, and, once only the feature +// releases are left installed, the newest of them. +func checkGraalVMBareMajor(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, activation string) { t.Helper() spec := common.OracleGraalVMSpec out := execRetry(ctx, t, ctr, "vfox search java graal", "vfox search java graal", box.vars...) common.CheckGraalVMSearch(t, "vfox search java graal", out) builds, want := common.BareMajorBuilds(common.SearchedVersions(out), spec) require.NotEmptyf(t, want, "vfox search java graal lists no %s build but feature releases\n--- output ---\n%s", spec, out) - var create, remove []string - for _, build := range builds { - dir := fmt.Sprintf("(Join-Path $env:VFOX_HOME %s)", pwshQuote(`cache\java\v-`+build)) - create = append(create, fmt.Sprintf("New-Item -ItemType Directory -Force -Path (Join-Path %s %s) | Out-Null", - dir, pwshQuote("java-"+build))) - remove = append(remove, fmt.Sprintf("Remove-Item -LiteralPath %s -Recurse -Force", dir)) - } - execOK(ctx, t, ctr, strings.Join(create, "; "), box.vars...) + features := common.FeatureReleases(builds) + require.NotEmptyf(t, features, "vfox search java graal lists no feature release of %s\n--- output ---\n%s", spec, out) + fakeInstalls(ctx, t, ctr, box, builds) out = execRetry(ctx, t, ctr, "vfox install java@"+spec, "vfox install "+pwshQuote("java@"+spec), box.vars...) require.Containsf(t, common.StripANSI(out), "java@"+want+" is already installed", "vfox install java@%s must take %s, the newest build that is no feature release", spec, want) - execOK(ctx, t, ctr, strings.Join(remove, "; "), box.vars...) - t.Logf("PASS java@%s takes %s out of %s", spec, want, strings.Join(builds, ", ")) + checkBareMajorSelected(ctx, t, ctr, box, activation, spec, want, builds) + // Without the 25.0 line, the feature releases are all there is to select. + line := slices.DeleteFunc(slices.Clone(builds), func(build string) bool { return slices.Contains(features, build) }) + removeInstalls(ctx, t, ctr, box, line) + checkBareMajorSelected(ctx, t, ctr, box, activation, spec, features[0], builds) + removeInstalls(ctx, t, ctr, box, features) + t.Logf("PASS java@%s takes %s out of %s, and %s where only %s are installed", spec, want, + strings.Join(builds, ", "), features[0], strings.Join(features, ", ")) +} + +// checkBareMajorSelected proves `vfox use java@` and a .sdkmanrc +// holding spec both select want among the installed builds, which must be +// faked (see fakeInstalls), and nothing is downloaded. +func checkBareMajorSelected(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, activation, spec, want string, builds []string) { + t.Helper() + out := execOK(ctx, t, ctr, activated(activation, "vfox use "+pwshQuote("java@"+spec)), box.vars...) + require.Containsf(t, common.StripANSI(out), "Now using java@"+want+".", + "vfox use java@%s must select %s\n--- output ---\n%s", spec, want, out) + out = legacyRun(ctx, t, ctr, box, spec, printJava) + common.CheckSelects(t, out, common.LegacyCase{Identifier: spec, Selects: want}, builds) } // checkNameOnly proves the distributions checked by name only go by their diff --git a/tests/e2e/windows/verify.go b/tests/e2e/windows/verify.go index 680b857..8619022 100644 --- a/tests/e2e/windows/verify.go +++ b/tests/e2e/windows/verify.go @@ -125,21 +125,51 @@ func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, box s func verifyOwnNumberLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox) { t.Helper() cases, installs := common.OwnNumberIdentifiers() - var create, remove []string - for _, name := range installs { - dir := fmt.Sprintf("(Join-Path $env:VFOX_HOME %s)", pwshQuote(`cache\java\v-`+name)) + fakeInstalls(ctx, t, ctr, box, installs) + for _, c := range cases { + out := legacyRun(ctx, t, ctr, box, c.Identifier, printJava) + common.CheckSelects(t, out, c, installs) + } + removeInstalls(ctx, t, ctr, box, installs) +} + +// printJava is the command that runs the stand-in java (a java.cmd) of a +// faked install, which prints the install's name, or does nothing where no +// install is selected. +const printJava = "if (Get-Command java -ErrorAction SilentlyContinue) { java }" + +// installDir renders the directory vfox keeps the install of name in, as a +// pwsh expression. +func installDir(name string) string { + return fmt.Sprintf("(Join-Path $env:VFOX_HOME %s)", pwshQuote(`cache\java\v-`+name)) +} + +// fakeInstalls fakes the installs of names, each with a stand-in java (a +// java.cmd) that only prints its name (see common.CheckSelects). +func fakeInstalls(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, names []string) { + t.Helper() + var create []string + for _, name := range names { create = append(create, fmt.Sprintf( "$bin = Join-Path %s %s; New-Item -ItemType Directory -Force -Path $bin | Out-Null; "+ "Set-Content -Path (Join-Path $bin 'java.cmd') -Value %s", - dir, pwshQuote(`java-`+name+`\bin`), pwshQuote("@echo "+name))) - remove = append(remove, fmt.Sprintf("Remove-Item -LiteralPath %s -Recurse -Force", dir)) + installDir(name), pwshQuote(`java-`+name+`\bin`), pwshQuote("@echo "+name))) } - execOK(ctx, t, ctr, strings.Join(create, "; "), box.vars...) - for _, c := range cases { - out := legacyRun(ctx, t, ctr, box, c.Identifier, "if (Get-Command java -ErrorAction SilentlyContinue) { java }") - common.CheckSelects(t, out, c, installs) + if len(create) > 0 { + execOK(ctx, t, ctr, strings.Join(create, "; "), box.vars...) + } +} + +// removeInstalls removes the installs of names that fakeInstalls made. +func removeInstalls(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, names []string) { + t.Helper() + var remove []string + for _, name := range names { + remove = append(remove, fmt.Sprintf("Remove-Item -LiteralPath %s -Recurse -Force", installDir(name))) + } + if len(remove) > 0 { + execOK(ctx, t, ctr, strings.Join(remove, "; "), box.vars...) } - execOK(ctx, t, ctr, strings.Join(remove, "; "), box.vars...) } // legacyJava writes java= to a .sdkmanrc and returns what java diff --git a/tests/parse_legacy_file_test.lua b/tests/parse_legacy_file_test.lua index 465290c..9987c9e 100644 --- a/tests/parse_legacy_file_test.lua +++ b/tests/parse_legacy_file_test.lua @@ -173,6 +173,34 @@ local cases = { -- Until Foojay's change in October 2026, release 0.6.2 named Liberica NIK -- with BellSoft's build. { "java=23.1.8.r21-nik\n", { "23.1.8+1-nik", "23.1.12.1-nik" }, "23.1.8+1-nik" }, + -- A bare major, or a release that is not installed, takes the newest + -- release of the major, and Oracle GraalVM's monthly feature releases come + -- after the 25.0 line there, as in vfox install java@25-graal and vfox use + -- java@25-graal (vfox-java#110). + { "java=25-graal\n", { "25.4.4.1.1-graal", "25.0.4.1-graal" }, "25.0.4.1-graal" }, + { + "java=25-graal\n", + { "25.3.4.1-graal", "25.0.4.1.1-graal", "25.4.4.1.1-graal", "25.0.4.1-graal" }, + "25.0.4.1.1-graal", + }, + { "java=25.0.3-graal\n", { "25.4.4.1.1-graal", "25.0.4.1-graal" }, "25.0.4.1-graal" }, + { "java=25.0.4.1-graal\n", { "25.4.4.1.1-graal", "25.0.4.1.1-graal", "25.0.5-graal" }, "25.0.5-graal" }, + -- The first release of the major belongs to the 25.0 line as well. + { "java=25.0.3-graal\n", { "25.4.4.1.1-graal", "25-graal" }, "25-graal" }, + -- Nothing else of the major installed. + { "java=25-graal\n", { "25.4.4.1.1-graal", "25.3.4.1-graal" }, "25.4.4.1.1-graal" }, + { "java=25.0.3-graal\n", { "25.4.4.1.1-graal" }, "25.4.4.1.1-graal" }, + -- A file that names a feature release (SDKMAN's identifier is 25.4.4+1-graal) + -- takes the newest release of the major, as it did. + { "java=25.4.4+1-graal\n", { "25.4.4.1.1-graal", "25.0.4.1-graal" }, "25.4.4.1.1-graal" }, + { "java=25.5.5-graal\n", { "25.4.4.1.1-graal", "25.0.4.1-graal" }, "25.4.4.1.1-graal" }, + { "java=25.4.4.1.1-graal\n", { "25.4.4.1.1-graal", "25.0.4.1-graal" }, "25.4.4.1.1-graal" }, + -- Other majors and distributions: GraalVM Community's feature releases are + -- what it still ships, and a minor above 0 means nothing for Mandrel. + { "java=21-graal\n", { "21.0.8-graal", "25.4.4.1.1-graal" }, "21.0.8-graal" }, + { "java=25-graalce\n", { "25.4.4.1.1-graalce", "25.0.2-graalce" }, "25.4.4.1.1-graalce" }, + { "java=25.0.1-graalce\n", { "25.4.4.1.1-graalce", "25.0.2-graalce" }, "25.4.4.1.1-graalce" }, + { "java=23-mandrel\n", { "23.1.12.1-mandrel", "23.0.1.2-mandrel" }, "23.1.12.1-mandrel" }, -- No java line at all selects nothing. { "maven=3.9.9\n", { "21.0.8+9-tem" }, nil }, } diff --git a/tests/pre_use_test.lua b/tests/pre_use_test.lua index 4beb8d2..7fd58ff 100644 --- a/tests/pre_use_test.lua +++ b/tests/pre_use_test.lua @@ -17,8 +17,15 @@ PLUGIN = {} dofile("hooks/pre_use.lua") -- Installed versions as PreInstall names them, keyed the way vfox passes them. -local installed = {} -for _, version in ipairs({ +local function sdks(versions) + local installed = {} + for _, version in ipairs(versions) do + installed[version] = { name = "java", version = version, path = "/sdks/java-" .. version } + end + return installed +end + +local installed = sdks({ "8.0.482+8-zulu-fx", "21.0.8+9-tem", "21.0.12+8-tem", @@ -50,9 +57,7 @@ for _, version in ipairs({ "21.0.8+9-rdht", "25.0.4-semcrt", "25.0.2+10-sem", -}) do - installed[version] = { name = "java", version = version, path = "/sdks/java-" .. version } -end +}) local cases = { { "8.0.482+8-zulu-fx", "8.0.482+8-zulu-fx" }, @@ -145,6 +150,44 @@ for _, case in ipairs(cases) do assert(version == case[2], case[1] .. ": expected " .. tostring(case[2]) .. ", got " .. tostring(version)) end +-- Oracle GraalVM's bare major selects the newest installed build of its 25.0 +-- line, as installing it installs one, and takes a monthly feature release +-- only where nothing else of the major is installed. It took the newest +-- release of the major, which is a feature release as soon as one is +-- installed (vfox-java#110). +-- Each case is the installed versions, the input and the version selected. +local graalvm_cases = { + { { "25.4.4.1.1-graal", "25.0.4.1-graal" }, "25-graal", "25.0.4.1-graal" }, + { { "25.4.4.1.1-graal", "25.0.4.1-graal" }, "graalvm-25", "25.0.4.1-graal" }, + { + { "25.3.4.1-graal", "25.0.4.1.1-graal", "25.4.4.1.1-graal", "25.0.4.1-graal", "25.0.4-graal" }, + "25-graal", + "25.0.4.1.1-graal", + }, + { { "25.4.4.1-graal", "25.0.2-graal" }, "25-graal", "25.0.2-graal" }, + -- Nothing else of the major installed. + { { "25.4.4.1.1-graal", "25.3.4.1-graal", "25.1.3-graal" }, "25-graal", "25.4.4.1.1-graal" }, + { { "25.3.4.1-graal" }, "25-graal", "25.3.4.1-graal" }, + -- A feature release selects itself by its version, and not another one + -- when it is not installed. + { { "25.4.4.1.1-graal", "25.0.5-graal" }, "25.4.4.1.1-graal", "25.4.4.1.1-graal" }, + { { "25.3.4.1-graal", "25.0.5-graal" }, "25.4.4.1.1-graal", "25.4.4.1.1-graal" }, + -- Other majors, distributions and releases with a minor above 0 are not skipped. + { { "21.0.8-graal", "25.4.4.1.1-graal" }, "21-graal", "21.0.8-graal" }, + { { "25.4.4.1.1-graal", "25.0.5-graal" }, "26-graal", "26-graal" }, + { { "25.4.4.1.1-graalce", "25.0.2-graalce" }, "25-graalce", "25.4.4.1.1-graalce" }, + { { "25.4.4.1.1-graalce", "25.0.5-graal" }, "25-graal", "25.0.5-graal" }, + { { "25.4.4.1.1-graalce" }, "25-graal", "25-graal" }, + { { "23.1.12.1-mandrel", "23.0.1.2-mandrel" }, "23-mandrel", "23.1.12.1-mandrel" }, + { { "23.1.8-nik", "23.0.1-nik" }, "23-nik", "23.1.8-nik" }, +} +for _, case in ipairs(graalvm_cases) do + local result = PLUGIN:PreUse({ version = case[2], installedSdks = sdks(case[1]) }) + local version = result and result.version + local message = table.concat(case[1], ", ") .. " with " .. case[2] + assert(version == case[3], message .. ": expected " .. tostring(case[3]) .. ", got " .. tostring(version)) +end + -- A distribution without a version names no installed version, so PreUse asks -- for one, as PreInstall does (vfox-java#98). Alone it failed with a Lua error; -- after an empty version, vfox reported -tem not installed. @@ -154,4 +197,4 @@ for _, version in ipairs(rejected) do local message = "No version in " .. version .. "; add one" assert(not ok and tostring(err):find(message, 1, true), version .. ": unexpected " .. tostring(err)) end -print("PASS: " .. #cases + #rejected .. " PreUse regression cases") +print("PASS: " .. #cases + #graalvm_cases + #rejected .. " PreUse regression cases")