diff --git a/docs/changes.md b/docs/changes.md index 23b5076..d5ae316 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -12,6 +12,7 @@ What changed in each release, as a user notices it. GitHub's release pages list - 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`. +- 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)). ## 0.6.2 (2026-10-18) diff --git a/docs/development.md b/docs/development.md index 50ee0aa..c10041b 100644 --- a/docs/development.md +++ b/docs/development.md @@ -51,12 +51,12 @@ SDKMAN spells some things differently (`jbr`, `21.0.12.fx-zulu`, `21.0.12-fx+1.1 ## Foojay - A `+` in a query has to be percent-encoded. Foojay reads a bare one as a space and drops the build, so `21.0.5+63130` would match every 21.0.5 build. -- `version=21` is the GA build `21+35`. A bare major adds `latest=available` to get the newest 21.x. +- `version=21` is the GA build `21+35`, so a bare major asks for every package, the question `vfox search` asks, and `lib/foojay.lua` keeps the major's packages, newest first as Foojay lists them. It does not use `latest=available`, which picks the newest 21.x: Foojay's answers are cached per question, and for half an hour after a release that one can hold a build a search does not list yet ([foojay.md](foojay.md#what-the-plugin-asks)). - Windows asks for `zip` and `tar.gz` in one request. JetBrains Runtime ships Windows zips for its newer builds only. `PreInstall` takes the zip when a version has both. - Elsewhere `other_archive_type` in `lib/foojay.lua` adds one archive type for the distributions that ship no tar.gz there for some or all releases: `tar.xz` for Red Hat, `zip` for OJDKBuild, OpenLogic and Gluon GraalVM. vfox unpacks both, and the zips carry their Unix permissions, so `bin/java` stays executable. -- `own_numbers` in `lib/foojay.lua` holds Mandrel and Liberica NIK, which carry the GraalVM version they are built from. Foojay gives it as `distribution_version` and the Java version as `java_version`, for Mandrel the major only, so these two are asked for with `distribution_version` and an empty `version`. Foojay matches the numbers given (`23.1.12` lists 23.1.12.1 too), so a longer version keeps only its own release, and a bare major takes the newest itself, as `latest=available` picks an older one for them. The answer is sorted newest first, then by the newest Java for a release that comes for several, then by BellSoft's later build for the same Java, which only the file name carries. `distribution_version` then goes into `java_version`, so the hooks name and list them like the rest. -- Oracle GraalVM and GraalVM Community go by GraalVM's own version, which Foojay has given as `distribution_version` since October 2026, with the JDK's as `java_version`. `graalvm_packages` in `lib/foojay.lua` asks for every package, the question `vfox search` asks, so that installing sees the builds a search lists ([foojay.md](foojay.md#the-versions-of-one-distribution)). It renames each package after GraalVM's version (`graalvm_version`: the newer of `java_version` and `distribution_version`, as GraalVM Community's feature releases are also listed the old way round), sorts them newest first, and keeps those of a bare major, or the release a longer version names, without its zero padding. For `vfox search` it keeps a download once, under the newer of its two names (`25.4.4.1.1`, not `25.4.4.1`). `latest=available` is not used for them, as it misses releases: on 2026-10-20 its answer for 25 on Windows had GraalVM `25.0.4.1` but not `25.0.4.1.1`, both Java 25.0.4.1. Early-access builds still go by `version`. -- `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. For a bare major `latest=available` answers with one package per archive type, which can be one the fix drops, so for these two the plugin then asks for that release in full. +- `own_numbers` in `lib/foojay.lua` holds Mandrel and Liberica NIK, which carry the GraalVM version they are built from. Foojay gives it as `distribution_version` and the Java version as `java_version`, for Mandrel the major only, so every package of these two is asked for, as for a search, and picked by `distribution_version`: a longer version keeps only its own release, and a bare major takes the newest of the major, as `latest=available` picks an older one for them. The answer is sorted newest first, then by the newest Java for a release that comes for several, then by BellSoft's later build for the same Java, which only the file name carries. `distribution_version` then goes into `java_version`, so the hooks name and list them like the rest. +- Oracle GraalVM and GraalVM Community go by GraalVM's own version, which Foojay has given as `distribution_version` since October 2026, with the JDK's as `java_version`. `graalvm_packages` in `lib/foojay.lua` asks for every package, the question `vfox search` asks, so that installing sees the builds a search lists ([foojay.md](foojay.md#what-the-plugin-asks)). It renames each package after GraalVM's version (`graalvm_version`: the newer of `java_version` and `distribution_version`, as GraalVM Community's feature releases are also listed the old way round), sorts them newest first, and keeps those of a bare major, or the release a longer version names, without its zero padding. For `vfox search` it keeps a download once, under the newer of its two names (`25.4.4.1.1`, not `25.4.4.1`). `latest=available` would miss releases there too: on 2026-10-20 its answer for 25 on Windows had GraalVM `25.0.4.1` but not `25.0.4.1.1`, both Java 25.0.4.1. Early-access builds still go by `version`. +- `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. @@ -74,7 +74,7 @@ go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/loongnix_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/post_install_test.lua ``` -Run them from the repository root. All five load `tests/support.lua`, which stubs `vfox.strings`, and `tests/pre_install_test.lua` and `tests/loongnix_test.lua` stub `http` and `json` themselves. `tests/pre_install_test.lua` keeps Foojay's answers in a table at the top, keyed by ` [] []`, with `windows` or `macos` in front for those systems. Add the answer a new case needs there, and note the day you observed it. Lint, license headers and the container tests are in [e2e.md](e2e.md#local-checks). +Run them from the repository root. All five load `tests/support.lua`, which stubs `vfox.strings`, and `tests/pre_install_test.lua` and `tests/loongnix_test.lua` stub `http` and `json` themselves. `tests/pre_install_test.lua` keeps Foojay's answers in a table at the top, keyed by ` []`, with `windows` or `macos` in front for those systems. Add the answer a new case needs there, and note the day you observed it. Lint, license headers and the container tests are in [e2e.md](e2e.md#local-checks). To call a single hook against the real Foojay, vfox 1.0.13 and later has `vfox plugin run`. It does not register the plugin, and `--os` and `--arch` answer for another platform: diff --git a/docs/e2e.md b/docs/e2e.md index 8097a35..8c1af20 100644 --- a/docs/e2e.md +++ b/docs/e2e.md @@ -93,10 +93,8 @@ JDK, so `java` can only come from what vfox installs: The builds of 25 that the search lists are faked as installed first, so `vfox install` names the one `PreInstall` picked and nothing downloads. GA 25 is left out, as vfox would find `25-graal` itself installed without - asking `PreInstall`. The search and `PreInstall` ask Foojay the same - question, so they see the same builds - ([foojay.md](foojay.md#the-versions-of-one-distribution)): a build that - only `PreInstall` saw would download and fail the check. + asking `PreInstall`. On Linux the install must also ask Foojay what the + search asked, as at the end of item 4. 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 @@ -117,6 +115,18 @@ JDK, so `java` can only come from what vfox installs: version in October 2026, the search listed one name per Java major, `23.1.12-mandrel` was not found, and `java@23-mandrel` installed Mandrel 24.1.2, built for Java 23. + + On Linux these rows, and the GraalVM Community rows of item 3, reach + Foojay through a stand-in for api.foojay.io, built from + `tests/e2e/linux/front`, which logs what the plugin asks. Each install + above must ask what the search before it asked (`AsksAsSearch` in + `tests/e2e/common`). Foojay's answers are cached per question, so for half + an hour after a release another question can take a build that the search + does not list yet, which would download and fail the check + ([foojay.md](foojay.md#what-the-plugin-asks)). Before + [#113](https://github.com/version-fox/vfox-java/issues/113) a bare major + asked for `latest=available`, and Mandrel and Liberica NIK by + `distribution_version`. 5. The pinned release installs under the name `PreInstall` gives it: the requested version plus the build number (`21.0.8-tem` installs as `21.0.8+9-tem`). `vfox search java ` (the `Available` hook) diff --git a/docs/foojay.md b/docs/foojay.md index 1030521..aca6bd8 100644 --- a/docs/foojay.md +++ b/docs/foojay.md @@ -24,16 +24,16 @@ https://api.foojay.io/disco/v3.0/packages/jdks | Parameter | Value | | --- | --- | | `distribution` | the long name, see [distributions.md](distributions.md) | -| `version` | empty for `vfox search`, else the version part of what you typed. A `+` is percent-encoded, because Foojay reads a bare one as a space and drops the build. `28.ea.19` becomes `28-ea+19`. Always empty for Mandrel and Liberica NIK, and for the GA releases of Oracle GraalVM and GraalVM Community | -| `distribution_version` | for Mandrel and Liberica NIK only, the version part of what you typed, see [below](#mandrel-and-liberica-nik) | +| `version` | empty for `vfox search` and for a bare major such as `21`, else the version part of what you typed. A `+` is percent-encoded, because Foojay reads a bare one as a space and drops the build. `28.ea.19` becomes `28-ea+19`. Always empty for Mandrel and Liberica NIK, and for the GA releases of Oracle GraalVM and GraalVM Community | | `architecture` | `aarch64` for arm64, `x64` on Windows x64, `amd64` for x64 elsewhere, and the name vfox reports for other CPUs | | `operating_system` | `linux`, `macos` or `windows` | | `archive_type` | `tar.gz`, and `zip` plus `tar.gz` on Windows. Elsewhere `tar.xz` comes in for Red Hat, and `zip` for OJDKBuild, OpenLogic and Gluon GraalVM | | `lib_c_type` | `glibc` or `musl` on Linux, empty elsewhere | | `directly_downloadable` | always `true` | -| `latest` | `available`, added only when the version is a bare major such as `21`, and never for Mandrel, Liberica NIK, Oracle GraalVM and GraalVM Community | -Without `latest=available`, `version=21` matches the 21 GA build only (`21+35`). With it Foojay picks the newest 21.x, which is what `java@21-tem` installs. Longer versions match as they are. +For a bare major such as `21` the answer lists every release, and the plugin takes the newest 21.x itself, which is what `java@21-tem` installs: `version=21` would match the 21 GA build only (`21+35`). Longer versions match as they are. Unreleased: release 0.6.2 asks for `version=21&latest=available` instead, with which Foojay picks the newest 21.x. + +A bare major asks what searching asks so that installing sees the builds `vfox search` lists. Foojay's answers are cached per question, and two questions can get answers of different ages: half an hour after Oracle GraalVM 25.0.5 reached Foojay on 2026-10-20, GitHub's runners got it from a lookup with `distribution_version=25` but not from a search, and `vfox install java@25-graal` took a build that `vfox search java graal` did not list ([#113](https://github.com/version-fox/vfox-java/issues/113)). The answer is a list of packages. The plugin names a release after `java_version` (Mandrel and Liberica NIK after `distribution_version`, Oracle GraalVM and GraalVM Community after GraalVM's own version), adds `-fx` where `javafx_bundled` is true, marks it LTS where `term_of_support` is `lts`, and prefers a `zip` over a `tar.gz` of the same version on Windows. It leaves out 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, see [distributions.md](distributions.md). For the install it then fetches the package's `links.pkg_info_uri` and downloads its `direct_download_uri`, which is a vendor's own server (GitHub Releases for Temurin). That record also holds a `checksum`. The plugin does not pass it on, so vfox prints `WARNING: Checksum is not provided, skip verify...` and does not verify the download. @@ -53,7 +53,7 @@ curl -s 'https://api.foojay.io/disco/v3.0/packages/jdks?distribution=mandrel&dis 21 23.1.12 mandrel-java21-linux-amd64-23.1.12.0-Final.tar.gz ``` -So the plugin asks for them with `distribution_version` and an empty `version`. Foojay matches the numbers given, which is why `23.1.12` also lists 23.1.12.1, and the plugin keeps only the release named. For a bare major it picks the newest release in the answer itself, as `latest=available` does not: for `distribution_version=23` it answered with 23.1.8 and two builds of 23.0.1.2, although 23.1.12.1 was out. Where a release comes for several Java versions the plugin takes the build for the newest, and of two Liberica NIK builds for the same Java the later one, which only the file name tells apart (`...-25.0.3+2-linux-amd64.tar.gz`). Searching sorts them by `distribution_version`, newest first. +So the plugin asks for every package of these two, as `vfox search` does, and picks the release by `distribution_version` itself: the one named, or for a bare major the newest of the major. Foojay's own matching does neither: `distribution_version=23.1.12` lists 23.1.12.1 as well, as above, and with `distribution_version=23`, `latest=available` answered with 23.1.8 and two builds of 23.0.1.2, although 23.1.12.1 was out. Where a release comes for several Java versions the plugin takes the build for the newest, and of two Liberica NIK builds for the same Java the later one, which only the file name tells apart (`...-25.0.3+2-linux-amd64.tar.gz`). Searching sorts them by `distribution_version`, newest first. ## Which distributions have a build for my platform @@ -140,8 +140,6 @@ curl -s 'https://api.foojay.io/disco/v3.0/packages/jdks?distribution=graalvm&ver `version` cannot tell those apart, and `distribution_version` matches whole leading components (`25.0.4.1` also lists `25.0.4.1.1`). So the plugin asks for every package, as `vfox search` does, picks the release itself, and names it after GraalVM's version: the newer of a package's `java_version` and `distribution_version`, as GraalVM Community's feature releases are also listed the old way round, with GraalVM's version as `java_version`. `vfox search` lists them newest first by that version, and a file that Foojay lists under two versions once. -Installing asks what searching asks so that the two see the same builds. Foojay's answers are cached per question, and two questions can get answers of different ages: half an hour after GraalVM 25.0.5 reached Foojay on 2026-10-20, GitHub's runners got it from a lookup with `distribution_version=25` but not from a search. - ## Releases and support terms ```bash diff --git a/docs/zh-CN/changes.md b/docs/zh-CN/changes.md index 4a46332..1b16dd2 100644 --- a/docs/zh-CN/changes.md +++ b/docs/zh-CN/changes.md @@ -12,6 +12,7 @@ - `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` 为止。 +- 只写大版本号(如 `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))。 ## 0.6.2(2026-10-18) diff --git a/docs/zh-CN/development.md b/docs/zh-CN/development.md index 556f45a..3598c29 100644 --- a/docs/zh-CN/development.md +++ b/docs/zh-CN/development.md @@ -51,12 +51,12 @@ SDKMAN 有些写法不同(`jbr`、`21.0.12.fx-zulu`、`21.0.12-fx+1.1-librca` ## Foojay - 查询里的 `+` 必须做百分号编码。Foojay 会把没编码的 `+` 当成空格并丢掉构建号,`21.0.5+63130` 就会匹配 21.0.5 的所有构建。 -- `version=21` 只是 GA 构建 `21+35`。只写大版本号时会加上 `latest=available`,才能拿到最新的 21.x。 +- `version=21` 只是 GA 构建 `21+35`,所以只写大版本号时和 `vfox search` 发同一个请求,查出全部的包,再由 `lib/foojay.lua` 留下这个大版本的包,顺序沿用 Foojay 的从新到旧。不用 `latest=available` 挑最新的 21.x,因为 Foojay 的结果按请求分别缓存,新版本发布后的半小时里,那个请求的结果里可能有搜索还没列出的构建(见 [foojay.md](foojay.md#插件发出的请求))。 - Windows 在同一次请求里同时要 `zip` 和 `tar.gz`。JetBrains Runtime 只为较新的构建提供 Windows 的 zip。同一版本两种都有时,`PreInstall` 取 zip。 - 在其他系统上,有几个发行版的部分或全部版本没有 tar.gz,`lib/foojay.lua` 里的 `other_archive_type` 会为它们多要一种格式:Red Hat 要 `tar.xz`,OJDKBuild、OpenLogic 和 Gluon GraalVM 要 `zip`。这两种 vfox 都能解压,这些 zip 包也带着 Unix 权限,所以 `bin/java` 仍然可以执行。 -- `lib/foojay.lua` 里的 `own_numbers` 列的是 Mandrel 和 Liberica NIK,它们沿用所基于的 GraalVM 的版本号。Foojay 把这个版本号放在 `distribution_version` 里,把 Java 的版本号放在 `java_version` 里(Mandrel 的 `java_version` 只有大版本号),所以查这两个发行版时用 `distribution_version`,`version` 留空。Foojay 按给出的几段数字匹配(查 `23.1.12` 也会返回 23.1.12.1),所以写得更具体的版本,插件只保留与它完全相同的那个;只写大版本号时由插件自己挑最新的,因为 `latest=available` 对它们会挑出旧版本。结果按版本从新到旧排;同一版本有几个 Java 版本的构建时,Java 最新的在前;为同一个 Java 构建了两次时,BellSoft 后一次的构建在前,两次构建只能从文件名区分。之后 `distribution_version` 写进 `java_version`,钩子就能像对其他发行版一样给它们命名、列出它们。 -- Oracle GraalVM 和 GraalVM 社区版按 GraalVM 自己的版本号命名和查找。从 2026 年 10 月起,Foojay 把它放在 `distribution_version` 里,`java_version` 则是 JDK 的版本号。`lib/foojay.lua` 里的 `graalvm_packages` 和 `vfox search` 发同一个请求,查出全部的包,这样安装时看到的构建和搜索列出的一致(见 [foojay.md](foojay.md#某个发行版的所有版本))。它把每个包改用 GraalVM 的版本号命名(`graalvm_version`:取 `java_version` 和 `distribution_version` 中较新的那个,因为 GraalVM 社区版的特性版本还有一份按老办法列的记录),按从新到旧排序;只写大版本号时留下这个大版本的全部包,更具体的版本则只留下它指的那一个,补的零不算。给 `vfox search` 用时,同一个下载只留一条,用两个名字中较新的那个(`25.4.4.1.1`,不是 `25.4.4.1`)。查这两个发行版时不加 `latest=available`,因为它会漏掉版本:2026-10-20 在 Windows 上查 25,结果里有 GraalVM `25.0.4.1`,却没有 `25.0.4.1.1`,两者的 Java 版本都是 25.0.4.1。EA 构建仍按 `version` 查。 -- `lib/foojay.lua` 里的 `fixes` 会在钩子拿到结果之前去掉一些包:Foojay 列在 OJDKBuild 的 Windows JDK 里的 JRE、installer bundle 包和 RHEL 6 构建,以及 Gluon GraalVM 22.x 的 JDK 11 构建,它们和 JDK 17 构建共用一个版本号。只写大版本号时,`latest=available` 每种格式只返回一个包,这个包可能正好是要去掉的,所以对这两个发行版,插件会再按那个版本完整地查一次。 +- `lib/foojay.lua` 里的 `own_numbers` 列的是 Mandrel 和 Liberica NIK,它们沿用所基于的 GraalVM 的版本号。Foojay 把这个版本号放在 `distribution_version` 里,把 Java 的版本号放在 `java_version` 里(Mandrel 的 `java_version` 只有大版本号),所以查这两个发行版时和搜索一样查出全部的包,再按 `distribution_version` 挑:写得更具体的版本,只保留与它完全相同的那个;只写大版本号时挑这个大版本里最新的,因为 `latest=available` 对它们会挑出旧版本。结果按版本从新到旧排;同一版本有几个 Java 版本的构建时,Java 最新的在前;为同一个 Java 构建了两次时,BellSoft 后一次的构建在前,两次构建只能从文件名区分。之后 `distribution_version` 写进 `java_version`,钩子就能像对其他发行版一样给它们命名、列出它们。 +- Oracle GraalVM 和 GraalVM 社区版按 GraalVM 自己的版本号命名和查找。从 2026 年 10 月起,Foojay 把它放在 `distribution_version` 里,`java_version` 则是 JDK 的版本号。`lib/foojay.lua` 里的 `graalvm_packages` 和 `vfox search` 发同一个请求,查出全部的包,这样安装时看到的构建和搜索列出的一致(见 [foojay.md](foojay.md#插件发出的请求))。它把每个包改用 GraalVM 的版本号命名(`graalvm_version`:取 `java_version` 和 `distribution_version` 中较新的那个,因为 GraalVM 社区版的特性版本还有一份按老办法列的记录),按从新到旧排序;只写大版本号时留下这个大版本的全部包,更具体的版本则只留下它指的那一个,补的零不算。给 `vfox search` 用时,同一个下载只留一条,用两个名字中较新的那个(`25.4.4.1.1`,不是 `25.4.4.1`)。`latest=available` 在这里也会漏掉版本:2026-10-20 在 Windows 上查 25,结果里有 GraalVM `25.0.4.1`,却没有 `25.0.4.1.1`,两者的 Java 版本都是 25.0.4.1。EA 构建仍按 `version` 查。 +- `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 社区版不做这个过滤,因为特性版本就是它现在发布的全部。 @@ -74,7 +74,7 @@ go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/loongnix_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/post_install_test.lua ``` -请在仓库根目录运行。五个文件都会加载 `tests/support.lua`,它模拟 `vfox.strings`;`tests/pre_install_test.lua` 和 `tests/loongnix_test.lua` 还自己模拟了 `http` 和 `json`。`tests/pre_install_test.lua` 把 Foojay 的返回结果放在文件开头的一张表里,键是 `<发行版> [<版本>] []`,Windows 和 macOS 的前面再分别加 `windows` 和 `macos`。新增用例需要的返回结果就加在那里,并注明观察到的日期。Lint、许可证头和容器测试见 [e2e.md](e2e.md#本地检查)。 +请在仓库根目录运行。五个文件都会加载 `tests/support.lua`,它模拟 `vfox.strings`;`tests/pre_install_test.lua` 和 `tests/loongnix_test.lua` 还自己模拟了 `http` 和 `json`。`tests/pre_install_test.lua` 把 Foojay 的返回结果放在文件开头的一张表里,键是 `<发行版> [<版本>]`,Windows 和 macOS 的前面再分别加 `windows` 和 `macos`。新增用例需要的返回结果就加在那里,并注明观察到的日期。Lint、许可证头和容器测试见 [e2e.md](e2e.md#本地检查)。 想对着真实的 Foojay 调用单个钩子,可以用 vfox 1.0.13 及以后版本的 `vfox plugin run`。它不会注册插件,`--os` 和 `--arch` 可以模拟其他平台: diff --git a/docs/zh-CN/e2e.md b/docs/zh-CN/e2e.md index f5fb1da..f477df1 100644 --- a/docs/zh-CN/e2e.md +++ b/docs/zh-CN/e2e.md @@ -32,10 +32,12 @@ 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`。搜索和 `PreInstall` 向 Foojay 发的是同一个请求,所以两者看到的构建相同(见 [foojay.md](foojay.md#某个发行版的所有版本)):要是某个构建只有 `PreInstall` 看到了,它就会真的被下载,检查也会失败。 +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 条末尾。 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。 + + 在 Linux 上,这几行和第 3 条的 `graalce` 行经由一个冒充 api.foojay.io 的替身访问 Foojay(由 `tests/e2e/linux/front` 构建),替身记下插件发出的每个请求。上面每次安装发的请求都必须和它前面那次搜索的相同(`tests/e2e/common` 里的 `AsksAsSearch`)。Foojay 的结果按请求分别缓存,新版本发布后的半小时里,换一个请求就可能装上搜索还没列出的构建,于是真的下载,检查失败(见 [foojay.md](foojay.md#插件发出的请求))。[#113](https://github.com/version-fox/vfox-java/issues/113) 之前,只写大版本号时查的是 `latest=available`,Mandrel 和 Liberica NIK 则按 `distribution_version` 查。 5. 固定的版本按 `PreInstall` 给它起的名字安装:请求的版本加上构建号(`21.0.8-tem` 安装为 `21.0.8+9-tem`)。`vfox search java <发行版>`(`Available` 钩子)把同一个名字列为已安装,带 JavaFX 的构建会标注 JavaFX。对 GraalVM,它还必须列出自 25.1 起每月发布的特性版本(`25.4.4.1.1-graalce`),这些版本都不标 LTS(Foojay 给其中一些标了,因为它们基于的 JDK 是 LTS);每个版本大约只存在一个月。同一个大版本里的其他版本,备注必须一致。插件以前把特性版本 25.4.4.1.1 列成 `25.0.4.1-graalce`,没有 `25.0.2-graalce` 那样的 LTS 备注。在 Windows 上,列表里要包含 JetBrains Runtime 21.0.5,它在 Windows 上只有 tar.gz。 6. 在执行任何 `vfox use` 之前,`.sdkmanrc` 要能通过 `ParseLegacyFile` 选中 JDK。测试时,`vfox list java` 里排在最前面的是另一个版本的空安装(`21.0.80+1-tem`,它的版本号以固定的版本开头;vfox 按字符串降序给已安装版本排序)。以前 `ParseLegacyFile` 会把它当成第一个以该版本开头的版本,对补零的版本(见下面第二条),则当成该大版本里最新的版本。要覆盖的写法有: - 不含构建号的 SDKMAN 标识符:`java=21.0.8-tem`、`java=21.0.5-jbr`(SDKMAN 对 JetBrains 的叫法)、`java=25.0.4.fx-librca`(它对 JavaFX 构建的写法)。 diff --git a/docs/zh-CN/foojay.md b/docs/zh-CN/foojay.md index 7f090be..01d346f 100644 --- a/docs/zh-CN/foojay.md +++ b/docs/zh-CN/foojay.md @@ -24,16 +24,16 @@ https://api.foojay.io/disco/v3.0/packages/jdks | 参数 | 取值 | | --- | --- | | `distribution` | 发行版的长名,见 [distributions.md](distributions.md) | -| `version` | `vfox search` 时为空,否则是你输入内容里的版本部分。`+` 会做百分号编码,因为 Foojay 会把没编码的 `+` 当成空格,进而丢掉构建号。`28.ea.19` 会变成 `28-ea+19`。查 Mandrel、Liberica NIK,以及 Oracle GraalVM 和 GraalVM 社区版的 GA 版本时始终为空 | -| `distribution_version` | 只用于 Mandrel 和 Liberica NIK,是你输入内容里的版本部分,见[下文](#mandrel-与-liberica-nik) | +| `version` | `vfox search` 时和只写大版本号(如 `21`)时为空,否则是你输入内容里的版本部分。`+` 会做百分号编码,因为 Foojay 会把没编码的 `+` 当成空格,进而丢掉构建号。`28.ea.19` 会变成 `28-ea+19`。查 Mandrel、Liberica NIK,以及 Oracle GraalVM 和 GraalVM 社区版的 GA 版本时始终为空 | | `architecture` | arm64 用 `aarch64`,Windows x64 用 `x64`,其他系统的 x64 用 `amd64`,其他 CPU 用 vfox 报告的名字 | | `operating_system` | `linux`、`macos` 或 `windows` | | `archive_type` | `tar.gz`;Windows 上是 `zip` 加 `tar.gz`。其他系统上,Red Hat 还要 `tar.xz`,OJDKBuild、OpenLogic 和 Gluon GraalVM 还要 `zip` | | `lib_c_type` | Linux 上是 `glibc` 或 `musl`,其他系统为空 | | `directly_downloadable` | 始终是 `true` | -| `latest` | `available`,仅当版本只有大版本号(如 `21`)时才加;查 Mandrel、Liberica NIK、Oracle GraalVM 和 GraalVM 社区版时一律不加 | -不加 `latest=available` 时,`version=21` 只匹配 21 的 GA 构建(`21+35`);加上之后,Foojay 会挑出最新的 21.x,`java@21-tem` 装的就是它。更具体的版本则按原样匹配。 +只写大版本号(如 `21`)时,返回结果里有全部版本,由插件自己挑出最新的 21.x,`java@21-tem` 装的就是它:`version=21` 只会匹配 21 的 GA 构建(`21+35`)。更具体的版本则按原样匹配。未发布:0.6.2 查的是 `version=21&latest=available`,由 Foojay 挑出最新的 21.x。 + +只写大版本号时和搜索发同一个请求,这样安装时看到的构建和 `vfox search` 列出的才一致。Foojay 的结果按请求分别缓存,不同的请求拿到的结果可能新旧不一:2026-10-20 Oracle GraalVM 25.0.5 上了 Foojay 半小时后,GitHub 的 runner 用 `distribution_version=25` 查已经有它,搜索的结果里却还没有,`vfox install java@25-graal` 装上了 `vfox search java graal` 没有列出的构建([#113](https://github.com/version-fox/vfox-java/issues/113))。 返回的是一个包列表。插件用 `java_version` 给版本命名(Mandrel 和 Liberica NIK 用 `distribution_version`,Oracle GraalVM 和 GraalVM 社区版用 GraalVM 自己的版本号);`javafx_bundled` 为真时加上 `-fx`;`term_of_support` 是 `lts` 时标注 LTS;在 Windows 上,同一版本既有 `zip` 又有 `tar.gz` 时优先用 `zip`。Foojay 混在 OJDKBuild 的 Windows JDK 里的 JRE、installer bundle 包和 RHEL 6 构建,以及 Gluon GraalVM 22.x 的 JDK 11 构建,插件都会略过,见 [distributions.md](distributions.md)。安装时,插件再取这个包的 `links.pkg_info_uri`,并从其中的 `direct_download_uri` 下载,那是厂商自己的服务器(Temurin 是 GitHub Releases)。这条记录里还有 `checksum`,但插件没有把它交给 vfox,所以 vfox 会打印 `WARNING: Checksum is not provided, skip verify...`,下载内容不做校验。 @@ -53,7 +53,7 @@ curl -s 'https://api.foojay.io/disco/v3.0/packages/jdks?distribution=mandrel&dis 21 23.1.12 mandrel-java21-linux-amd64-23.1.12.0-Final.tar.gz ``` -所以插件查这两个发行版时用 `distribution_version`,`version` 留空。Foojay 按给出的几段数字匹配,所以查 `23.1.12` 也会返回 23.1.12.1,插件只保留指定的那个版本。只写大版本号时,插件自己从结果里挑最新的版本,因为 `latest=available` 挑不对:查 `distribution_version=23` 时,它返回的是 23.1.8 和 23.0.1.2 的两个构建,而 23.1.12.1 已经发布。同一个版本有几个 Java 版本的构建时,插件取 Java 最新的那个;Liberica NIK 为同一个 Java 构建了两次时,取后一次的,两次构建只能从文件名里区分(`...-25.0.3+2-linux-amd64.tar.gz`)。搜索时按 `distribution_version` 从新到旧排列。 +所以插件像 `vfox search` 那样查出这两个发行版的全部包,再按 `distribution_version` 自己挑:写得具体时挑指定的那个版本,只写大版本号时挑这个大版本里最新的。Foojay 自己的匹配两样都做不到:如上所示,查 `distribution_version=23.1.12` 也会返回 23.1.12.1;查 `distribution_version=23` 并加上 `latest=available` 时,返回的是 23.1.8 和 23.0.1.2 的两个构建,而 23.1.12.1 已经发布。同一个版本有几个 Java 版本的构建时,插件取 Java 最新的那个;Liberica NIK 为同一个 Java 构建了两次时,取后一次的,两次构建只能从文件名里区分(`...-25.0.3+2-linux-amd64.tar.gz`)。搜索时按 `distribution_version` 从新到旧排列。 ## 我的平台上有哪些发行版 @@ -140,8 +140,6 @@ curl -s 'https://api.foojay.io/disco/v3.0/packages/jdks?distribution=graalvm&ver 用 `version` 分不开这几个包,而 `distribution_version` 按开头的完整几段来匹配(`25.0.4.1` 也会列出 `25.0.4.1.1`)。所以插件像 `vfox search` 那样查出全部的包,具体是哪个版本由它自己挑,并按 GraalVM 的版本号命名,也就是每个包的 `java_version` 和 `distribution_version` 中较新的那个:GraalVM 社区版的特性版本还有一份按老办法列的记录,GraalVM 的版本号放在 `java_version` 里。`vfox search` 按这个版本号从新到旧列出,Foojay 在两个版本号下各列一次的同一个文件,只列一次。 -安装和搜索发的是同一个请求,这样两者看到的构建才一致。Foojay 的结果按请求分别缓存,不同的请求拿到的结果可能新旧不一:2026-10-20 GraalVM 25.0.5 上了 Foojay 半小时后,GitHub 的 runner 用 `distribution_version=25` 查已经有它,搜索的结果里却还没有。 - ## 版本与支持期限 ```bash diff --git a/lib/foojay.lua b/lib/foojay.lua index ff7f70a..e32c3ef 100644 --- a/lib/foojay.lua +++ b/lib/foojay.lua @@ -163,11 +163,11 @@ end --- installing in step with searching: Foojay's answers are cached per --- question, and half an hour after GraalVM 25.0.5 came out, a search still --- left it out while a lookup by distribution_version had it. ---- @param request function Foojay lookup taking the version and more query +--- @param request function Foojay lookup taking the version --- @param version string --- @return table local function graalvm_packages(request, version) - local packages = request("", "") or {} + local packages = request("") or {} local order = {} for i, package in ipairs(packages) do package.java_version = graalvm_version(package) @@ -241,13 +241,8 @@ foojay.fetchtJdkList = function(distribution, version) lib_c_type = detect_lib_c_type() end - -- A bare major such as 21 matches only its GA build (21+35); latest=available - -- picks the newest 21.x instead. Longer versions keep matching exactly, as - -- latest=available would look at the major alone. - local latest = version:match("^%d+$") and "&latest=available" or "" - - local function request(query, extra) - local reqUrl = URL:format(encode(query), distribution, arch, archive_types, os, lib_c_type) .. extra + local function request(query) + local reqUrl = URL:format(encode(query), distribution, arch, archive_types, os, lib_c_type) local resp, err = http.get({ url = reqUrl, @@ -269,21 +264,33 @@ foojay.fetchtJdkList = function(distribution, version) return graalvm_packages(request, version) end + -- A bare major such as 21 asks for every package, as a search does, and + -- the major's packages are picked here. version=21 alone matches the GA + -- build only (21+35), and latest=available, which picks the newest 21.x, + -- is another question, which Foojay's cache answers on its own: for half + -- an hour after a release, it could install a build that a search leaves + -- out. + local major = version:match("^%d+$") + if own_numbers[distribution] then - -- Foojay matches distribution_version by the numbers given, so 23.1.12 - -- lists 23.1.12.1 as well, and its latest=available takes an older - -- release (23.1.8 for 23.1). A bare major takes the newest release - -- itself, and a longer version the release it names, whose zeros - -- Foojay leaves out (23.1.12.0 is 23.1.12). - local bare = version == "" or version:match("^%d+$") + -- Every package is asked for whatever the version, as Foojay's version + -- parameter matches the JDK, and the releases are picked by the number + -- they go by: a bare major takes the newest release of the major, as + -- latest=available takes an older one (23.1.8 for 23.1), and a longer + -- version the release it names, whose zeros Foojay leaves out + -- (23.1.12.0 is 23.1.12). local release = installed_version.unpad(version) + local function wanted(number) + if version == "" then + return true + elseif major then + return number:match("^%d+") == major + end + return installed_version.unpad(number) == release + end local result = {} - local query = version ~= "" and "&distribution_version=" .. encode(version) or "" - for _, package in ipairs(request("", query) or {}) do - if - package.distribution_version - and (bare or installed_version.unpad(package.distribution_version) == release) - then + for _, package in ipairs(request("") or {}) do + if package.distribution_version and wanted(package.distribution_version) then table.insert(result, package) end end @@ -294,17 +301,20 @@ foojay.fetchtJdkList = function(distribution, version) return result end - local result = request(version, latest) + local result = request(major and "" or version) or {} local fix = fixes[distribution] - if fix and result then - -- latest=available answers with one package of the newest release - -- per archive type, which may be one the fix drops (OJDKBuild's - -- 17.0.3 JRE on Windows): ask for that release in full. - if latest ~= "" and result[1] then - result = request(result[1].java_version, "") or {} - end + if fix then result = fix(result) end + if major then + local packages = {} + for _, package in ipairs(result) do + if package.java_version:match("^%d+") == major then + table.insert(packages, package) + end + end + result = packages + end return result end diff --git a/tests/e2e/common/common.go b/tests/e2e/common/common.go index 9c91fb2..726c84c 100644 --- a/tests/e2e/common/common.go +++ b/tests/e2e/common/common.go @@ -864,6 +864,37 @@ func CheckSelects(t *testing.T, out string, c LegacyCase, installs []string) { t.Logf("PASS .sdkmanrc java=%s selects %s", c.Identifier, c.Selects) } +// JdkLookups picks, out of the log of the Linux suite's stand-in for +// api.foojay.io (tests/e2e/linux/front), the lookups of packages, path and +// query, in the order they were asked. +func JdkLookups(log string) []string { + var lookups []string + for _, line := range strings.Split(log, "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "/disco/v3.0/packages/jdks?") { + lookups = append(lookups, line) + } + } + return lookups +} + +// AsksAsSearch reports an error unless every lookup of an install is the +// last one of the search before it. Foojay's answers are cached per +// question, so after a release another question could take a build the +// search does not list yet (vfox-java#113). +func AsksAsSearch(searched, installed []string) error { + if len(searched) == 0 || len(installed) == 0 { + return fmt.Errorf("the search asked Foojay %q and the install %q", searched, installed) + } + want := searched[len(searched)-1] + for _, lookup := range installed { + if lookup != want { + return fmt.Errorf("the install asked Foojay %s, the search %s", lookup, want) + } + } + return nil +} + // 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 bb56904..f8f8b29 100644 --- a/tests/e2e/common/common_test.go +++ b/tests/e2e/common/common_test.go @@ -737,3 +737,49 @@ func TestPrintedNames(t *testing.T) { }) } } + +func TestJdkLookups(t *testing.T) { + // The front's log after its readiness check, `vfox search java mandrel` + // and `vfox install java@23-mandrel` before vfox-java#113, which fetched + // the package's record as well. + log := `/disco/v3.0/distributions +/disco/v3.0/packages/jdks?version=&distribution=mandrel&architecture=amd64&archive_type=tar.gz&operating_system=linux&lib_c_type=glibc&directly_downloadable=true +/disco/v3.0/packages/jdks?version=&distribution=mandrel&architecture=amd64&archive_type=tar.gz&operating_system=linux&lib_c_type=glibc&directly_downloadable=true&distribution_version=23 +/disco/v3.0/ids/4d2b4ad0dbd6fea8ba1a5c4f1ea1e5bd +` + want := []string{ + "/disco/v3.0/packages/jdks?version=&distribution=mandrel&architecture=amd64&archive_type=tar.gz&operating_system=linux&lib_c_type=glibc&directly_downloadable=true", + "/disco/v3.0/packages/jdks?version=&distribution=mandrel&architecture=amd64&archive_type=tar.gz&operating_system=linux&lib_c_type=glibc&directly_downloadable=true&distribution_version=23", + } + if got := JdkLookups(log); !slices.Equal(got, want) { + t.Errorf("JdkLookups = %q, want %q", got, want) + } + if got := JdkLookups(""); len(got) != 0 { + t.Errorf("JdkLookups of an empty log = %q", got) + } +} + +func TestAsksAsSearch(t *testing.T) { + search := "/disco/v3.0/packages/jdks?version=&distribution=temurin&architecture=amd64&archive_type=tar.gz&operating_system=linux&lib_c_type=glibc&directly_downloadable=true" + latest := "/disco/v3.0/packages/jdks?version=21&distribution=temurin&architecture=amd64&archive_type=tar.gz&operating_system=linux&lib_c_type=glibc&directly_downloadable=true&latest=available" + for _, c := range []struct { + searched, installed []string + ok bool + }{ + {[]string{search}, []string{search}, true}, + // A retried search or install asks again. + {[]string{search, search}, []string{search, search}, true}, + // Before vfox-java#113 a bare major asked with latest=available. + {[]string{search}, []string{latest}, false}, + {[]string{search}, []string{search, latest}, false}, + {[]string{latest, search}, []string{latest}, false}, + // Nothing to compare: vfox answered the search from its cache, or + // the install did not get to PreInstall. + {nil, []string{search}, false}, + {[]string{search}, nil, false}, + } { + if err := AsksAsSearch(c.searched, c.installed); (err == nil) != c.ok { + t.Errorf("AsksAsSearch(%q, %q) = %v, want ok %v", c.searched, c.installed, err, c.ok) + } + } +} diff --git a/tests/e2e/linux/e2e_test.go b/tests/e2e/linux/e2e_test.go index 27b6935..7e9c548 100644 --- a/tests/e2e/linux/e2e_test.go +++ b/tests/e2e/linux/e2e_test.go @@ -64,6 +64,7 @@ func TestE2E(t *testing.T) { ctx := context.Background() buildImage(t, ctx, repoRoot, image, platform) + front := buildFront(t, here, arch) combos, err := common.Matrix() if err != nil { @@ -80,7 +81,7 @@ func TestE2E(t *testing.T) { t.Parallel() sem <- struct{}{} defer func() { <-sem }() - runOne(t, ctx, image, platform, arch, c) + runOne(t, ctx, image, platform, arch, front, c) }) } } @@ -127,7 +128,7 @@ func buildImage(t *testing.T, ctx context.Context, repoRoot, image, platform str // E2E flow (setup -> install -> verify) through exec calls, asserting in Go. // A combo passes when every phase exits 0 and the outputs carry the expected // markers. -func runOne(t *testing.T, ctx context.Context, image, platform, arch string, c common.Combo) { +func runOne(t *testing.T, ctx context.Context, image, platform, arch string, front frontFiles, c common.Combo) { t.Helper() prefix := c.Prefix(platform) @@ -169,6 +170,13 @@ func runOne(t *testing.T, ctx context.Context, image, platform, arch string, c c checkNotes(ctx, t, ctr) checkVersionlessRejected(ctx, t, ctr, c.Distribution) checkMissingVersionRejected(ctx, t, ctr, c.Distribution, version, arch) + // The checks that compare a search with an install read what each asked + // Foojay through a stand-in for api.foojay.io. + comparesLookups := common.ChecksOracleGraalVM(c.Distribution) || + common.ChecksNameOnly(c.Distribution) || common.ChecksOwnNumbers(c.Distribution) + if comparesLookups { + startFront(ctx, t, ctr, front) + } if common.ChecksOracleGraalVM(c.Distribution) { checkGraalVMBareMajor(ctx, t, ctr) } @@ -178,6 +186,9 @@ func runOne(t *testing.T, ctx context.Context, image, platform, arch string, c c if common.ChecksOwnNumbers(c.Distribution) { checkOwnNumbers(ctx, t, ctr, platform) } + if comparesLookups { + stopFront(ctx, t, ctr) + } installed := installJava(ctx, t, ctr, common.Spec(c.Distribution, version)) verifySearch(ctx, t, ctr, c.Distribution, installed) diff --git a/tests/e2e/linux/front.go b/tests/e2e/linux/front.go new file mode 100644 index 0000000..2790b63 --- /dev/null +++ b/tests/e2e/linux/front.go @@ -0,0 +1,142 @@ +// Copyright 2026 Han Li and contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package linux + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + tc "github.com/testcontainers/testcontainers-go" + + "github.com/version-fox/vfox-java/tests/e2e/common" +) + +// frontDir holds, in a container, the stand-in for api.foojay.io built from +// ./front, its certificate and key, and the log of what it was asked. +const frontDir = "/opt/foojay-front" + +// frontFiles is what every container's front is made of: its binary on the +// host, built once per run, and a certificate for api.foojay.io that signs +// itself, with its key. +type frontFiles struct { + binary string + cert, key []byte +} + +// buildFront builds ./front for the containers' architecture and makes its +// certificate. +func buildFront(t *testing.T, here, arch string) frontFiles { + t.Helper() + binary := filepath.Join(t.TempDir(), "front") + cmd := exec.Command("go", "build", "-trimpath", "-o", binary, "./front") + cmd.Dir = here + cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH="+arch, "CGO_ENABLED=0") + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("build the stand-in for api.foojay.io: %v\n%s", err, out) + } + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "api.foojay.io"}, + DNSNames: []string{"api.foojay.io"}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) + if err != nil { + t.Fatal(err) + } + keyDER, err := x509.MarshalECPrivateKey(key) + if err != nil { + t.Fatal(err) + } + return frontFiles{ + binary: binary, + cert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), + key: pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}), + } +} + +// startFront puts the front between vfox and Foojay in ctr: api.foojay.io +// resolves to it, its certificate is trusted, and vfox runs the Available +// hook for every search, where it would answer a repeated one from its +// cache. stopFront takes it out again. +func startFront(ctx context.Context, t *testing.T, ctr tc.Container, files frontFiles) { + t.Helper() + execOK(ctx, t, ctr, "mkdir --parents "+frontDir) + if err := ctr.CopyFileToContainer(ctx, files.binary, frontDir+"/front", 0o755); err != nil { + t.Fatalf("copy the stand-in for api.foojay.io: %v", err) + } + for name, content := range map[string][]byte{"cert.pem": files.cert, "key.pem": files.key} { + if err := ctr.CopyToContainer(ctx, content, frontDir+"/"+name, 0o600); err != nil { + t.Fatalf("copy %s: %v", name, err) + } + } + execOK(ctx, t, ctr, `set -e +# Foojay's own address, taken before api.foojay.io resolves to the front. +upstream=$(getent ahostsv4 api.foojay.io | awk 'NR == 1 { print $1 }') +[ -n "$upstream" ] || upstream=$(getent ahosts api.foojay.io | awk 'NR == 1 { print $1 }') +[ -n "$upstream" ] +cp `+frontDir+`/cert.pem /usr/local/share/ca-certificates/foojay-front.crt +update-ca-certificates > /dev/null +echo '127.0.0.1 api.foojay.io' >> /etc/hosts +cd `+frontDir+` +nohup ./front -cert cert.pem -key key.pem -upstream "$upstream" -log asked.log < /dev/null > front.out 2>&1 & +echo $! > front.pid +# Up once curl gets an answer through it. +for i in $(seq 50); do curl --silent --output /dev/null https://api.foojay.io/disco/v3.0/distributions && break; sleep 0.2; done +curl --silent --show-error --output /dev/null https://api.foojay.io/disco/v3.0/distributions +vfox config cache.availableHookDuration 0 > /dev/null`) +} + +// stopFront takes the front out of ctr again, so that the checks after it +// talk to Foojay directly. +func stopFront(ctx context.Context, t *testing.T, ctr tc.Container) { + t.Helper() + execOK(ctx, t, ctr, `set -e +kill "$(cat `+frontDir+`/front.pid)" +grep --invert-match ' api.foojay.io$' /etc/hosts > `+frontDir+`/hosts +# /etc/hosts is mounted by Docker, so it is written over, not replaced. +cat `+frontDir+`/hosts > /etc/hosts +vfox config --unset cache.availableHookDuration > /dev/null`) +} + +// asked runs script, retried as execRetry does, and returns its output with +// the lookups of packages the front logged meanwhile (see +// common.JdkLookups). +func asked(ctx context.Context, t *testing.T, ctr tc.Container, script string) (string, []string) { + t.Helper() + before := len(common.JdkLookups(execOK(ctx, t, ctr, "cat "+frontDir+"/asked.log"))) + out := execRetry(ctx, t, ctr, script, script) + return out, common.JdkLookups(execOK(ctx, t, ctr, "cat "+frontDir+"/asked.log"))[before:] +} diff --git a/tests/e2e/linux/front/main.go b/tests/e2e/linux/front/main.go new file mode 100644 index 0000000..1f2a65b --- /dev/null +++ b/tests/e2e/linux/front/main.go @@ -0,0 +1,67 @@ +// Copyright 2026 Han Li and contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Command front stands in for api.foojay.io in an E2E container, so that a +// check can read what the plugin asked Foojay. It serves TLS with the +// certificate it is given, appends the path and query of every request to +// the log, one per line, and passes the request on to Foojay at the address +// it is given, as api.foojay.io itself resolves to the front. +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "sync" +) + +func main() { + listen := flag.String("listen", "127.0.0.1:443", "address to serve on") + cert := flag.String("cert", "", "certificate file for api.foojay.io") + key := flag.String("key", "", "key file of the certificate") + upstream := flag.String("upstream", "", "IP address that api.foojay.io resolved to") + logPath := flag.String("log", "", "file to append the path and query of each request to") + flag.Parse() + + out, err := os.OpenFile(*logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + log.Fatal(err) + } + proxy := httputil.NewSingleHostReverseProxy(&url.URL{Scheme: "https", Host: "api.foojay.io"}) + address := net.JoinHostPort(*upstream, "443") + var dialer net.Dialer + proxy.Transport = &http.Transport{ + DialContext: func(ctx context.Context, network, _ string) (net.Conn, error) { + return dialer.DialContext(ctx, network, address) + }, + ForceAttemptHTTP2: true, + } + var mu sync.Mutex + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + mu.Lock() + _, err := fmt.Fprintln(out, r.URL.RequestURI()) + mu.Unlock() + if err != nil { + log.Print(err) + } + proxy.ServeHTTP(w, r) + }) + log.Fatal(http.ListenAndServeTLS(*listen, *cert, *key, handler)) +} diff --git a/tests/e2e/linux/setup.go b/tests/e2e/linux/setup.go index 40c092d..5fc0d72 100644 --- a/tests/e2e/linux/setup.go +++ b/tests/e2e/linux/setup.go @@ -112,12 +112,14 @@ 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. +// one PreInstall picked and downloads nothing. The install must ask Foojay +// what the search asked (see common.AsksAsSearch). func checkGraalVMBareMajor(ctx context.Context, t *testing.T, ctr tc.Container) { t.Helper() spec := common.OracleGraalVMSpec - out := execRetry(ctx, t, ctr, "vfox search java graal", "vfox search java graal") - common.CheckGraalVMSearch(t, "vfox search java graal", out) + search := "vfox search java graal" + out, searchLookups := asked(ctx, t, ctr, search) + 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 @@ -127,7 +129,9 @@ func checkGraalVMBareMajor(ctx context.Context, t *testing.T, ctr tc.Container) javaDirs = append(javaDirs, dir+"/"+shQuote("java-"+build)) } execOK(ctx, t, ctr, "mkdir --parents "+strings.Join(javaDirs, " ")) - out = execRetry(ctx, t, ctr, "vfox install java@"+spec, "vfox install java@"+spec) + 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, " ")) @@ -138,20 +142,23 @@ func checkGraalVMBareMajor(ctx context.Context, t *testing.T, ctr tc.Container) // short names (see common.NameOnlyDistributions): `vfox search java // ` lists each, and with the newest version it lists faked as // installed, `vfox install java@-` finds that one installed, -// so nothing downloads (see common.NewestBareMajor). +// so nothing downloads (see common.NewestBareMajor). The install must ask +// Foojay what the search asked (see common.AsksAsSearch). func checkNameOnly(ctx context.Context, t *testing.T, ctr tc.Container, platform string) { t.Helper() names := common.NameOnlyDistributions(platform) var dirs []string for _, name := range names { search := "vfox search java " + name - out := execRetry(ctx, t, ctr, search, search) + out, searchLookups := asked(ctx, t, ctr, search) spec, newest, ok := common.NewestBareMajor(common.SearchedVersions(out), name) require.Truef(t, ok, "%s lists no newest version that a bare major names\n--- output ---\n%s", search, out) dir := `"${VFOX_HOME:-$HOME/.vfox}"/cache/java/` + shQuote("v-"+newest) dirs = append(dirs, dir) execOK(ctx, t, ctr, "mkdir --parents "+dir+"/"+shQuote("java-"+newest)) - out = execRetry(ctx, t, ctr, "vfox install java@"+spec, "vfox install java@"+spec) + 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@"+newest+" is already installed", "vfox install java@%s must take %s, the newest version %s lists", spec, newest, search) } @@ -167,13 +174,14 @@ func checkNameOnly(ctx context.Context, t *testing.T, ctr tc.Container, platform // and the newest release of its major faked as installed, `vfox install // java@-` and `vfox install java@-` find those // installed (see common.OwnNumberSpecs and common.NewestOfMajor), so nothing -// downloads. +// downloads. Both installs must ask Foojay what the search asked (see +// common.AsksAsSearch). func checkOwnNumbers(ctx context.Context, t *testing.T, ctr tc.Container, platform string) { t.Helper() names := common.OwnNumberedDistributions(platform) for _, name := range names { search := "vfox search java " + name - out := execRetry(ctx, t, ctr, search, search) + out, searchLookups := asked(ctx, t, ctr, search) searched := common.SearchedVersions(out) spec, installed, bare := common.OwnNumberSpecs(name) require.Containsf(t, searched, installed, "%s must list %s\n--- output ---\n%s", search, installed, out) @@ -186,7 +194,9 @@ func checkOwnNumbers(ctx context.Context, t *testing.T, ctr tc.Container, platfo execOK(ctx, t, ctr, "mkdir --parents "+dir+"/"+shQuote("java-"+version)) } for _, c := range []struct{ spec, want string }{{spec, installed}, {bare, newest}} { - out = execRetry(ctx, t, ctr, "vfox install java@"+c.spec, "vfox install java@"+c.spec) + out, installLookups := asked(ctx, t, ctr, "vfox install java@"+c.spec) + require.NoErrorf(t, common.AsksAsSearch(searchLookups, installLookups), + "vfox install java@%s must ask what %s asks", c.spec, search) require.Containsf(t, common.StripANSI(out), "java@"+c.want+" is already installed", "vfox install java@%s must take %s, which %s lists", c.spec, c.want, search) } diff --git a/tests/pre_install_test.lua b/tests/pre_install_test.lua index e1585df..f01c8da 100644 --- a/tests/pre_install_test.lua +++ b/tests/pre_install_test.lua @@ -114,38 +114,30 @@ local function zips(list) end -- Foojay answers observed on 2026-10-17 for linux/amd64 tar.gz packages, keyed --- by "[ ][ ]", and for windows/amd64 zip and --- tar.gz packages, keyed by "windows [ ][ ]" --- (darwin/amd64: "macos ..."). +-- by "[ ]", and for windows/amd64 zip and tar.gz +-- packages, keyed by "windows [ ]" (darwin/amd64: +-- "macos ..."). local foojay = { - ["temurin 21"] = { pkg("21+35") }, - ["temurin 21 available"] = { pkg("21.0.12.1+1") }, ["temurin 21.0.8"] = { pkg("21.0.8+9") }, - -- latest=available looks at the major alone. - ["temurin 21.0.8 available"] = { pkg("21.0.12.1+1") }, -- Oracle GraalVM's early-access builds are asked for by version. ["graalvm 26-ea+13"] = { graal("26-ea+13", "26", "sts", "graalvm-jdk-26.0.0-ea.13_linux-x64_bin.tar.gz") }, - ["zulu 21"] = { pkg("21+35"), pkg("21+35", true) }, - ["zulu 21 available"] = { pkg("21.0.12.1+1"), pkg("21.0.12.1+1", true) }, - ["openjdk 21"] = { pkg("21+35") }, - ["openjdk 21 available"] = { pkg("21.0.2+13") }, ["openjdk 25.0.2"] = { pkg("25.0.2+10") }, ["openjdk 28-ea"] = { pkg("28-ea+19"), pkg("28-ea+18"), pkg("28-ea+15") }, ["openjdk 28-ea+15"] = { pkg("28-ea+15") }, -- JetBrains Runtime has nine 21.0.5 builds; abridged. ["jetbrains 21.0.5"] = { pkg("21.0.5+79248"), pkg("21.0.5+76240"), pkg("21.0.5+63130"), pkg("21.0.5+6318") }, ["jetbrains 21.0.5+63130"] = { pkg("21.0.5+63130") }, - -- Searching asks for no version; abridged. + -- Searching asks for no version, and so does a bare major, which takes the + -- newest release of the major; abridged, newest first as Foojay lists them. ["openjdk"] = { pkg("25.0.2+10"), pkg("21.0.2+13") }, - ["temurin"] = { pkg("25.0.2+10"), pkg("21.0.8+9") }, + ["temurin"] = { pkg("25.0.2+10"), pkg("21.0.12.1+1"), pkg("21.0.8+9"), pkg("21+35") }, + ["zulu"] = { pkg("25.0.2+10"), pkg("21.0.12.1+1"), pkg("21.0.12.1+1", true), pkg("21+35"), pkg("21+35", true) }, -- Installing Oracle GraalVM or GraalVM Community asks for every package, -- as searching does. ["graalvm"] = graalvm_linux, ["graalvm_community"] = graalvm_community_linux, -- JetBrains Runtime ships Windows zips for its newer builds only, and Foojay -- lists a version's zip and tar.gz in either order; abridged. - ["windows jetbrains 21"] = { pkg("21+2121") }, - ["windows jetbrains 21 available"] = { pkg("21.0.11+1163116"), pkg("21.0.11+1163116", false, "zip") }, ["windows jetbrains 21.0.5"] = { pkg("21.0.5+79248"), pkg("21.0.5+76240") }, -- Windows lists the same GraalVM packages as zips. ["windows graalvm"] = zips(graalvm_linux), @@ -160,25 +152,15 @@ local foojay = { -- Observed on 2026-10-20. Outside Windows, Red Hat ships tar.xz only, -- OJDKBuild's Linux builds and OpenLogic's macOS ones are zips, and so are -- Gluon GraalVM's releases before 22.1 (macos in front for macOS). - ["redhat 21 available"] = { pkg("21.0.8+9", false, "tar.xz") }, - ["ojdk_build 11 available"] = { pkg("11.0.15", false, "zip") }, - ["macos openlogic 25 available"] = { pkg("25.0.4+7", false, "zip") }, - -- latest=available answers with one package of the newest release per - -- archive type, here the JDK 11 build of Gluon's 22.1.0.1, which the JDK - -- 17 one supersedes, as its JRE does OJDKBuild's 17.0.3 JDK. - ["gluon_graalvm 22 available"] = { - file(pkg("22.1.0.1"), "graalvm-svm-java11-linux-gluon-22.1.0.1-Final.tar.gz", "11"), - file(pkg("22.0.0.3", false, "zip"), "graalvm-svm-java11-linux-gluon-22.0.0.3-Final.zip", "11"), - }, + ["redhat"] = { pkg("21.0.8+9", false, "tar.xz") }, + ["ojdk_build"] = { pkg("11.0.15", false, "zip") }, + ["macos openlogic"] = { pkg("25.0.4+7", false, "zip") }, + -- Gluon's 22.1.0.1 comes for JDK 11 first and JDK 17, which supersedes + -- it, as OJDKBuild's 17.0.3 JRE comes before its JDK. ["gluon_graalvm 22.1.0.1"] = { file(pkg("22.1.0.1"), "graalvm-svm-java11-linux-gluon-22.1.0.1-Final.tar.gz", "11"), file(pkg("22.1.0.1"), "graalvm-svm-java17-linux-gluon-22.1.0.1-Final.tar.gz", "17"), }, - ["gluon_graalvm 21 available"] = { - file(pkg("21.2", false, "zip"), "graalvm-svm-linux-gluon-21.2.0-dev.zip", "21.2"), - file(pkg("21.1", false, "zip"), "graalvm-svm-linux-gluon-21.1.0-dev.zip", "21.1"), - }, - ["gluon_graalvm 21.2"] = { file(pkg("21.2", false, "zip"), "graalvm-svm-linux-gluon-21.2.0-dev.zip", "21.2") }, ["gluon_graalvm"] = { file(pkg("23+25"), "graalvm-java23-linux-amd64-gluon-23+25.1-dev.tar.gz", "23"), file(pkg("22.1.0.1"), "graalvm-svm-java11-linux-gluon-22.1.0.1-Final.tar.gz", "11"), @@ -187,13 +169,6 @@ local foojay = { }, -- Among OJDKBuild's Windows JDKs Foojay lists JREs, installer bundles, which -- have the JDK one directory down, and RHEL 6 builds for Linux. - ["windows ojdk_build 17 available"] = { - file(pkg("17.0.3", false, "zip"), "java-17-openjdk-17.0.3.0.6-1.jre.win.x86_64.zip"), - }, - ["windows ojdk_build 17.0.3"] = { - file(pkg("17.0.3", false, "zip"), "java-17-openjdk-17.0.3.0.6-1.jre.win.x86_64.zip"), - file(pkg("17.0.3", false, "zip"), "java-17-openjdk-17.0.3.0.6-1.win.x86_64.zip"), - }, ["windows ojdk_build 11.0.14"] = { file(pkg("11.0.14", false, "zip"), "java-11-openjdk-11.0.14.9-2.windows.ojdkbuild.x86_64.installer_bundle.zip"), file(pkg("11.0.14", false, "zip"), "java-11-openjdk-11.0.14.9-2.windows.ojdkbuild.x86_64.zip"), @@ -202,6 +177,14 @@ local foojay = { file(pkg("8.0.161+14", false, "zip"), "java-1.8.0-openjdk-1.8.0.161-3.b14.el6_9.x86_64.zip"), file(pkg("8.0.161+14", false, "zip"), "java-1.8.0-openjdk-1.8.0.161-1.b14.ojdkbuild.windows.x86_64.zip"), }, + ["windows ojdk_build"] = { + file(pkg("17.0.3", false, "zip"), "java-17-openjdk-17.0.3.0.6-1.jre.win.x86_64.zip"), + file(pkg("17.0.3", false, "zip"), "java-17-openjdk-17.0.3.0.6-1.win.x86_64.zip"), + file(pkg("11.0.14", false, "zip"), "java-11-openjdk-11.0.14.9-2.windows.ojdkbuild.x86_64.installer_bundle.zip"), + file(pkg("11.0.14", false, "zip"), "java-11-openjdk-11.0.14.9-2.windows.ojdkbuild.x86_64.zip"), + file(pkg("8.0.161+14", false, "zip"), "java-1.8.0-openjdk-1.8.0.161-3.b14.el6_9.x86_64.zip"), + file(pkg("8.0.161+14", false, "zip"), "java-1.8.0-openjdk-1.8.0.161-1.b14.ojdkbuild.windows.x86_64.zip"), + }, } -- The 13 distributions added after release 0.6.2, with their Foojay name and the @@ -224,11 +207,7 @@ local others = { { "semcrt", "semeru_certified", "25.0.4" }, } for _, other in ipairs(others) do - -- OJDKBuild and Gluon GraalVM ask for the newest release itself as well. - for _, key in ipairs({ other[3]:match("^%d+") .. " available", other[3] }) do - key = other[2] .. " " .. key - foojay[key] = foojay[key] or { pkg(other[3], false, other[4]) } - end + foojay[other[2]] = foojay[other[2]] or { pkg(other[3], false, other[4]) } end -- A package of Mandrel or Liberica NIK: its JDK, the GraalVM number it goes by, @@ -258,9 +237,7 @@ end -- Observed on 2026-10-20 for linux/amd64: Foojay gives the numbers Mandrel and -- Liberica NIK go by as distribution_version, and the JDK as java_version, -- Mandrel's as the major alone; abridged, in Foojay's order. The plugin asks --- for them by distribution_version, which lists the packages whose number --- starts with the numbers given, a missing one counting as 0, so 23.1.12 --- lists 23.1.12.1 as well and 23.1.12.0 does not. A build is ignored. +-- for every package, as searching does, and picks them by that number. local own_numbers = { mandrel = { mandrel("25", "25.0.4.1", "lts"), @@ -303,21 +280,8 @@ local own_numbers = { }, } --- Whether Foojay lists a package of own_numbers whose number is --- distribution_version for the query value asked. -local function own_number_matches(distribution_version, asked) - local numbers = {} - for number in distribution_version:gmatch("%d+") do - table.insert(numbers, tonumber(number)) - end - local i = 0 - for number in asked:match("^[^+]*"):gmatch("%d+") do - i = i + 1 - if (numbers[i] or 0) ~= tonumber(number) then - return false - end - end - return true +for distribution, packages in pairs(own_numbers) do + foojay[distribution] = packages end local requests = {} @@ -346,22 +310,10 @@ package.preload.http = function() if version ~= "" then key = key .. " " .. version end - if params.latest then - key = key .. " " .. params.latest - end if params.operating_system == "windows" or params.operating_system == "macos" then key = params.operating_system .. " " .. key end local packages = foojay[key] or {} - if own_numbers[params.distribution] and key == params.distribution then - packages = {} - local asked = (params.distribution_version or ""):match("^[^ ]*") - for _, package in ipairs(own_numbers[params.distribution]) do - if own_number_matches(package.distribution_version, asked) then - table.insert(packages, package) - end - end - end -- Only the packages of the archive types asked for are listed, -- each a copy, as a decoded answer is. local result = {} @@ -397,39 +349,29 @@ PLUGIN = {} dofile("hooks/available.lua") dofile("hooks/pre_install.lua") +-- What searching asks Foojay for the distribution of version. +local function searched(version) + requests = {} + PLUGIN:Available({ args = { require("distribution_version").parse_version(version).distribution.short_name } }) + return requests[1] +end + local cases = 0 -local function check(version, expected, latest, archive_type) +-- Installs version, from a file ending in file_end where given, in one lookup. +-- A bare major asks what searching asks, as Foojay's answers are cached per +-- question and two of them can disagree for a while; any other version asks +-- for itself. +local function check(version, expected, bare, file_end) + local search = searched(version) requests = {} local result = PLUGIN:PreInstall({ version = version }) assert(result.version == expected, version .. ": expected " .. expected .. ", got " .. tostring(result.version)) - if archive_type then + if file_end then assert( - result.url:sub(-#archive_type - 1) == "." .. archive_type, - version .. ": expected the " .. archive_type .. " package, got " .. result.url + result.url:sub(-#file_end) == file_end, + version .. ": expected ..." .. file_end .. ", got " .. result.url ) end - local lookups = 0 - for _, url in ipairs(requests) do - if url:sub(1, #JDKS) == JDKS then - lookups = lookups + 1 - assert((url:find("&latest=available", 1, true) ~= nil) == latest, version .. ": unexpected lookup " .. url) - -- Only Mandrel and Liberica NIK ask by distribution_version. - assert(not url:find("distribution_version=", 1, true), version .. ": unexpected lookup " .. url) - end - end - assert(lookups > 0, version .. ": no Foojay lookup") - cases = cases + 1 - return lookups -end - --- Installs version as OJDKBuild's and Gluon GraalVM's bare majors do: the --- latest=available lookup names the newest release, and a second one asks --- for that release in full, from which the package of the named file comes. -local function check_release_lookup(version, expected, release, filename) - requests = {} - local result = PLUGIN:PreInstall({ version = version }) - assert(result.version == expected, version .. ": expected " .. expected .. ", got " .. tostring(result.version)) - assert(result.url:sub(-#filename) == filename, version .. ": expected " .. filename .. ", got " .. result.url) local lookups = {} for _, url in ipairs(requests) do if url:sub(1, #JDKS) == JDKS then @@ -437,11 +379,11 @@ local function check_release_lookup(version, expected, release, filename) end end assert( - #lookups == 2 - and lookups[1]:find("&latest=available", 1, true) - and lookups[2]:find("?version=" .. release:gsub("%+", "%%2B") .. "&", 1, true) - and not lookups[2]:find("latest=", 1, true), - version .. ": unexpected lookups " .. table.concat(lookups, " ") + #lookups == 1 + and (lookups[1] == search) == bare + and not lookups[1]:find("latest=", 1, true) + and not lookups[1]:find("distribution_version=", 1, true), + version .. ": asked " .. table.concat(lookups, " ") .. ", searching asks " .. search ) cases = cases + 1 end @@ -450,10 +392,7 @@ end -- The plugin asks Foojay the question searching asks, as Foojay's answers are -- cached per question and two of them can disagree for a while. local function check_graalvm(version, expected, filename) - local short = require("distribution_version").parse_version(version).distribution.short_name - requests = {} - PLUGIN:Available({ args = { short } }) - local searched = requests[1] + local search = searched(version) requests = {} local result = PLUGIN:PreInstall({ version = version }) assert(result.version == expected, version .. ": expected " .. expected .. ", got " .. tostring(result.version)) @@ -468,8 +407,8 @@ local function check_graalvm(version, expected, filename) end end assert( - #lookups == 1 and lookups[1] == searched, - version .. ": asked " .. table.concat(lookups, " ") .. ", searching asks " .. searched + #lookups == 1 and lookups[1] == search, + version .. ": asked " .. table.concat(lookups, " ") .. ", searching asks " .. search ) cases = cases + 1 end @@ -575,8 +514,8 @@ end -- version's zip where there is one, and searching lists the releases that ship -- only as tar.gz too, newest first and each once. RUNTIME.osType = "windows" -assert(check("21-jb", "21.0.11+1163116-jb", true, "zip") == 1, "21-jb: expected one lookup") -assert(check("21.0.5-jb", "21.0.5+79248-jb", false, "tar.gz") == 1, "21.0.5-jb: expected one lookup") +check("21-jb", "21.0.11+1163116-jb", true, ".zip") +check("21.0.5-jb", "21.0.5+79248-jb", false, ".tar.gz") check_graalvm("25-graal", "25.0.4.1.1-graal", "graalvm-jdk-25.0.4.1.1_windows-x64_bin.zip") check_graalvm("25-graalce", "25.4.4.1.1-graalce", "gds/graalvm-community-jdk-25i4-25.0.4.1.1_windows-x64_bin.zip") -- Searching GraalVM lists each release once, under GraalVM's own version and @@ -609,11 +548,11 @@ assert( table.concat(jb, " ") == "21.0.11+1163116-jb 21.0.8+895146-jb 21.0.5+79248-jb", "Available jb on Windows: unexpected " .. table.concat(jb, " ") ) --- OJDKBuild's bare major installs the JDK, not the JRE that latest=available --- answers with, and no release installs an installer bundle or a Linux build. -check_release_lookup("17-ojdkbld", "17.0.3-ojdkbld", "17.0.3", "/java-17-openjdk-17.0.3.0.6-1.win.x86_64.zip") -check("11.0.14-ojdkbld", "11.0.14-ojdkbld", false, "windows.ojdkbuild.x86_64.zip") -check("8.0.161-ojdkbld", "8.0.161+14-ojdkbld", false, "b14.ojdkbuild.windows.x86_64.zip") +-- OJDKBuild's bare major installs the JDK, not the JRE Foojay lists first, and +-- no release installs an installer bundle or a Linux build. +check("17-ojdkbld", "17.0.3-ojdkbld", true, "/java-17-openjdk-17.0.3.0.6-1.win.x86_64.zip") +check("11.0.14-ojdkbld", "11.0.14-ojdkbld", false, ".windows.ojdkbuild.x86_64.zip") +check("8.0.161-ojdkbld", "8.0.161+14-ojdkbld", false, ".b14.ojdkbuild.windows.x86_64.zip") RUNTIME.osType = "linux" -- The distributions in others install by their short and long names, each from @@ -621,13 +560,8 @@ RUNTIME.osType = "linux" for _, other in ipairs(others) do local short, long, release, archive_type = other[1], other[2], other[3], other[4] local major = release:match("^%d+") - if long == "ojdk_build" or long == "gluon_graalvm" then - check_release_lookup(major .. "-" .. short, release .. "-" .. short, release, "." .. (archive_type or "tar.gz")) - check_release_lookup(long .. "-" .. major, release .. "-" .. short, release, "." .. (archive_type or "tar.gz")) - else - check(major .. "-" .. short, release .. "-" .. short, true, archive_type) - check(long .. "-" .. major, release .. "-" .. short, true, archive_type) - end + check(major .. "-" .. short, release .. "-" .. short, true, "." .. (archive_type or "tar.gz")) + check(long .. "-" .. major, release .. "-" .. short, true, "." .. (archive_type or "tar.gz")) end -- Only these distributions ask for another archive type besides tar.gz. requests = {} @@ -638,8 +572,8 @@ PLUGIN:PreInstall({ version = "21-tem" }) assert(requests[1]:find("&archive_type=tar.gz&operating_system=", 1, true), "21-tem: " .. requests[1]) -- Gluon GraalVM's 22.x releases install their JDK 17 build, which they share a -- version with the JDK 11 one, listed once by searching. -check_release_lookup("22-gln", "22.1.0.1-gln", "22.1.0.1", "/graalvm-svm-java17-linux-gluon-22.1.0.1-Final.tar.gz") -check_release_lookup("21-gln", "21.2-gln", "21.2", "/graalvm-svm-linux-gluon-21.2.0-dev.zip") +check("22-gln", "22.1.0.1-gln", true, "/graalvm-svm-java17-linux-gluon-22.1.0.1-Final.tar.gz") +check("21-gln", "21.2-gln", true, "/graalvm-svm-linux-gluon-21.2.0-dev.zip") requests = {} local gluon = PLUGIN:PreInstall({ version = "22.1.0.1-gln" }) assert(gluon.url:find("/graalvm-svm-java17-", 1, true) and #requests == 2, "22.1.0.1-gln: " .. gluon.url) @@ -653,17 +587,17 @@ assert( ) -- OpenLogic installs from a zip on macOS, where it ships no tar.gz. RUNTIME.osType = "darwin" -check("25-opnlgc", "25.0.4+7-opnlgc", true, "zip") +check("25-opnlgc", "25.0.4+7-opnlgc", true, ".zip") RUNTIME.osType = "linux" -- Installs version of Mandrel or Liberica NIK, which the plugin asks Foojay --- for by distribution_version alone, and checks the end of the file name. +-- for as searching does, and checks the end of the file name. local function check_own_number(version, expected, file_end) + local search = searched(version) requests = {} local result = PLUGIN:PreInstall({ version = version }) assert(result.version == expected, version .. ": expected " .. expected .. ", got " .. tostring(result.version)) assert(result.url:sub(-#file_end) == file_end, version .. ": expected ..." .. file_end .. ", got " .. result.url) - local asked = require("distribution_version").parse_version(version).version:gsub("%+", "%%2B") local lookups = {} for _, url in ipairs(requests) do if url:sub(1, #JDKS) == JDKS then @@ -671,11 +605,8 @@ local function check_own_number(version, expected, file_end) end end assert( - #lookups == 1 - and lookups[1]:find("?version=&", 1, true) - and lookups[1]:sub(-#asked - 22) == "&distribution_version=" .. asked - and not lookups[1]:find("latest=", 1, true), - version .. ": unexpected lookups " .. table.concat(lookups, " ") + #lookups == 1 and lookups[1] == search, + version .. ": asked " .. table.concat(lookups, " ") .. ", searching asks " .. search ) cases = cases + 1 end @@ -683,7 +614,7 @@ end -- Mandrel and Liberica NIK install by the GraalVM numbers they go by, which -- Foojay gave as java_version until October 2026 and now as -- distribution_version (vfox-java#106). A longer version takes the release it --- names, though Foojay lists 23.1.12.1 for 23.1.12 as well. +-- names, not 23.1.12.1 for 23.1.12. check_own_number("23.1.12-mandrel", "23.1.12-mandrel", "java21-linux-amd64-23.1.12.0-Final.tar.gz") check_own_number("mandrel-23.1.12", "23.1.12-mandrel", "java21-linux-amd64-23.1.12.0-Final.tar.gz") check_own_number("23.1.12.0-mandrel", "23.1.12-mandrel", "java21-linux-amd64-23.1.12.0-Final.tar.gz")