diff --git a/docs/changes.md b/docs/changes.md index 215b3e1..23b5076 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -8,6 +8,7 @@ What changed in each release, as a user notices it. GitHub's release pages list - 13 more distributions, the rest of what Foojay lists: AdoptOpenJDK (`adpt`, `adptj9`), Eliya (`eliya`), OJDKBuild (`ojdkbld`), OpenLogic (`opnlgc`), Red Hat (`rdht`), Semeru certified (`semcrt`), Gluon GraalVM (`gln`) and GraalVM CE for JDK 8 to 19 (`grl8`, `grl11`, `grl16`, `grl17`, `grl19`). Red Hat installs from tar.xz, and OJDKBuild on Linux, OpenLogic on macOS and Gluon GraalVM before 22.1 from zips. A `.sdkmanrc` takes SDKMAN's old identifiers for them, such as `11.0.10.j9-adpt` and `22.3.3.r17-grl`. See [distributions.md](distributions.md). - Mandrel and Liberica NIK install by the GraalVM version they carry again: `java@23.1.12-mandrel` and `java@23.1.8-nik` work, `vfox search java mandrel` lists every release, and `java@23-mandrel` installs the newest 23.x, `23.1.12.1-mandrel`, which runs Java 21. In October 2026 Foojay moved that version from `java_version` to `distribution_version`, and since then release 0.6.2 fails on such versions with `No JDK found`, lists one Mandrel per Java major and installs Mandrel 23.1.8 for `java@21-mandrel`. Installs it made since then keep names after the Java version (`21-mandrel`, `21.0.12.1+2-nik`), and vfox still finds them by those names; uninstall one to get what the spec means now. See [distributions.md](distributions.md#behavior-of-mandrel-and-liberica-nik) ([#106](https://github.com/version-fox/vfox-java/issues/106)). +- A `.sdkmanrc` takes SDKMAN's identifiers of Mandrel and Liberica NIK with the JDK after `.r`: `java=25.0.4.r25-nik` selects `25.0.4-nik`, where release 0.6.2 selects the newest installed release of the major. Of a release that comes for two JDKs, the plugin installs only the newer one's build, so `22.1.r11-mandrel` selects nothing. See [sdkmanrc.md](sdkmanrc.md#sdkmans-spellings) ([#109](https://github.com/version-fox/vfox-java/issues/109)). - The notes printed by `vfox add java`, `vfox update java` and `vfox info java` are a link to the README, which lists every short name and shows how to search a distribution. Release 0.6.2 gives five short names there but not how to list their versions, while `vfox search java` lists OpenJDK only ([vfox#390](https://github.com/version-fox/vfox/issues/390)). - 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`. diff --git a/docs/development.md b/docs/development.md index 5159838..50ee0aa 100644 --- a/docs/development.md +++ b/docs/development.md @@ -46,7 +46,7 @@ A few contracts that cost time to find: 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. -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`). `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). +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). ## Foojay diff --git a/docs/e2e.md b/docs/e2e.md index 8e113f0..8097a35 100644 --- a/docs/e2e.md +++ b/docs/e2e.md @@ -152,6 +152,15 @@ JDK, so `java` can only come from what vfox installs: weeks, so that install is faked: its `bin` holds a stand-in `java` that only prints the name. On Windows the file is written with CRLF line endings. + + On the Temurin rows, a `.sdkmanrc` holding one of SDKMAN's identifiers of + Mandrel and Liberica NIK with the JDK after `.r` must then select the + release it names: `java=25.0.4.r25-nik` selects `25.0.4-nik`, not + `25.0.4.1-nik`, which `ParseLegacyFile` took while it read `25.0.4.r25` as + the release. `java=22.1.r11-mandrel` must select nothing, as the plugin + installs only the JDK 17 build of Mandrel 22.1 (`OwnNumberIdentifiers` in + `tests/e2e/common`). The releases they name and a newer one of each major + are faked the same way. 7. 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 @@ -176,13 +185,13 @@ JDK, so `java` can only come from what vfox installs: The default matrix runs every distribution against both vfox channels (`latest`, `main`): -| Distribution | Release | Covers | amd64 | arm64 | -| ------------ | ------- | ---------------------------------------------------------------------------------------- | ----- | ----- | -| `open` | 25.0.2 | the default distribution, versions without a suffix | ✓ | | -| `tem` | 21.0.8 | the common path (tar.gz on Linux, zip on Windows); the name and number checks of item 4 | ✓ | ✓ | -| `librca-fx` | 25.0.4 | JavaFX bundled builds (`-fx`) | ✓ | ✓ | -| `jb` | 21.0.5 | the tar.gz path on Windows, which has no zip for it | ✓ | ✓ | -| `graalce` | 25.0.2 | a `GRAALVM_HOME` set beforehand keeps its value; Oracle GraalVM's `25-graal` | ✓ | | +| Distribution | Release | Covers | amd64 | arm64 | +| ------------ | ------- | ---------------------------------------------------------------------------------------------- | ----- | ----- | +| `open` | 25.0.2 | the default distribution, versions without a suffix | ✓ | | +| `tem` | 21.0.8 | the common path (tar.gz on Linux, zip on Windows); the name and number checks of items 4 and 6 | ✓ | ✓ | +| `librca-fx` | 25.0.4 | JavaFX bundled builds (`-fx`) | ✓ | ✓ | +| `jb` | 21.0.5 | the tar.gz path on Windows, which has no zip for it | ✓ | ✓ | +| `graalce` | 25.0.2 | a `GRAALVM_HOME` set beforehand keeps its value; Oracle GraalVM's `25-graal` | ✓ | | Every pinned release is published through Foojay for each platform its row runs on. OpenJDK and GraalVM Community ship no `windows-aarch64` build, so diff --git a/docs/sdkmanrc.md b/docs/sdkmanrc.md index 815ef78..151d620 100644 --- a/docs/sdkmanrc.md +++ b/docs/sdkmanrc.md @@ -55,12 +55,15 @@ It never crosses to another distribution, between a JavaFX build and a plain one | `21.0.8+1.1-tem` | Temurin 21.0.8, see below | | `27.0.0+36-librca`, `25.0.1.0+8-open` | `27+36-librca`, `25.0.1+8`: the zeros are dropped | | `28.ea.19-open`, `28.0.0+ea.19-open` | the early-access build `28-ea+19` | +| `25.0.4.r25-nik`, `25.0.2.r25-mandrel` | `25.0.4-nik`, `25.0.2-mandrel`: `.r` and the JDK after it are dropped | | `11.0.10.hs-adpt`, `11.0.10.j9-adpt` | AdoptOpenJDK with HotSpot, `11.0.10-adpt`, and with OpenJ9, `11.0.10-adptj9` | | `22.3.3.r17-grl` | GraalVM CE 22.3.3 for JDK 17, `22.3.3-grl17` | | `22.1.0.1.r17-gln` | Gluon GraalVM 22.1.0.1, `22.1.0.1-gln` | The last three rows are SDKMAN's identifiers for distributions it no longer lists, with the virtual machine or the JDK in the version, see [distributions.md](distributions.md#short-names-and-sdkman). The plugin installs no JDK 11 build of Gluon GraalVM 22 and no AdoptOpenJDK upstream build, so `22.1.0.1.r11-gln` and `11.0.9.open-adpt` select nothing. Unreleased: release 0.6.2 selects nothing for any of the three. +SDKMAN writes the JDK into some versions of Mandrel and Liberica NIK as well, which the plugin names after the GraalVM version alone, see [distributions.md](distributions.md#behavior-of-mandrel-and-liberica-nik). Of a release that comes for two JDKs, the plugin installs only the build for the newer one, so an identifier with the older one selects nothing: `22.1.r11-mandrel`, `23.0.1.r17-nik`. The older JDK is 11 from 21.3 to 22.3.5 and 17 from 23 to 23.0.1.2. `25.r25-mandrel` has only the major and selects the newest installed Mandrel 25, as `java=25-mandrel` does, while SDKMAN installs 25.0.1.0 for it. Unreleased: release 0.6.2 takes `25.0.4.r25` for a release, finds none and selects the newest installed release of the major. + The number after `+` in SDKMAN's newer identifiers (`21.0.8+1.1-tem`) counts SDKMAN's own rebuilds. It is not a build number of the vendor, so the plugin does not look for one and takes the release. Foojay, and so the installed name, has no zero padding, which is why `27.0.0` means the release `27`. A `.0` padding in `sdk list java` is common (`25.0.1.0+8-open`, `21.0.9.0+116386-jbr`), and the plugin reads it as well. ## Not finding it diff --git a/docs/zh-CN/changes.md b/docs/zh-CN/changes.md index fcb3d4c..4a46332 100644 --- a/docs/zh-CN/changes.md +++ b/docs/zh-CN/changes.md @@ -8,6 +8,7 @@ - 新增 13 个发行版,Foojay 列出的发行版从此全都有了:AdoptOpenJDK(`adpt`、`adptj9`)、Eliya(`eliya`)、OJDKBuild(`ojdkbld`)、OpenLogic(`opnlgc`)、Red Hat(`rdht`)、Semeru 认证版(`semcrt`)、Gluon GraalVM(`gln`),以及基于 JDK 8 到 19 的 GraalVM CE(`grl8`、`grl11`、`grl16`、`grl17`、`grl19`)。Red Hat 从 tar.xz 包安装;Linux 上的 OJDKBuild、macOS 上的 OpenLogic 和 22.1 以前的 Gluon GraalVM 从 zip 包安装。`.sdkmanrc` 里写 SDKMAN 以前给它们的标识符也认,例如 `11.0.10.j9-adpt`、`22.3.3.r17-grl`。见 [distributions.md](distributions.md)。 - Mandrel 和 Liberica NIK 重新按它们沿用的 GraalVM 版本号安装:`java@23.1.12-mandrel` 和 `java@23.1.8-nik` 能装了,`vfox search java mandrel` 列出全部版本,`java@23-mandrel` 装最新的 23.x,即运行 Java 21 的 `23.1.12.1-mandrel`。2026 年 10 月,Foojay 把这个版本号从 `java_version` 挪到了 `distribution_version`,此后 0.6.2 装这样的版本都报 `No JDK found`,每个 Java 大版本只列一个 Mandrel,`java@21-mandrel` 装的是 Mandrel 23.1.8。这段时间里它装上的版本按 Java 版本号命名(`21-mandrel`、`21.0.12.1+2-nik`),vfox 仍会按这些名字找到它们;想要这个写法现在所指的版本,先把旧的卸载。见 [distributions.md](distributions.md#mandrel-与-liberica-nik-的行为)([#106](https://github.com/version-fox/vfox-java/issues/106))。 +- `.sdkmanrc` 里写 SDKMAN 给 Mandrel 和 Liberica NIK 的、`.r` 后面带 JDK 版本的标识符,会选中标识符指明的版本:`java=25.0.4.r25-nik` 选中 `25.0.4-nik`,0.6.2 选中的则是同一大版本里已安装的最新版本。一个版本同时为两个 JDK 发布时,插件只装较新 JDK 的构建,所以 `22.1.r11-mandrel` 什么都不会选中。见 [sdkmanrc.md](sdkmanrc.md#sdkman-的写法)([#109](https://github.com/version-fox/vfox-java/issues/109))。 - `vfox add java`、`vfox update java` 和 `vfox info java` 打印的 Notes 改成了一个 README 链接,README 里列了全部简称,也写了怎样搜索某个发行版。0.6.2 的 Notes 只给了五个简称,没说怎样列出它们的版本,而 `vfox search java` 只列 OpenJDK([vfox#390](https://github.com/version-fox/vfox/issues/390))。 - 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` 为止。 diff --git a/docs/zh-CN/development.md b/docs/zh-CN/development.md index 84ed3df..556f45a 100644 --- a/docs/zh-CN/development.md +++ b/docs/zh-CN/development.md @@ -46,7 +46,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` 还会再退一步,取同一大版本里已安装的最新版本。 -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 给版本补的零由 `installed_version.unpad` 去掉(`27.0.0+36` 就是 `27+36`)。简称与 SDKMAN 不同的地方见 [distributions.md](distributions.md#简称与-sdkman-的不同)。 +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-的不同)。 ## Foojay diff --git a/docs/zh-CN/e2e.md b/docs/zh-CN/e2e.md index 7482cab..f5fb1da 100644 --- a/docs/zh-CN/e2e.md +++ b/docs/zh-CN/e2e.md @@ -44,6 +44,8 @@ Windows 容器的覆盖(`tests/e2e/windows`)无法在开发容器里运行 - EA 构建的标识符。固定大版本的 EA 构建,如果按 `sdk list java` 里 OpenJDK 的写法来写(`28.0.0.0+ea.20-open`),必须选中那个 EA 构建:`java=21.0.0.0+ea.1-tem` 要选中 `21-ea+1-tem`,不能选中 GA 版本。`ParseLegacyFile` 只认 SDKMAN 较旧的 `21.ea.1-tem` 写法时,选中的正是 GA 版本。安装后的版本名本身(`java=21-ea+1-tem`)也要能选中:`ParseLegacyFile` 以前解析不了它,什么也没选中,而 `vfox use` 是能选中的。 jdk.java.net 会在几周内撤掉每个 EA 构建,所以这个安装是模拟出来的:它的 `bin` 里放着一个只打印名字的替身 `java`。在 Windows 上,文件用 CRLF 换行写入。 + + 接着,在 `tem` 这几行里,`.sdkmanrc` 里写的是 SDKMAN 给 Mandrel 和 Liberica NIK 的、`.r` 后面带 JDK 版本的标识符时,要选中标识符指明的版本:`java=25.0.4.r25-nik` 选中 `25.0.4-nik`,而不是 `25.0.4.1-nik`。`ParseLegacyFile` 以前把 `25.0.4.r25` 当成版本号,选中的正是后者。`java=22.1.r11-mandrel` 则什么都不能选中,因为插件只装 Mandrel 22.1 的 JDK 17 构建(`tests/e2e/common` 里的 `OwnNumberIdentifiers`)。标识符指明的版本,以及同一大版本里更新的一个版本,也是这样模拟安装的。 7. `vfox use --global` 之后,`PATH` 上的 JDK 要报告请求的 `java.version`、runner 的 `os.arch`(`amd64` / `aarch64`,这也能抓出在 Windows ARM64 上模拟运行的 x64 JDK)和这个发行版的 `java.vendor`。`JAVA_HOME` 指向同一个 JDK,`javac` 能编译一个程序,`java` 能运行它,模块列表恰好在 `-fx` 构建里含有 JavaFX,`vfox current java` 报告已安装的版本。事先设置的 `GRAALVM_HOME` 保持原值,因为用户会把它指向自己的 GraalVM;对 GraalVM,插件以前会把它替换成正在使用的 JDK。 8. `PreUse` 把用户输入的各种写法映射到已安装的版本,不需要再次下载:只写大版本号(`java@21`,交给 vfox 核心做模糊匹配)、安装时用的写法(`java@21.0.8-tem`,`PreUse` 会在已安装的版本里找到它的构建号),以及 asdf 风格的 `<长名>-<版本>[-fx]`(`java@temurin-21.0.8+9` 改写成 `21.0.8+9-tem`;`java@openjdk-25.0.2+10` 改写成 `25.0.2+10`)。SDKMAN 那样补零的版本也一样(`java@21.0.8.0-tem`、`java@21.0.8.0+9-tem`),`vfox install` 必须先发现它们已经安装:`PreInstall` 把零原样交给了 Foojay,Foojay 会去掉它,而 `PreUse` 以前去找以补零版本开头的已安装版本,报告说没有安装。 @@ -52,7 +54,7 @@ Windows 容器的覆盖(`tests/e2e/windows`)无法在开发容器里运行 | 发行版 | 版本 | 覆盖内容 | amd64 | arm64 | | ------------ | ------- | ---------------------------------------------------------------------------- | ----- | ----- | | `open` | 25.0.2 | 默认发行版,版本不带后缀 | ✓ | | -| `tem` | 21.0.8 | 常规路径(Linux 上是 tar.gz,Windows 上是 zip);第 4 条里按名字和按版本号的检查 | ✓ | ✓ | +| `tem` | 21.0.8 | 常规路径(Linux 上是 tar.gz,Windows 上是 zip);第 4、6 条里按名字和按版本号的检查 | ✓ | ✓ | | `librca-fx` | 25.0.4 | 带 JavaFX 的构建(`-fx`) | ✓ | ✓ | | `jb` | 21.0.5 | Windows 上走 tar.gz 的路径,那里没有 zip | ✓ | ✓ | | `graalce` | 25.0.2 | 事先设置的 `GRAALVM_HOME` 保持原值;Oracle GraalVM 的 `25-graal` | ✓ | | diff --git a/docs/zh-CN/sdkmanrc.md b/docs/zh-CN/sdkmanrc.md index e397b61..47c91f0 100644 --- a/docs/zh-CN/sdkmanrc.md +++ b/docs/zh-CN/sdkmanrc.md @@ -55,12 +55,15 @@ vfox 挂载到 Shell 之后,进入目录时会读取这个文件,并选中 | `21.0.8+1.1-tem` | Temurin 21.0.8,见下文 | | `27.0.0+36-librca`、`25.0.1.0+8-open` | `27+36-librca`、`25.0.1+8`:补的零会被去掉 | | `28.ea.19-open`、`28.0.0+ea.19-open` | EA 构建 `28-ea+19` | +| `25.0.4.r25-nik`、`25.0.2.r25-mandrel` | `25.0.4-nik`、`25.0.2-mandrel`:`.r` 和后面的 JDK 版本会被去掉 | | `11.0.10.hs-adpt`、`11.0.10.j9-adpt` | AdoptOpenJDK 的 HotSpot 版 `11.0.10-adpt` 和 OpenJ9 版 `11.0.10-adptj9` | | `22.3.3.r17-grl` | 基于 JDK 17 的 GraalVM CE 22.3.3,`22.3.3-grl17` | | `22.1.0.1.r17-gln` | Gluon GraalVM 22.1.0.1,`22.1.0.1-gln` | 最后三行是 SDKMAN 给它已经不再提供的发行版用过的标识符,虚拟机或 JDK 版本写在版本号里,见 [distributions.md](distributions.md#简称与-sdkman-的不同)。插件不安装 Gluon GraalVM 22 的 JDK 11 构建,也不安装 AdoptOpenJDK 的 upstream 构建,所以 `22.1.0.1.r11-gln` 和 `11.0.9.open-adpt` 什么都不会选中。未发布:0.6.2 对这三行都选不中任何 JDK。 +SDKMAN 在一些 Mandrel 和 Liberica NIK 的版本号里也写了 JDK 版本,而插件只按 GraalVM 版本号给它们命名,见 [distributions.md](distributions.md#mandrel-与-liberica-nik-的行为)。一个版本同时为两个 JDK 发布时,插件只装较新 JDK 的构建,所以写了较旧 JDK 的标识符什么都不会选中:`22.1.r11-mandrel`、`23.0.1.r17-nik`。较旧的 JDK,在 21.3 到 22.3.5 是 JDK 11,在 23 到 23.0.1.2 是 JDK 17。`25.r25-mandrel` 只写了大版本号,会选中已安装的最新 Mandrel 25,和 `java=25-mandrel` 一样,而 SDKMAN 按这个标识符装的是 25.0.1.0。未发布:0.6.2 把 `25.0.4.r25` 当成版本号,找不到这个版本,就选中同一大版本里已安装的最新版本。 + SDKMAN 新写法里 `+` 后面的数字(`21.0.8+1.1-tem`)是 SDKMAN 自己重新构建的次数,不是厂商的构建号,所以插件不会去找这个构建,而是按版本匹配。Foojay 和安装后的版本名都不补零,所以 `27.0.0` 指的就是 `27`。`sdk list java` 里补一个 `.0` 很常见(`25.0.1.0+8-open`、`21.0.9.0+116386-jbr`),插件同样能读。 ## 没有生效怎么办 diff --git a/hooks/parse_legacy_file.lua b/hooks/parse_legacy_file.lua index 7074d6b..5788a56 100644 --- a/hooks/parse_legacy_file.lua +++ b/hooks/parse_legacy_file.lua @@ -28,6 +28,30 @@ local sdkman_flavours = { gln = { r17 = "gln" }, } +--- Mandrel and Liberica NIK, which SDKMAN still offers. The plugin names them +--- after the version of the GraalVM they are built from, and SDKMAN adds the +--- JDK to some of those versions: 25.0.4.r25-nik is the plugin's 25.0.4-nik. +local sdkman_jdks = { + mandrel = true, + nik = true, +} + +--- Returns the JDK whose build of a Mandrel or Liberica NIK release the +--- plugin does not install, if any. Their releases from 21.3 to 23.0.1.2 come +--- for two JDKs, and the plugin installs the build for the newer one, so the +--- older one is 11 up to 22.3.5 (Mandrel 22.3 has no JDK 11 build at all) +--- and 17 from 23 to 23.0.1.2. +local function older_jdk(release) + if installed_version.newer("21.3", release) then + return nil + elseif installed_version.newer("23", release) then + return "11" + elseif installed_version.newer("23.0.2", release) then + return "17" + end + return nil +end + --- Rewrites an SDKMAN identifier in the plugin's spelling, which parse_version --- reads. SDKMAN calls JetBrains jbr (the plugin's jb) and puts fx in the --- version of JavaFX builds: 21.0.12.fx-zulu, or 21.0.12-fx+1.1-librca in its @@ -36,9 +60,10 @@ local sdkman_flavours = { --- not (28.0.0.0+ea.19-open), where SDKMAN used to write the plugin's --- 28.ea.19-open (Foojay's 28-ea+19). The VM or JDK of the distributions in --- sdkman_flavours becomes the short name (22.3.3.r17-grl is 22.3.3-grl17), ---- and nil comes back for one the plugin does not install (11.0.9.open-adpt, ---- 22.1.0.1.r11-gln). Any other spelling, the plugin's own included, is ---- returned unchanged. +--- the JDK of those in sdkman_jdks is dropped (25.0.4.r25-nik is +--- 25.0.4-nik), and nil comes back for a build the plugin does not install +--- (11.0.9.open-adpt, 22.1.0.1.r11-gln, 22.1.r11-mandrel). Any other +--- spelling, the plugin's own included, is returned unchanged. local function plugin_spelling(identifier) local version, distribution = identifier:match("^(.+)%-([^-]+)$") if not version then @@ -56,6 +81,13 @@ local function plugin_spelling(identifier) end version = flavoured end + local own_number, jdk = version:match("^(%d[%d.]*)%.r(%d+)$") + if sdkman_jdks[distribution] and own_number then + if jdk == older_jdk(own_number) then + return nil + end + version = own_number + end local suffix = "-" .. distribution local release, build = version:match("^(%d[%d.]*)[.-]fx(.*)$") if release then diff --git a/tests/e2e/common/common.go b/tests/e2e/common/common.go index 942158a..9c91fb2 100644 --- a/tests/e2e/common/common.go +++ b/tests/e2e/common/common.go @@ -794,12 +794,76 @@ func NewestOfMajor(searched []string, bare string) (newest string, ok bool) { } // ChecksOwnNumbers reports whether a combo also checks the distributions in -// OwnNumberedDistributions, which downloads nothing. Temurin's combos do: they +// OwnNumberedDistributions and SDKMAN's identifiers of them +// (OwnNumberIdentifiers), which downloads nothing. Temurin's combos do: they // run on every runner. func ChecksOwnNumbers(distribution string) bool { return Base(distribution) == "tem" } +// LegacyCase is a java= value of a .sdkmanrc and the installed version it +// must select, "" for none. +type LegacyCase struct { + Identifier, Selects string +} + +// ownNumberIdentifiers lists identifiers of Mandrel and Liberica NIK in +// SDKMAN's spelling, with the JDK after .r, and what each selects out of +// ownNumberInstalls: three that `sdk list java` shows (2026-10-20), and +// 22.1.r17-mandrel for the JDK 17 build of the release 22.1.r11-mandrel +// names. +var ownNumberIdentifiers = []LegacyCase{ + {"25.0.4.r25-nik", "25.0.4-nik"}, + {"25.0.2.r25-mandrel", "25.0.2-mandrel"}, + {"22.1.r17-mandrel", "22.1-mandrel"}, + {"22.1.r11-mandrel", ""}, +} + +// ownNumberInstalls holds the release each of ownNumberIdentifiers names and a +// newer release of its major. +var ownNumberInstalls = []string{ + "25.0.4-nik", "25.0.4.1-nik", "25.0.2-mandrel", "25.0.4.1-mandrel", "22.1-mandrel", "22.2-mandrel", +} + +// OwnNumberIdentifiers returns identifiers of Mandrel and Liberica NIK in +// SDKMAN's spelling, with the JDK after .r (25.0.4.r25-nik), each with what a +// .sdkmanrc holding it must select, and the installs to fake for them: the +// releases they name, each next to a newer release of its major +// (25.0.4.1-nik). ParseLegacyFile read 25.0.4.r25 as the release, found no +// such install and took that newer one (vfox-java#109). Mandrel 22.1 comes for +// JDK 11 and 17, and the plugin installs the build for 17 only, so +// 22.1.r11-mandrel must select nothing. +func OwnNumberIdentifiers() (cases []LegacyCase, installs []string) { + return ownNumberIdentifiers, ownNumberInstalls +} + +// printedNames returns those of names that output holds, in the order of +// names, joined by spaces. +func printedNames(output string, names []string) string { + var printed []string + for _, name := range names { + if strings.Contains(output, name) { + printed = append(printed, name) + } + } + return strings.Join(printed, " ") +} + +// CheckSelects fails t unless a .sdkmanrc holding c.Identifier selected what +// c.Selects names among installs, whose stand-in java prints its name: out is +// what java printed next to the file, nothing when none was selected. +func CheckSelects(t *testing.T, out string, c LegacyCase, installs []string) { + t.Helper() + selected := printedNames(out, installs) + if c.Selects == "" { + require.Emptyf(t, selected, ".sdkmanrc java=%s must select nothing\n--- output ---\n%s", c.Identifier, out) + t.Logf("PASS .sdkmanrc java=%s selects nothing", c.Identifier) + return + } + require.Equalf(t, c.Selects, selected, ".sdkmanrc java=%s must select %s\n--- output ---\n%s", c.Identifier, c.Selects, out) + t.Logf("PASS .sdkmanrc java=%s selects %s", c.Identifier, c.Selects) +} + // ContainerName returns a unique container name per run: the combo slug stays // greppable in `docker ps`, the random suffix avoids clashes with leftovers // from killed runs. diff --git a/tests/e2e/common/common_test.go b/tests/e2e/common/common_test.go index 872e31f..bb56904 100644 --- a/tests/e2e/common/common_test.go +++ b/tests/e2e/common/common_test.go @@ -16,6 +16,7 @@ package common import ( "slices" + "strconv" "strings" "testing" ) @@ -660,3 +661,79 @@ func TestNewestBareMajor(t *testing.T) { } } } + +// releaseNumbers returns the numbers of a release such as 25.0.4.1. +func releaseNumbers(release string) []int { + var numbers []int + for _, part := range strings.Split(release, ".") { + n, _ := strconv.Atoi(part) + numbers = append(numbers, n) + } + return numbers +} + +func TestOwnNumberIdentifiers(t *testing.T) { + cases, installs := OwnNumberIdentifiers() + none := 0 + for _, c := range cases { + release, rest, _ := strings.Cut(c.Identifier, ".r") + jdk, short, _ := strings.Cut(rest, "-") + if strings.Trim(release, "0123456789.") != "" || strings.Trim(jdk, "0123456789") != "" || + release == "" || jdk == "" { + t.Errorf("%s does not carry the JDK after .r", c.Identifier) + continue + } + major := Major(release) + if _, ok := ownNumbers[short]; !ok { + t.Errorf("%s is no identifier of Mandrel or Liberica NIK", c.Identifier) + } + // The plugin took the newest release of the major, so one newer than + // the release named must be faked too (any one, for an identifier + // that selects nothing), or the check passes by chance. + if !slices.ContainsFunc(installs, func(name string) bool { + core, ok := strings.CutSuffix(name, "-"+short) + return ok && Major(core) == major && + (c.Selects == "" || slices.Compare(releaseNumbers(core), releaseNumbers(release)) > 0) + }) { + t.Errorf("no release of %s-%s that the plugin took for %s is faked", major, short, c.Identifier) + } + if c.Selects == "" { + none++ + continue + } + if c.Selects != release+"-"+short || !slices.Contains(installs, c.Selects) { + t.Errorf("%s selects %s, not the faked install of the release it names", c.Identifier, c.Selects) + } + } + if none == 0 { + t.Error("no identifier must select nothing") + } + // Each stand-in java prints its name, which must hold no other one. + for _, a := range installs { + for _, b := range installs { + if a != b && strings.Contains(a, b) { + t.Errorf("%s holds %s, so their output cannot tell them apart", a, b) + } + } + } +} + +func TestPrintedNames(t *testing.T) { + _, installs := OwnNumberIdentifiers() + // The stand-in java of a faked install prints its name, and nothing + // prints when none is selected. + for _, ending := range lineEndings { + t.Run(ending.name, func(t *testing.T) { + for output, want := range map[string]string{ + "25.0.4-nik\n": "25.0.4-nik", + "25.0.4.1-mandrel\n": "25.0.4.1-mandrel", + "22.1-mandrel\n22.2-mandrel\n": "22.1-mandrel 22.2-mandrel", + "": "", + } { + if got := printedNames(ending.apply(output), installs); got != want { + t.Errorf("printedNames(%q) = %q, want %q", ending.apply(output), got, want) + } + } + }) + } +} diff --git a/tests/e2e/linux/e2e_test.go b/tests/e2e/linux/e2e_test.go index c5e56c8..27b6935 100644 --- a/tests/e2e/linux/e2e_test.go +++ b/tests/e2e/linux/e2e_test.go @@ -183,6 +183,9 @@ func runOne(t *testing.T, ctx context.Context, image, platform, arch string, c c verifySearch(ctx, t, ctr, c.Distribution, installed) // Before any `vfox use`, so java can only come from the legacy file. verifyLegacyFile(ctx, t, ctr, c.Distribution, version, installed, arch) + if common.ChecksOwnNumbers(c.Distribution) { + verifyOwnNumberLegacyFile(ctx, t, ctr) + } useJava(ctx, t, ctr, installed) verifyToolchain(ctx, t, ctr, c.Distribution, version, installed, arch) diff --git a/tests/e2e/linux/verify.go b/tests/e2e/linux/verify.go index a3af0b9..dd08adb 100644 --- a/tests/e2e/linux/verify.go +++ b/tests/e2e/linux/verify.go @@ -110,17 +110,49 @@ func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, distr execOK(ctx, t, ctr, "rm --recursive "+eaDir) } +// verifyOwnNumberLegacyFile proves a .sdkmanrc holding SDKMAN's identifiers +// of Mandrel and Liberica NIK that carry the JDK after .r (e.g. +// java=25.0.4.r25-nik) selects the release they name, not a newer one of its +// major, and nothing for the JDK 11 build of Mandrel 22.1, which the plugin +// does not install (see common.OwnNumberIdentifiers). The installs are faked, +// each with a stand-in java that only prints its name. It runs before any +// `vfox use`, so java can only come from the legacy file. +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) + 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) + } + execOK(ctx, t, ctr, "rm --recursive "+strings.Join(dirs, " ")) +} + // legacyJava writes java= to a .sdkmanrc and returns what java +// prints next to it. +func legacyJava(ctx context.Context, t *testing.T, ctr tc.Container, identifier string) string { + t.Helper() + return legacyRun(ctx, t, ctr, identifier, showSettings) +} + +// legacyRun writes java= to a .sdkmanrc and returns what command // prints next to it. The probe activates inside its own exec, a fresh shell // session, so no `vfox env` state cached by an earlier call can answer for // the file. -func legacyJava(ctx context.Context, t *testing.T, ctr tc.Container, identifier string) string { +func legacyRun(ctx context.Context, t *testing.T, ctr tc.Container, identifier, command string) string { t.Helper() const dir = "/tmp/e2e-legacy-sdkmanrc" execOK(ctx, t, ctr, fmt.Sprintf("mkdir --parents %s && printf 'java=%%s\\n' %s > %s/.sdkmanrc", dir, shQuote(identifier), dir)) return execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, fmt.Sprintf( - `cd %s && eval "$(vfox activate bash)" && eval "$(vfox env -s bash)" && %s`, dir, showSettings)) + `cd %s && eval "$(vfox activate bash)" && eval "$(vfox env -s bash)" && %s`, dir, command)) } // verifyToolchain runs java and javac through the activate hook and checks diff --git a/tests/e2e/windows/e2e_test.go b/tests/e2e/windows/e2e_test.go index 46bd395..595c058 100644 --- a/tests/e2e/windows/e2e_test.go +++ b/tests/e2e/windows/e2e_test.go @@ -260,6 +260,9 @@ func runOne(t *testing.T, ctx context.Context, image, platform, arch string, c c verifySearch(ctx, t, ctr, box, c.Distribution, installed) // Before any `vfox use`, so java can only come from the legacy file. verifyLegacyFile(ctx, t, ctr, box, c.Distribution, version, installed, arch) + if common.ChecksOwnNumbers(c.Distribution) { + verifyOwnNumberLegacyFile(ctx, t, ctr, box) + } useJava(ctx, t, ctr, box, activation, installed) verifyToolchain(ctx, t, ctr, box, activation, c.Distribution, version, installed, arch) diff --git a/tests/e2e/windows/verify.go b/tests/e2e/windows/verify.go index 39a057c..680b857 100644 --- a/tests/e2e/windows/verify.go +++ b/tests/e2e/windows/verify.go @@ -115,12 +115,46 @@ func verifyLegacyFile(ctx context.Context, t *testing.T, ctr tc.Container, box s execOK(ctx, t, ctr, fmt.Sprintf("Remove-Item -LiteralPath %s -Recurse -Force", eaDir), box.vars...) } +// verifyOwnNumberLegacyFile proves a .sdkmanrc holding SDKMAN's identifiers +// of Mandrel and Liberica NIK that carry the JDK after .r (e.g. +// java=25.0.4.r25-nik) selects the release they name, not a newer one of its +// major, and nothing for the JDK 11 build of Mandrel 22.1, which the plugin +// does not install (see common.OwnNumberIdentifiers). The installs are faked, +// each with a stand-in java (a java.cmd) that only prints its name. It runs +// before any `vfox use`, so java can only come from the legacy file. +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)) + 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)) + } + 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) + } + execOK(ctx, t, ctr, strings.Join(remove, "; "), box.vars...) +} + // legacyJava writes java= to a .sdkmanrc and returns what java +// prints next to it. +func legacyJava(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, identifier string) string { + t.Helper() + return legacyRun(ctx, t, ctr, box, identifier, "java "+showSettings) +} + +// legacyRun writes java= to a .sdkmanrc and returns what command // prints next to it. The probe activates its own PowerShell process. // Activation pins __VFOX_PID, which scopes the `vfox env` state cache, but // Windows may reuse a just-exited PID and legacy files are not among the // cache's tracked inputs, so the cache file is removed before the export. -func legacyJava(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, identifier string) string { +func legacyRun(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, identifier, command string) string { t.Helper() const dir = `C:\e2e-legacy-sdkmanrc` execOK(ctx, t, ctr, fmt.Sprintf( @@ -132,7 +166,7 @@ func legacyJava(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox "if ($env:__VFOX_CURTMPPATH) { Remove-Item -LiteralPath (Join-Path $env:__VFOX_CURTMPPATH 'env-state.json') -Force -ErrorAction SilentlyContinue }; "+ "$__vfoxExport = vfox env -s pwsh | Out-String; "+ "if ($__vfoxExport.Trim()) { Invoke-Expression -Command $__vfoxExport }; "+ - "java %s", pwshQuote(dir), showSettings) + "%s", pwshQuote(dir), command) return execRetry(ctx, t, ctr, ".sdkmanrc java="+identifier, script, box.vars...) } diff --git a/tests/parse_legacy_file_test.lua b/tests/parse_legacy_file_test.lua index 8a5a477..465290c 100644 --- a/tests/parse_legacy_file_test.lua +++ b/tests/parse_legacy_file_test.lua @@ -148,6 +148,31 @@ local cases = { { "java=21.0.8-rdht\n", { "21.0.8+9-rdht" }, "21.0.8+9-rdht" }, { "java=25.0.4-semcrt\n", { "25.0.4-semcrt", "25.0.2+10-sem" }, "25.0.4-semcrt" }, { "java=25.0.2-sem\n", { "25.0.4-semcrt", "25.0.2+10-sem" }, "25.0.2+10-sem" }, + -- SDKMAN writes the JDK into some versions of Mandrel and Liberica NIK as + -- well, which their installed names leave out (vfox-java#109). + -- ParseLegacyFile took 25.0.4.r25 for a release, found none and fell back + -- to the newest one of the major. A release from 21.3 to 23.0.1.2 comes + -- for two JDKs, of which the plugin installs the newer one's build, so the + -- older JDK selects nothing, as Gluon's JDK 11 does. + { "java=25.0.4.r25-nik\n", { "25.0.4-nik", "25.0.4.1-nik" }, "25.0.4-nik" }, + { "java=25.0.2.r25-mandrel\n", { "25.0.2-mandrel", "25.0.4.1-mandrel" }, "25.0.2-mandrel" }, + { "java=22.1.r17-mandrel\n", { "22.1-mandrel", "22.2-mandrel" }, "22.1-mandrel" }, + { "java=22.1.r11-mandrel\n", { "22.1-mandrel", "22.2-mandrel" }, nil }, + { "java=21.2.r11-nik\n", { "21.2-nik" }, "21.2-nik" }, + { "java=21.3.r11-nik\n", { "21.3-nik" }, nil }, + { "java=22.3.5.r11-nik\n", { "22.3.5-nik" }, nil }, + { "java=22.3.5.r17-nik\n", { "22.3.5-nik" }, "22.3.5-nik" }, + { "java=23.r17-nik\n", { "23-nik" }, nil }, + { "java=23.r20-nik\n", { "23-nik", "23.1.12.1-nik" }, "23-nik" }, + { "java=23.0.1.2.r17-mandrel\n", { "23.0.1.2-mandrel" }, nil }, + { "java=23.0.1.2.r20-mandrel\n", { "23.0.1.2-mandrel", "23.0.6-mandrel" }, "23.0.1.2-mandrel" }, + { "java=23.0.2.r17-nik\n", { "23.0.2-nik" }, "23.0.2-nik" }, + -- SDKMAN's 25.r25-mandrel (Mandrel 25.0.1.0) names a bare major, which + -- selects the newest release of it, as java@25-mandrel does. + { "java=25.r25-mandrel\n", { "25.0.1-mandrel", "25.0.4.1-mandrel" }, "25.0.4.1-mandrel" }, + -- 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" }, -- No java line at all selects nothing. { "maven=3.9.9\n", { "21.0.8+9-tem" }, nil }, }