diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2f2480a..0ad611b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,6 +40,7 @@ jobs: go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/pre_use_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/parse_legacy_file_test.lua 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/github_mirror_test.lua - name: Test macOS Java home normalization run: go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/post_install_test.lua macos-layout: diff --git a/README.md b/README.md index 087909e..5461d8f 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,12 @@ Zulu, Liberica, Liberica NIK and Corretto (Java 8 only) have builds with JavaFX vfox reads the `java=` line of SDKMAN's `.sdkmanrc` through this plugin and selects a JDK that is already installed. See [docs/sdkmanrc.md](docs/sdkmanrc.md) for details. +## GitHub mirror + +Most distributions download from GitHub. If that is slow where you are, `export VFOX_JAVA_GITHUB_MIRROR=https://gh-proxy.org/` sends these downloads through a mirror. See [docs/mirror.md](docs/mirror.md) for details. + +Unreleased: release 0.6.2 has no mirror setting. + ## Querying Foojay To see which JDKs exist for your platform, ask Foojay yourself. [docs/foojay.md](docs/foojay.md) has the Swagger UI and the commands. diff --git a/README_CN.md b/README_CN.md index f4f530c..945c64b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -86,6 +86,12 @@ Zulu、Liberica、Liberica NIK 和 Corretto(只有 Java 8)提供内置 JavaF vfox 通过这个插件读取 SDKMAN 的 `.sdkmanrc` 里的 `java=` 一行,并选中已经安装的 JDK。详见 [docs/zh-CN/sdkmanrc.md](docs/zh-CN/sdkmanrc.md)。 +## GitHub 镜像 + +大多数发行版从 GitHub 下载。如果你那里下载很慢,执行 `export VFOX_JAVA_GITHUB_MIRROR=https://gh-proxy.org/`,这些下载就会经过镜像。详见 [docs/zh-CN/mirror.md](docs/zh-CN/mirror.md)。 + +未发布:0.6.2 没有镜像设置。 + ## 查询 Foojay 想知道你的平台上有哪些 JDK,可以自己去问 Foojay。[docs/zh-CN/foojay.md](docs/zh-CN/foojay.md) 里有 Swagger UI 和查询命令。 diff --git a/docs/changes.md b/docs/changes.md index 8d5f3f4..5def283 100644 --- a/docs/changes.md +++ b/docs/changes.md @@ -14,6 +14,7 @@ What changed in each release, as a user notices it. GitHub's release pages list - A 25.0 name that release 0.6.2 installed after Foojay's change can hold a feature release: `25.0.4.1-graal` is GraalVM 25.4.4.1.1, `25.0.4-graal` is 25.2.4 and `25.0.3-graal` is 25.1.3, and the same goes for these names with `-graalce`. vfox does not install a name it already has, so if the GraalVM version that `java -version` prints differs from the name, `vfox uninstall` it and install it again. `25.0.4.1-graalce`, `25.0.4-graalce` and `25.0.3-graalce` no longer install at all, as GraalVM Community's 25.0 line ends with `25.0.2`. - A bare major such as `java@21-tem` asks Foojay what `vfox search` asks and installs the newest release of the major that the search lists. Release 0.6.2 has Foojay pick it (`latest=available`), and as Foojay caches each question on its own, for half an hour after a release that can install a build `vfox search` does not list yet. Dragonwell 11 on Windows x64 and on Linux x64 with musl now installs the Extended build of 11.0.32, as `java@11.0.32-albba` does, where 0.6.2 installs the Standard one for `java@11-albba` ([#113](https://github.com/version-fox/vfox-java/issues/113)). - `vfox use java@25-graal` and a `.sdkmanrc` holding `java=25-graal` select the newest installed build of Oracle GraalVM's 25.0 line, the build `vfox install java@25-graal` installs. Release 0.6.2 selects the newest installed release of 25, which is a monthly feature release as soon as one is installed: with `25.0.5-graal` and `25.4.4.1.1-graal` installed, `vfox use java@25-graal` selected the second, while `vfox install java@25-graal` reported the first as installed. A feature release is selected by its full version (`java@25.4.4.1.1-graal`), or by the bare major where nothing else of 25 is installed. A `.sdkmanrc` naming a 25.0 release that is not installed, such as `java=25.0.3-graal`, falls back the same way. `java=25.4.4+1-graal`, which names a feature release as SDKMAN does, still takes the newest installed release of 25. GraalVM Community is not affected, as its feature releases are what it still ships ([#110](https://github.com/version-fox/vfox-java/issues/110)). +- `VFOX_JAVA_GITHUB_MIRROR` sends the downloads from GitHub through a mirror, as in `export VFOX_JAVA_GITHUB_MIRROR=https://gh-proxy.org/`. Foojay links the JDKs of most distributions to GitHub Releases (Temurin, Liberica and GraalVM Community among them). github.com is slow or out of reach in some regions. The value is a prefix that goes in front of the GitHub URL. Downloads from other hosts do not change. See [mirror.md](mirror.md). ## 0.6.2 (2026-10-18) diff --git a/docs/development.md b/docs/development.md index 42de0d2..a319705 100644 --- a/docs/development.md +++ b/docs/development.md @@ -14,6 +14,7 @@ hooks/pre_use.lua vfox use: map what was typed to an installed nam hooks/parse_legacy_file.lua .sdkmanrc lib/distribution_version.lua distribution names, parsing of specs, GraalVM feature releases lib/foojay.lua the Foojay request, and fixes to what it answers +lib/github_mirror.lua the mirror of the downloads from GitHub lib/installed_version.lua matching against installed names lib/loongnix.lua the Loongnix archive tests/*_test.lua offline hook tests @@ -37,6 +38,7 @@ vfox loads `metadata.lua`, calls a hook when a command needs it, and puts `PLUGI A few contracts that cost time to find: - `PreInstall` returns `url`, `version` and `note`. `version` becomes the name of the install, which is why it carries the build number. The package's checksum is not passed on yet (`TODO: checksum` in `hooks/pre_install.lua`), and vfox prints a warning for that. +- For a package from Foojay, `url` is its `direct_download_uri`, behind `$VFOX_JAVA_GITHUB_MIRROR` when it is on GitHub (`lib/github_mirror.lua`, [mirror.md](mirror.md)). vfox downloads from `http://` and `https://` addresses and takes anything else for a file on disk. It names the download after the last part of the URL's path and unpacks it by the extension of that name, which a prefix in front of the URL does not change. - `PreUse` returns a table or `nil`. For a bare major it returns `nil` so that vfox matches it itself. vfox 1.0.13 and later reject a bare string such as `""`. - `ParseLegacyFile` calls `ctx.getInstalledVersions()` with no argument. vfox 1.x binds it as a Go `func() []string` and rejects the extra argument that `ctx:getInstalledVersions()` adds. vfox ignores errors from this hook, so the failure is silent. - vfox hands over the installed versions sorted as strings, descending: `21.0.8+9-tem`, `21.0.12+8-tem`, `21.0.1+12-tem`. The first one that starts with a release is not the right one, so `lib/installed_version.lua` compares numbers one by one. @@ -71,10 +73,11 @@ go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/pre_install_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/pre_use_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/parse_legacy_file_test.lua 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/github_mirror_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 six 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 4a406f4..27a89a5 100644 --- a/docs/e2e.md +++ b/docs/e2e.md @@ -81,6 +81,14 @@ JDK, so `java` can only come from what vfox installs: (`+99`, e.g. `21.0.8+99-tem`), proving the lookup keeps the build number: Foojay reads an unencoded `+` as a space and drops the build, and the release's newest build would install instead. + + On the Temurin rows an install through a mirror that does not exist must + fail too. With `VFOX_JAVA_GITHUB_MIRROR=https://invalid.example.invalid` + the mirror goes in front of Temurin's GitHub URL, so `vfox install` fails on + the download, and its error must name the URL it asked for: the mirror, a + slash and the GitHub URL. Before the plugin read the variable, it downloaded + the JDK from github.com and the install succeeded. Nothing is downloaded, + and no mirror that works is run. 3. On the GraalVM Community rows, Oracle GraalVM's bare major `25-graal` installs the newest build of its 25.0 line (`25.0.5-graal` on 2026-10-20), the line Oracle's own `25/latest` download follows, not one @@ -204,13 +212,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 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` | ✓ | | +| 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 mirror, name and number checks of items 2, 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 @@ -388,6 +396,7 @@ go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/pre_install_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/pre_use_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/parse_legacy_file_test.lua 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/github_mirror_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/post_install_test.lua ``` diff --git a/docs/foojay.md b/docs/foojay.md index aca6bd8..5c5c950 100644 --- a/docs/foojay.md +++ b/docs/foojay.md @@ -35,7 +35,7 @@ For a bare major such as `21` the answer lists every release, and the plugin tak 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. +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. Unreleased: a download from GitHub can go through a mirror, see [mirror.md](mirror.md). `vfox search` differs from the raw list in a few ways. It lists a name once where Foojay has two records for it, and it leaves the LTS note off GraalVM's feature releases (see [distributions.md](distributions.md#behavior-of-oracle-graalvm-and-graalvm-community)). It only lists GA releases. diff --git a/docs/mirror.md b/docs/mirror.md new file mode 100644 index 0000000..444eea9 --- /dev/null +++ b/docs/mirror.md @@ -0,0 +1,70 @@ +# GitHub mirror + +[中文](zh-CN/mirror.md) | [English](mirror.md) + +Unreleased: release 0.6.2 has no mirror setting. + +Foojay links the JDKs of most distributions to GitHub Releases, so `vfox install java@21-tem` downloads from `github.com`. If github.com is slow or unreachable where you are, set `VFOX_JAVA_GITHUB_MIRROR` and the plugin sends these downloads through a GitHub mirror. + +The value is a URL prefix. The plugin puts it in front of the GitHub URL, and adds a `/` if the value does not end in one. For Temurin 21.0.8 on Linux x64: + +| `VFOX_JAVA_GITHUB_MIRROR` | vfox downloads | +| --- | --- | +| not set | `https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz` | +| `https://gh-proxy.org/` | `https://gh-proxy.org/https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz` | + +[gh-proxy](https://gh-proxy.org/) takes URLs in this form, and so does any service that takes the GitHub URL after its own address. The plugin does not run any of them and cannot vouch for them. The value must start with `http://` or `https://`. vfox takes any other address for a file on disk, so a download from GitHub stops with `VFOX_JAVA_GITHUB_MIRROR must start with http:// or https://` instead. + +## Set it + +In bash or zsh, for the current shell: + +```bash +export VFOX_JAVA_GITHUB_MIRROR=https://gh-proxy.org/ +``` + +Put the same line in `~/.bashrc` or `~/.zshrc` to keep it. In PowerShell: + +```powershell +$env:VFOX_JAVA_GITHUB_MIRROR = "https://gh-proxy.org/" +``` + +To set it for every terminal opened afterwards: + +```powershell +[Environment]::SetEnvironmentVariable('VFOX_JAVA_GITHUB_MIRROR', 'https://gh-proxy.org/', 'User') +``` + +Then install as usual, `vfox install java@21-tem`. To go back to github.com, unset the variable. + +## What goes through the mirror + +Only downloads from `https://github.com/`. A JDK on any other host downloads from there as before, and the requests to Foojay do not change. + +Where Foojay links each distribution (2026-10-21): + +| Short names | Download from | +| --- | --- | +| `tem`, `librca`, `sem`, `semcrt`, `sapmchn`, `kona`, `albba`, `mandrel`, `trava`, `adpt`, `adptj9`, `eliya`, `ojdkbld`, `gln`, `grl8`, `grl11`, `grl16`, `grl17`, `grl19` | GitHub | +| `graalce` | GitHub, except the monthly feature releases that `java@25-graalce` installs, which come from Oracle's GraalVM Download Service. `java@25.4.4.1-graalce` is the same file from GitHub | +| `nik` | GitHub for the newer releases, BellSoft's server for the older ones | +| `open` | jdk.java.net, except JDK 8 and most 11.x builds, which come from GitHub | +| `zulu`, `amzn`, `ms`, `oracle`, `graal`, `jb`, `bsg`, `opnlgc`, `rdht` | the vendor's server or a CDN, not GitHub | + +Loongnix (`lnxabi1`, `lnxabi2`) downloads from ftp.loongnix.cn and is not mirrored. + +To see where one release downloads from, ask Foojay for its package and read `direct_download_uri`. It is mirrored if it starts with `https://github.com/`. [foojay.md](foojay.md) has the queries for other platforms. + +```bash +curl -s 'https://api.foojay.io/disco/v3.0/packages/jdks?distribution=temurin&version=21.0.8&architecture=amd64&operating_system=linux&lib_c_type=glibc&archive_type=tar.gz&directly_downloadable=true' \ + | jq -r '.result[0].links.pkg_info_uri' | xargs curl -s | jq -r '.result[0].direct_download_uri' +``` + +``` +https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz +``` + +## Notes + +- The plugin gives vfox no checksum, so vfox does not verify the download (`WARNING: Checksum is not provided, skip verify...`, see [foojay.md](foojay.md#what-the-plugin-asks)). Through a mirror, the archive is whatever the mirror serves. Use one you trust. +- The variable only changes where `vfox install` downloads a JDK from. `vfox add java` does not use it. For a proxy that covers all of vfox, see [Network](troubleshooting.md#network). diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index d98f4c8..61336e0 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -32,7 +32,11 @@ The request to Foojay failed. After `status_code =>` comes the HTTP status, othe ### `source file not found` -The download answered 404. Vendors take old builds down while Foojay keeps their records. Early-access builds are the common case (see [versions.md](versions.md#early-access-builds)). Pick a newer build. Red Hat's builds for anything but Linux x64 all end here, see [distributions.md](distributions.md#red-hat-build-of-openjdk-rdht). +The download answered 404. Vendors take old builds down while Foojay keeps their records. Early-access builds are the common case (see [versions.md](versions.md#early-access-builds)). Pick a newer build. Red Hat's builds for anything but Linux x64 all end here, see [distributions.md](distributions.md#red-hat-build-of-openjdk-rdht). Through a mirror ([mirror.md](mirror.md)), a 404 from the mirror ends here too: unset `VFOX_JAVA_GITHUB_MIRROR` and try again. + +### `VFOX_JAVA_GITHUB_MIRROR must start with http:// or https://, such as https://gh-proxy.org/, but is gh-proxy.org` + +The value does not start with `http://` or `https://`. vfox downloads from `http://` and `https://` addresses only and takes any other for a file on disk, so a download from GitHub stops here before it starts. Other downloads do not read the variable. Fix the value or unset the variable, see [mirror.md](mirror.md). Unreleased: release 0.6.2 has no mirror setting. ### `java@21-tem is not installed` @@ -48,7 +52,7 @@ The download answered 404. Vendors take old builds down while Foojay keeps their ## Network -The plugin talks to Foojay for the lists, and vfox downloads the archive from the vendor's server, often GitHub Releases. The plugin has no mirror setting. +The plugin talks to Foojay for the lists, and vfox downloads the archive from the vendor's server, often GitHub Releases. For the downloads from GitHub the plugin has a mirror setting, see [mirror.md](mirror.md). Unreleased: release 0.6.2 has none. vfox's own proxy setting covers both the Foojay requests and the download: diff --git a/docs/zh-CN/changes.md b/docs/zh-CN/changes.md index c862e2f..d10cdb2 100644 --- a/docs/zh-CN/changes.md +++ b/docs/zh-CN/changes.md @@ -14,6 +14,7 @@ - 在 Foojay 改格式之后用 0.6.2 装的 25.0 版本名,里面可能是特性版本:`25.0.4.1-graal` 是 GraalVM 25.4.4.1.1,`25.0.4-graal` 是 25.2.4,`25.0.3-graal` 是 25.1.3,这几个名字换成 `-graalce` 也一样。名字已经装过的,vfox 不会重装,所以如果 `java -version` 打印的 GraalVM 版本和名字对不上,先 `vfox uninstall` 再重新安装。`25.0.4.1-graalce`、`25.0.4-graalce` 和 `25.0.3-graalce` 现在根本装不上了,因为 GraalVM 社区版的 25.0 系列到 `25.0.2` 为止。 - 只写大版本号(如 `java@21-tem`)时,向 Foojay 发的请求和 `vfox search` 相同,装的是搜索列出的这个大版本里最新的版本。0.6.2 让 Foojay 来挑(`latest=available`),而 Foojay 按请求分别缓存结果,新版本发布后的半小时里,可能装上 `vfox search` 还没列出的构建。Windows x64 和 musl 的 Linux x64 上,Dragonwell 11 现在装 11.0.32 的 Extended 构建,和 `java@11.0.32-albba` 一样;0.6.2 的 `java@11-albba` 装的是 Standard 构建([#113](https://github.com/version-fox/vfox-java/issues/113))。 - `vfox use java@25-graal` 和写着 `java=25-graal` 的 `.sdkmanrc`,选中的是 Oracle GraalVM 25.0 系列里已安装的最新构建,也就是 `vfox install java@25-graal` 装的那个。0.6.2 选的是 25 里已安装的最新版本,所以一旦装了特性版本,选中的就是它:装了 `25.0.5-graal` 和 `25.4.4.1.1-graal` 时,`vfox use java@25-graal` 选中后者,而 `vfox install java@25-graal` 报告前者已经安装。现在特性版本要写全版本号才会被选中(`java@25.4.4.1.1-graal`),只写大版本号时,只有 25 里没有装别的版本才会选它。`.sdkmanrc` 里写了没装的 25.0 版本(例如 `java=25.0.3-graal`),退到同一大版本时也这样挑。`java=25.4.4+1-graal` 是 SDKMAN 指明特性版本的写法,仍然选 25 里已安装的最新版本。GraalVM 社区版不受影响,因为特性版本就是它现在发布的全部([#110](https://github.com/version-fox/vfox-java/issues/110))。 +- `VFOX_JAVA_GITHUB_MIRROR` 让从 GitHub 下载的 JDK 经过镜像,例如 `export VFOX_JAVA_GITHUB_MIRROR=https://gh-proxy.org/`。Foojay 把大多数发行版的 JDK 链接到 GitHub Releases(Temurin、Liberica 和 GraalVM 社区版都在其中)。有些地区访问 github.com 很慢,甚至连不上。值是放在 GitHub 地址前面的前缀,其他主机上的下载不变。见 [mirror.md](mirror.md)。 ## 0.6.2(2026-10-18) diff --git a/docs/zh-CN/development.md b/docs/zh-CN/development.md index e52d1e1..cefb29f 100644 --- a/docs/zh-CN/development.md +++ b/docs/zh-CN/development.md @@ -14,6 +14,7 @@ hooks/pre_use.lua vfox use:把输入映射到已安装的版本 hooks/parse_legacy_file.lua .sdkmanrc lib/distribution_version.lua 发行版名称、版本写法的解析、GraalVM 特性版本的识别 lib/foojay.lua 向 Foojay 发出的请求,以及对返回结果的修正 +lib/github_mirror.lua 从 GitHub 下载时用的镜像 lib/installed_version.lua 与已安装的版本名做匹配 lib/loongnix.lua Loongnix 归档 tests/*_test.lua 离线的钩子测试 @@ -37,6 +38,7 @@ vfox 加载 `metadata.lua`,命令需要时调用相应的钩子,并向钩子 几条容易踩坑的约定: - `PreInstall` 返回 `url`、`version` 和 `note`。`version` 会成为安装后的版本名,所以它带着构建号。包的校验和目前没有传给 vfox(`hooks/pre_install.lua` 里的 `TODO: checksum`),vfox 为此会打印警告。 +- Foojay 的包,`url` 就是它的 `direct_download_uri`;它在 GitHub 上时,前面会加上 `$VFOX_JAVA_GITHUB_MIRROR`(`lib/github_mirror.lua`,见 [mirror.md](mirror.md))。vfox 只从 `http://` 和 `https://` 开头的地址下载,别的写法它会当成磁盘上的文件。它用 URL 路径的最后一段给下载的文件命名,再按这个名字的扩展名解压,在 URL 前面加个前缀不会影响这一点。 - `PreUse` 返回 Lua 表或 `nil`。只写大版本号时返回 `nil`,让 vfox 自己去匹配。vfox 1.0.13 及以后会拒绝裸字符串,比如 `""`。 - `ParseLegacyFile` 调用 `ctx.getInstalledVersions()` 时不能带参数。vfox 1.x 把它绑定成 Go 的 `func() []string`,会拒绝 `ctx:getInstalledVersions()` 多传的那个参数。vfox 会忽略这个钩子的错误,所以出错时没有任何提示。 - vfox 把已安装的版本按字符串降序传给钩子,例如 `21.0.8+9-tem`、`21.0.12+8-tem`、`21.0.1+12-tem`。按前缀匹配时,第一个命中的不一定是最新的,所以 `lib/installed_version.lua` 逐段比较数字。 @@ -71,10 +73,11 @@ go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/pre_install_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/pre_use_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/parse_legacy_file_test.lua 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/github_mirror_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 dbf5e77..4cf1171 100644 --- a/docs/zh-CN/e2e.md +++ b/docs/zh-CN/e2e.md @@ -32,6 +32,8 @@ Windows 容器的覆盖(`tests/e2e/windows`)无法在开发容器里运行 1. 从指定的渠道安装 vfox,要么是最新发布版(`latest`),要么从源码构建(`main`),然后从当前检出的代码注册本地的 `java` 插件。在 Windows 上,runner 会在 `latest` 组合运行期间把 `main` 构建一次,`main` 的容器由 e2e 镜像加上这个 `vfox.exe` 启动。接着,`vfox info java` 打印的 Notes 要链接到 README,README 里列了各个简称,也写了怎样搜索某个发行版。以前的 Notes 只给了五个简称,没说怎样列出它们的版本([vfox#390](https://github.com/version-fox/vfox/issues/390))。 2. 只写发行版不写版本,无论单独写还是跟在空版本后面(`java@tem`、`java@-tem`),`vfox install` 和 `vfox use` 都应失败,并提示补上版本。以前,`java@tem` 会让钩子报 Lua 错误;`java@-tem` 则让 `PreInstall` 把空版本发给 Foojay,Foojay 列出所有版本,结果装上了最新的。没有任何发行版发布过的版本(`<大版本>.0.99`)应该安装失败,并且 `PreInstall` 要指出它搜索的平台(`linux/amd64`、`windows/arm64` 等),以此确认向 Foojay 查询的就是 runner 所在的平台。已固定版本里从未发布过的构建(`<版本>+99`,例如 `21.0.8+99-tem`)也一样,这证明查询保留了构建号:Foojay 会把没编码的 `+` 当成空格并丢掉构建号,于是会装上这个版本的最新构建。 + + 在 `tem` 这几行里,经过一个不存在的镜像安装也必须失败。设成 `VFOX_JAVA_GITHUB_MIRROR=https://invalid.example.invalid` 后,镜像会加在 Temurin 的 GitHub 地址前面,所以 `vfox install` 在下载时失败,错误信息里必须出现 vfox 请求的那个地址:镜像、一个斜杠,再加 GitHub 地址。以前插件不读这个变量,会从 github.com 下载 JDK,安装成功。这一步不下载任何东西,也不运行能正常工作的镜像。 3. 在 `graalce` 这一行里,Oracle GraalVM 只写大版本号的 `25-graal` 应该装 25.0 系列的最新构建(2026-10-20 是 `25.0.5-graal`),也就是 Oracle 官方 `25/latest` 下载跟的那个系列,而不是列在它前面的某个每月特性版本(比如 `25.4.4.1.1-graal`)。`vfox search java graal` 必须按 GraalVM 自己的版本号列出这些特性版本,25 的其他版本备注要一致,同第 5 条。从 2026 年 10 月起,Foojay 给特性版本标的是 JDK 的版本号,和 25.0 系列共用。插件以前按这个版本号命名,结果搜索里一个特性版本都没有,`25.0.4.1-graal` 也没有 LTS 备注,装上的却是 25.4.4.1.1。搜索列出的 25 的各构建会先被模拟成已安装,这样 `vfox install` 报出的就是 `PreInstall` 选中的那个,而且不会下载任何东西。GA 版本 25 不放进去,因为那样 vfox 会发现 `25-graal` 已安装,根本不会去问 `PreInstall`。在 Linux 上,安装向 Foojay 发的请求还必须和搜索的相同,同第 4 条末尾。 装着这些构建时,`vfox use java@25-graal` 和写着 `java=25-graal` 的 `.sdkmanrc` 必须选中 `vfox install` 报出的那个构建。`PreUse` 和 `ParseLegacyFile` 以前选的是 25 里已安装的最新版本,所以一旦装了特性版本,选中的就是它([#110](https://github.com/version-fox/vfox-java/issues/110))。模拟出的每个构建里都放着一个只打印名字的替身 `java`,同第 6 条。把 25.0 系列的构建删掉之后,`vfox use` 和 `.sdkmanrc` 都必须选中最新的特性版本。 @@ -58,7 +60,7 @@ Windows 容器的覆盖(`tests/e2e/windows`)无法在开发容器里运行 | 发行版 | 版本 | 覆盖内容 | amd64 | arm64 | | ------------ | ------- | ---------------------------------------------------------------------------- | ----- | ----- | | `open` | 25.0.2 | 默认发行版,版本不带后缀 | ✓ | | -| `tem` | 21.0.8 | 常规路径(Linux 上是 tar.gz,Windows 上是 zip);第 4、6 条里按名字和按版本号的检查 | ✓ | ✓ | +| `tem` | 21.0.8 | 常规路径(Linux 上是 tar.gz,Windows 上是 zip);第 2、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` | ✓ | | @@ -195,6 +197,7 @@ go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/pre_install_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/pre_use_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/parse_legacy_file_test.lua 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/github_mirror_test.lua go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/post_install_test.lua ``` diff --git a/docs/zh-CN/foojay.md b/docs/zh-CN/foojay.md index 01d346f..029b46f 100644 --- a/docs/zh-CN/foojay.md +++ b/docs/zh-CN/foojay.md @@ -35,7 +35,7 @@ https://api.foojay.io/disco/v3.0/packages/jdks 只写大版本号时和搜索发同一个请求,这样安装时看到的构建和 `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...`,下载内容不做校验。 +返回的是一个包列表。插件用 `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...`,下载内容不做校验。未发布:从 GitHub 下载时可以经过镜像,见 [mirror.md](mirror.md)。 `vfox search` 与原始列表有几处不同:Foojay 里同一个名字有两条记录时,它只列一次;不给 GraalVM 的特性版本加 LTS 备注(见 [distributions.md](distributions.md#oracle-graalvm-与-graalvm-社区版的行为));只列 GA 版本。 diff --git a/docs/zh-CN/mirror.md b/docs/zh-CN/mirror.md new file mode 100644 index 0000000..73d26ab --- /dev/null +++ b/docs/zh-CN/mirror.md @@ -0,0 +1,70 @@ +# GitHub 镜像 + +[中文](mirror.md) | [English](../mirror.md) + +未发布:0.6.2 没有镜像设置。 + +Foojay 把大多数发行版的 JDK 链接到 GitHub Releases,所以 `vfox install java@21-tem` 是从 `github.com` 下载的。如果你那里访问 github.com 很慢,或者连不上,就设置 `VFOX_JAVA_GITHUB_MIRROR`,插件会让这些下载经过 GitHub 镜像。 + +值是一个 URL 前缀。插件把它放在 GitHub 地址前面,值不以 `/` 结尾时会补上一个。以 Linux x64 上的 Temurin 21.0.8 为例: + +| `VFOX_JAVA_GITHUB_MIRROR` | vfox 下载的地址 | +| --- | --- | +| 没有设置 | `https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz` | +| `https://gh-proxy.org/` | `https://gh-proxy.org/https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz` | + +[gh-proxy](https://gh-proxy.org/) 接受这种形式的地址,凡是把 GitHub 地址接在自己地址后面的服务都可以。插件不运营其中任何一个,也不为它们担保。值必须以 `http://` 或 `https://` 开头。别的写法 vfox 会当成磁盘上的文件,所以从 GitHub 下载时,插件会直接报错:`VFOX_JAVA_GITHUB_MIRROR must start with http:// or https://`。 + +## 设置 + +在 bash 或 zsh 里,对当前 shell 生效: + +```bash +export VFOX_JAVA_GITHUB_MIRROR=https://gh-proxy.org/ +``` + +把同一行加进 `~/.bashrc` 或 `~/.zshrc`,就一直生效。在 PowerShell 里: + +```powershell +$env:VFOX_JAVA_GITHUB_MIRROR = "https://gh-proxy.org/" +``` + +要让之后打开的每个终端都生效,执行: + +```powershell +[Environment]::SetEnvironmentVariable('VFOX_JAVA_GITHUB_MIRROR', 'https://gh-proxy.org/', 'User') +``` + +然后照常安装,`vfox install java@21-tem`。想回到 github.com,取消设置这个变量即可。 + +## 哪些下载会经过镜像 + +只有以 `https://github.com/` 开头的地址。其他主机上的 JDK 照旧从那里下载,向 Foojay 发的请求也不变。 + +Foojay 给各发行版的链接(2026-10-21): + +| 简称 | 下载来源 | +| --- | --- | +| `tem`、`librca`、`sem`、`semcrt`、`sapmchn`、`kona`、`albba`、`mandrel`、`trava`、`adpt`、`adptj9`、`eliya`、`ojdkbld`、`gln`、`grl8`、`grl11`、`grl16`、`grl17`、`grl19` | GitHub | +| `graalce` | GitHub,但 `java@25-graalce` 安装的每月特性版本除外,它来自 Oracle 的 GraalVM Download Service。`java@25.4.4.1-graalce` 是同一个文件在 GitHub 上的副本 | +| `nik` | 较新的版本在 GitHub,较旧的在 BellSoft 的服务器上 | +| `open` | jdk.java.net,只有 JDK 8 和大部分 11.x 的构建来自 GitHub | +| `zulu`、`amzn`、`ms`、`oracle`、`graal`、`jb`、`bsg`、`opnlgc`、`rdht` | 厂商的服务器或 CDN,不在 GitHub | + +Loongnix(`lnxabi1`、`lnxabi2`)从 ftp.loongnix.cn 下载,不会经过镜像。 + +想看某个版本是从哪里下载的,向 Foojay 要它的包记录,看其中的 `direct_download_uri`。以 `https://github.com/` 开头的才会经过镜像。其他平台的查询见 [foojay.md](foojay.md)。 + +```bash +curl -s 'https://api.foojay.io/disco/v3.0/packages/jdks?distribution=temurin&version=21.0.8&architecture=amd64&operating_system=linux&lib_c_type=glibc&archive_type=tar.gz&directly_downloadable=true' \ + | jq -r '.result[0].links.pkg_info_uri' | xargs curl -s | jq -r '.result[0].direct_download_uri' +``` + +``` +https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz +``` + +## 补充说明 + +- 插件没有把校验和交给 vfox,所以 vfox 不会校验下载的文件(`WARNING: Checksum is not provided, skip verify...`,见 [foojay.md](foojay.md#插件发出的请求))。经过镜像时,拿到的压缩包就是镜像给的那个,请用你信得过的镜像。 +- 这个变量只改变 `vfox install` 下载 JDK 的地址,`vfox add java` 不会用到它。想让 vfox 的所有请求都走代理,见[网络](troubleshooting.md#网络)。 diff --git a/docs/zh-CN/troubleshooting.md b/docs/zh-CN/troubleshooting.md index 1c6c5f9..5de2971 100644 --- a/docs/zh-CN/troubleshooting.md +++ b/docs/zh-CN/troubleshooting.md @@ -32,7 +32,11 @@ Foojay 在这个平台上没有对应的包:版本、发行版、带不带 Jav ### `source file not found` -下载时服务器返回 404。厂商会下线旧的构建,而 Foojay 仍保留它们的记录。最常见的是 EA 构建(见 [versions.md](versions.md#早期访问ea构建))。换一个较新的构建。Red Hat 除了 Linux x64 以外的构建都会报这个错,见 [distributions.md](distributions.md#red-hat-build-of-openjdkrdht)。 +下载时服务器返回 404。厂商会下线旧的构建,而 Foojay 仍保留它们的记录。最常见的是 EA 构建(见 [versions.md](versions.md#早期访问ea构建))。换一个较新的构建。Red Hat 除了 Linux x64 以外的构建都会报这个错,见 [distributions.md](distributions.md#red-hat-build-of-openjdkrdht)。经过镜像([mirror.md](mirror.md))下载时,镜像返回的 404 也会走到这里:取消设置 `VFOX_JAVA_GITHUB_MIRROR` 再试一次。 + +### `VFOX_JAVA_GITHUB_MIRROR must start with http:// or https://, such as https://gh-proxy.org/, but is gh-proxy.org` + +这个值不是以 `http://` 或 `https://` 开头。vfox 只从 `http://` 和 `https://` 开头的地址下载,别的写法它会当成磁盘上的文件,所以从 GitHub 下载时,插件在下载开始之前就停在这里;其他下载不读这个变量。改正这个值,或者取消设置这个变量,见 [mirror.md](mirror.md)。未发布:0.6.2 没有镜像设置。 ### `java@21-tem is not installed` @@ -48,7 +52,7 @@ Foojay 在这个平台上没有对应的包:版本、发行版、带不带 Jav ## 网络 -插件向 Foojay 取版本列表,压缩包则由 vfox 从厂商的服务器下载,常常是 GitHub Releases。插件没有镜像设置。 +插件向 Foojay 取版本列表,压缩包则由 vfox 从厂商的服务器下载,常常是 GitHub Releases。对从 GitHub 下载的部分,插件有镜像设置,见 [mirror.md](mirror.md)。未发布:0.6.2 没有。 vfox 自己的代理设置对 Foojay 请求和下载都有效: diff --git a/hooks/pre_install.lua b/hooks/pre_install.lua index 1b06db2..f422fd6 100644 --- a/hooks/pre_install.lua +++ b/hooks/pre_install.lua @@ -16,6 +16,7 @@ local http = require("http") local json = require("json") local foojay = require("foojay") +local github_mirror = require("github_mirror") local loongnix = require("loongnix") local distribution_version_parser = require("distribution_version") --- @class PreInstallContext @@ -123,7 +124,7 @@ function PLUGIN:PreInstall(ctx) end return { -- [info.checksum_type] = checksum, - url = info.direct_download_uri, + url = github_mirror.url(info.direct_download_uri), version = finalV, note = jdk.major_version, } diff --git a/lib/github_mirror.lua b/lib/github_mirror.lua new file mode 100644 index 0000000..8e0eaa3 --- /dev/null +++ b/lib/github_mirror.lua @@ -0,0 +1,50 @@ +-- 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. + +--- Downloads from GitHub through a mirror, for those who reach github.com +--- slowly. Many distributions publish their JDKs as GitHub releases: Foojay +--- links Temurin, Liberica and GraalVM Community there, for instance. +--- +--- $VFOX_JAVA_GITHUB_MIRROR holds a URL prefix, such as https://gh-proxy.org/, +--- that goes in front of the whole GitHub URL, the way vfox-flutter does with +--- $VFOX_FLUTTER_GITHUB_MIRROR. URLs of other hosts stay as they are. +local github_mirror = {} + +local ENV = "VFOX_JAVA_GITHUB_MIRROR" +local GITHUB = "https://github.com/" + +--- Returns the URL to download from: url behind the mirror if the variable is +--- set and url is on GitHub, else url, whatever it is. +--- @param url string|nil +--- @return string|nil +function github_mirror.url(url) + if type(url) ~= "string" or url:sub(1, #GITHUB) ~= GITHUB then + return url + end + local prefix = (os.getenv(ENV) or ""):match("^%s*(.-)%s*$") + if prefix == "" then + return url + end + -- vfox downloads what starts with http:// or https:// and takes anything + -- else for a file on disk, which it then fails to move. + if not prefix:find("^https?://") then + error(ENV .. " must start with http:// or https://, such as https://gh-proxy.org/, but is " .. prefix) + end + if prefix:sub(-1) ~= "/" then + prefix = prefix .. "/" + end + return prefix .. url +end + +return github_mirror diff --git a/tests/e2e/common/common.go b/tests/e2e/common/common.go index 2bc8eca..2f074f8 100644 --- a/tests/e2e/common/common.go +++ b/tests/e2e/common/common.go @@ -105,6 +105,30 @@ func ChecksOracleGraalVM(distribution string) bool { // OpenJDK only (https://github.com/version-fox/vfox/issues/390). const ReadmeURL = "https://github.com/version-fox/vfox-java/blob/main/README.md" +// GitHubMirrorEnv is the variable that puts a mirror in front of the +// downloads from GitHub, where Foojay links the JDKs of most distributions: +// Temurin, Liberica, GraalVM Community and others. +const GitHubMirrorEnv = "VFOX_JAVA_GITHUB_MIRROR" + +// BogusMirror is a mirror that does not exist: the name cannot resolve, so a +// download through it fails, and vfox's error names the URL it asked for. +const BogusMirror = "https://invalid.example.invalid" + +// AskedBogusMirror reports whether `vfox install` output shows a download +// from GitHub going through BogusMirror: the error names the URL vfox asked +// for, which is the mirror, a slash and the GitHub URL. Before the plugin +// read GitHubMirrorEnv, it downloaded from github.com and installed. +func AskedBogusMirror(output string) bool { + return strings.Contains(StripANSI(output), BogusMirror+"/https://github.com/") +} + +// ChecksGitHubMirror reports whether a combo also checks the mirror of the +// downloads from GitHub, which downloads nothing. Temurin's combos do: Foojay +// links its JDKs to GitHub on every platform, and they run on every runner. +func ChecksGitHubMirror(distribution string) bool { + return Base(distribution) == "tem" +} + // PassThroughEnvs are host env knobs recorded in each target container's // environment, so `docker inspect` shows what a combo ran with. Forwarded // only when set, so unset stays default. diff --git a/tests/e2e/common/common_test.go b/tests/e2e/common/common_test.go index 37840c7..3df1f8a 100644 --- a/tests/e2e/common/common_test.go +++ b/tests/e2e/common/common_test.go @@ -788,3 +788,46 @@ func TestAsksAsSearch(t *testing.T) { } } } + +func TestAskedBogusMirror(t *testing.T) { + // The error vfox 1.0.13 prints when the mirror's name does not resolve, and + // when a fake-IP DNS answers it and the connection then ends. + url := BogusMirror + "/https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz" + for _, c := range []struct { + name, output string + ok bool + }{ + {"name not resolved", "Preinstalling java@21.0.8+9-tem...\nfailed to download java@21.0.8+9-tem file, err:Get \"" + url + "\": dial tcp: lookup invalid.example.invalid: no such host\nfailed to install java\n", true}, + {"connection ended", "Preinstalling java@21.0.8+9-tem...\nfailed to download java@21.0.8+9-tem file, err:Get \"" + url + "\": EOF\n", true}, + {"colours", "\x1b[91mfailed\x1b[0m to download file, err:Get \"" + url + "\": EOF", true}, + // The plugin ignored the variable: the download went to GitHub. + {"github.com asked", "failed to download java@21.0.8+9-tem file, err:Get \"https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz\": EOF", false}, + // The mirror was asked, but not for the GitHub URL behind it. + {"mirror alone", "failed to download file, err:Get \"" + BogusMirror + "/\": EOF", false}, + {"mirror in place of github.com", "err:Get \"" + BogusMirror + "/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/file.tar.gz\": EOF", false}, + // A failure before the download is retried by the check. + {"foojay unreachable", "Failed to fetch jdk info from foojay: Get \"https://api.foojay.io/disco/v3.0/packages/jdks\": EOF", false}, + {"empty", "", false}, + } { + if got := AskedBogusMirror(c.output); got != c.ok { + t.Errorf("AskedBogusMirror(%s) = %v, want %v", c.name, got, c.ok) + } + } +} + +func TestChecksGitHubMirror(t *testing.T) { + // Temurin's combos run on every runner, arm64 included. + for _, arch := range []string{"amd64", "arm64"} { + if !slices.ContainsFunc(DefaultDistributions(arch), ChecksGitHubMirror) { + t.Errorf("no %s default combo checks the mirror of the downloads from GitHub", arch) + } + } + for distribution, want := range map[string]bool{"tem": true, "librca-fx": false, "jb": false, "open": false, "graalce": false} { + if got := ChecksGitHubMirror(distribution); got != want { + t.Errorf("ChecksGitHubMirror(%s) = %v, want %v", distribution, got, want) + } + } + if !strings.HasPrefix(BogusMirror, "https://") || !strings.HasSuffix(BogusMirror, ".invalid") { + t.Errorf("BogusMirror = %q, want an https URL on the .invalid TLD, which never resolves", BogusMirror) + } +} diff --git a/tests/e2e/linux/e2e_test.go b/tests/e2e/linux/e2e_test.go index 7e9c548..1956d3d 100644 --- a/tests/e2e/linux/e2e_test.go +++ b/tests/e2e/linux/e2e_test.go @@ -170,6 +170,9 @@ func runOne(t *testing.T, ctx context.Context, image, platform, arch string, fro checkNotes(ctx, t, ctr) checkVersionlessRejected(ctx, t, ctr, c.Distribution) checkMissingVersionRejected(ctx, t, ctr, c.Distribution, version, arch) + if common.ChecksGitHubMirror(c.Distribution) { + checkGitHubMirror(ctx, t, ctr, common.Spec(c.Distribution, version)) + } // 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) || diff --git a/tests/e2e/linux/setup.go b/tests/e2e/linux/setup.go index 4127dc6..86630ba 100644 --- a/tests/e2e/linux/setup.go +++ b/tests/e2e/linux/setup.go @@ -107,6 +107,31 @@ func checkMissingVersionRejected(ctx context.Context, t *testing.T, ctr tc.Conta } } +// checkGitHubMirror proves the plugin honours $VFOX_JAVA_GITHUB_MIRROR for the +// downloads from GitHub, where Foojay links Temurin's JDKs: installing through +// a mirror that does not exist (see common.BogusMirror) must fail on the +// download, and vfox's error must name the URL it asked for, the mirror +// followed by the GitHub URL (see common.AskedBogusMirror). Before the plugin +// read the variable, it downloaded the JDK from github.com, and the install +// succeeded. +func checkGitHubMirror(ctx context.Context, t *testing.T, ctr tc.Container, spec string) { + t.Helper() + label := "vfox install java@" + spec + " through a mirror that does not exist" + common.Retry(ctx, t, label+" (must fail)", func() (int, string) { + code, out := execRun(ctx, t, ctr, "vfox install java@"+spec, common.GitHubMirrorEnv+"="+common.BogusMirror) + if code == 0 { + t.Fatalf("%s unexpectedly succeeded\n--- output ---\n%s", label, out) + } + // Any other failure (e.g. Foojay unreachable) is retried like the + // other network-touching calls. + if common.AskedBogusMirror(out) { + return 0, out + } + return code, out + }) + t.Logf("PASS the download of java@%s goes to the mirror that %s names", spec, common.GitHubMirrorEnv) +} + // checkGraalVMBareMajor proves Oracle GraalVM's bare major installs the // newest build of its 25.0 line, not one of the monthly feature releases // searching lists before it (see common.OracleGraalVMSpec), which `vfox diff --git a/tests/e2e/windows/e2e_test.go b/tests/e2e/windows/e2e_test.go index 86621af..af55634 100644 --- a/tests/e2e/windows/e2e_test.go +++ b/tests/e2e/windows/e2e_test.go @@ -246,6 +246,9 @@ func runOne(t *testing.T, ctx context.Context, image, platform, arch string, c c activation := fetchActivation(ctx, t, ctr, box) checkVersionlessRejected(ctx, t, ctr, box, activation, c.Distribution) checkMissingVersionRejected(ctx, t, ctr, box, c.Distribution, version, arch) + if common.ChecksGitHubMirror(c.Distribution) { + checkGitHubMirror(ctx, t, ctr, box, common.Spec(c.Distribution, version)) + } if common.ChecksOracleGraalVM(c.Distribution) { checkGraalVMBareMajor(ctx, t, ctr, box, activation) } diff --git a/tests/e2e/windows/setup.go b/tests/e2e/windows/setup.go index c7c0836..2e40a33 100644 --- a/tests/e2e/windows/setup.go +++ b/tests/e2e/windows/setup.go @@ -326,6 +326,32 @@ func checkMissingVersionRejected(ctx context.Context, t *testing.T, ctr tc.Conta } } +// checkGitHubMirror proves the plugin honours $VFOX_JAVA_GITHUB_MIRROR for the +// downloads from GitHub, where Foojay links Temurin's JDKs: installing through +// a mirror that does not exist (see common.BogusMirror) must fail on the +// download, and vfox's error must name the URL it asked for, the mirror +// followed by the GitHub URL (see common.AskedBogusMirror). Before the plugin +// read the variable, it downloaded the JDK from github.com, and the install +// succeeded. +func checkGitHubMirror(ctx context.Context, t *testing.T, ctr tc.Container, box slotBox, spec string) { + t.Helper() + label := "vfox install java@" + spec + " through a mirror that does not exist" + env := append(slices.Clone(box.vars), common.GitHubMirrorEnv+"="+common.BogusMirror) + common.Retry(ctx, t, label+" (must fail)", func() (int, string) { + code, out := execRun(ctx, t, ctr, "vfox install "+pwshQuote("java@"+spec), env...) + if code == 0 { + t.Fatalf("%s unexpectedly succeeded\n--- output ---\n%s", label, out) + } + // Any other failure (e.g. Foojay unreachable) is retried like the + // other network-touching calls. + if common.AskedBogusMirror(out) { + return 0, out + } + return code, out + }) + t.Logf("PASS the download of java@%s goes to the mirror that %s names", spec, common.GitHubMirrorEnv) +} + // checkGraalVMBareMajor proves Oracle GraalVM's bare major installs the // newest build of its 25.0 line, not one of the monthly feature releases // searching lists before it (see common.OracleGraalVMSpec), which `vfox diff --git a/tests/github_mirror_test.lua b/tests/github_mirror_test.lua new file mode 100644 index 0000000..c384991 --- /dev/null +++ b/tests/github_mirror_test.lua @@ -0,0 +1,98 @@ +-- 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. + +dofile("tests/support.lua") +local github_mirror = require("github_mirror") + +-- The mirror comes from the environment: os.getenv answers for it from this +-- table, whose field the cases set, and for everything else as it did. +local env = {} +local getenv = os.getenv +os.getenv = function(name) + if name == "VFOX_JAVA_GITHUB_MIRROR" then + return env.mirror + end + return getenv(name) +end + +-- Download URLs as Foojay gives them (2026-10-21): Temurin's has a %2B in the +-- path and Liberica's a bare +. +local temurin = + "https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/OpenJDK21U-jdk_x64_linux_hotspot_21.0.8_9.tar.gz" +local liberica = "https://github.com/bell-sw/Liberica/releases/download/27+36/bellsoft-jdk27+36-linux-amd64.tar.gz" + +local cases = 0 +local function check(mirror, url, expected) + env.mirror = mirror + local got = github_mirror.url(url) + assert( + got == expected, + ("mirror %q, %s: expected %s, got %s"):format(tostring(mirror), tostring(url), expected, got) + ) + cases = cases + 1 +end + +-- Without a mirror, or with an empty one, nothing changes. +for _, mirror in ipairs({ "", " ", "\t\n" }) do + check(mirror, temurin, temurin) +end +check(nil, temurin, temurin) + +-- A mirror is a prefix of the whole URL, whether or not it ends in a slash. +check("https://gh-proxy.org/", temurin, "https://gh-proxy.org/" .. temurin) +check("https://gh-proxy.org", temurin, "https://gh-proxy.org/" .. temurin) +check("https://gh-proxy.org/", liberica, "https://gh-proxy.org/" .. liberica) +check("https://mirror.example:8443/github/", liberica, "https://mirror.example:8443/github/" .. liberica) +check("http://127.0.0.1:8080", liberica, "http://127.0.0.1:8080/" .. liberica) +-- Blanks around the value, as a shell or a profile can leave them, are dropped. +check(" https://gh-proxy.org/\n", temurin, "https://gh-proxy.org/" .. temurin) + +-- Only github.com is mirrored: not the other hosts JDKs come from, nor +-- addresses that merely start like it. +for _, url in ipairs({ + "https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz", + "https://cache-redirector.jetbrains.com/intellij-jbr/jbr-21.0.5-linux-x64-b631.28.tar.gz", + "https://aka.ms/download-jdk/microsoft-jdk-21.0.8-linux-x64.tar.gz", + "https://api.foojay.io/disco/v3.0/ids/0123456789abcdef", + "http://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.8%2B9/file.tar.gz", + "https://github.com.example.org/adoptium/file.tar.gz", + "https://raw.githubusercontent.com/adoptium/file.tar.gz", + "https://gist.github.com/adoptium/file.tar.gz", + "https://github.com", + "", +}) do + check("https://gh-proxy.org/", url, url) +end +-- A missing URL is left to vfox to report. +env.mirror = "https://gh-proxy.org/" +assert(github_mirror.url(nil) == nil, "nil: expected nil") +cases = cases + 1 + +-- Something vfox would not download is refused with a message naming the +-- variable and the value, but only when a GitHub URL needs it. These cases come +-- last: once pcall has caught an error, gopher-lua's closures no longer see +-- later assignments to the locals around them. +for _, mirror in ipairs({ "gh-proxy.org", "gh-proxy.org/", "ftp://gh-proxy.org/", "HTTPS://gh-proxy.org/", "false" }) do + env.mirror = mirror + local ok, err = pcall(github_mirror.url, temurin) + local want = "VFOX_JAVA_GITHUB_MIRROR must start with http:// or https://, such as https://gh-proxy.org/, but is " + assert(not ok and tostring(err):find(want .. mirror, 1, true), mirror .. ": unexpected " .. tostring(err)) + assert( + github_mirror.url("https://download.oracle.com/file.tar.gz") == "https://download.oracle.com/file.tar.gz", + mirror .. ": a URL that is not on GitHub needs no mirror" + ) + cases = cases + 2 +end + +print("PASS: " .. cases .. " GitHub mirror cases") diff --git a/tests/pre_install_test.lua b/tests/pre_install_test.lua index f01c8da..feff522 100644 --- a/tests/pre_install_test.lua +++ b/tests/pre_install_test.lua @@ -15,6 +15,7 @@ dofile("tests/support.lua") local JDKS = "https://api.foojay.io/disco/v3.0/packages/jdks?" local IDS = "https://api.foojay.io/disco/v3.0/ids/" +local GITHUB = "https://github.com/graalvm/graalvm-ce-builds/releases/download/" local function pkg(java_version, fx, archive_type) archive_type = archive_type or "tar.gz" @@ -330,7 +331,11 @@ package.preload.http = function() else assert(request.url:sub(1, #IDS) == IDS, "unexpected request: " .. request.url) local id = request.url:sub(#IDS + 1) - body = { result = { { direct_download_uri = "https://download.invalid/" .. id } } } + -- The downloads of GraalVM Community that graal() gives the + -- host github are on GitHub, where the id puts the file after + -- it. All others are somewhere else. + local site = id:sub(1, 7) == "github/" and GITHUB or "https://download.invalid/" + body = { result = { { direct_download_uri = site .. id } } } end table.insert(responses, body) return { status_code = 200, body = tostring(#responses) }, nil @@ -458,6 +463,50 @@ for distribution, version in pairs({ graalvm_community = "25.0.4.1", graalvm = " local found = require("foojay").fetchtJdkList(distribution, version) assert(#found == 0, distribution .. " " .. version .. ": found " .. tostring(found[1] and found[1].filename)) end +-- $VFOX_JAVA_GITHUB_MIRROR puts a mirror in front of the downloads from GitHub +-- and leaves the others alone. Foojay links GraalVM Community's 25.4.4.1 to +-- GitHub and its 25 to Oracle's download service. The mirror is a table field, +-- as the error cases at the end of the file need. +local mirror = {} +local getenv = os.getenv +os.getenv = function(name) + if name == "VFOX_JAVA_GITHUB_MIRROR" then + return mirror.value + end + return getenv(name) +end +local function download(version) + return PLUGIN:PreInstall({ version = version }).url +end +local github = GITHUB .. "github/graalvm-community-jdk-25i4-25.0.4.1.1_linux-x64_bin.tar.gz" +local oracle = "https://download.invalid/gds/graalvm-community-jdk-25i4-25.0.4.1.1_linux-x64_bin.tar.gz" +for _, case in ipairs({ + { nil, github }, + { "https://gh-proxy.org/", "https://gh-proxy.org/" .. github }, + { "https://gh-proxy.org", "https://gh-proxy.org/" .. github }, + { "", github }, +}) do + mirror.value = case[1] + assert( + download("25.4.4.1-graalce") == case[2], + "mirror " .. tostring(case[1]) .. ": " .. download("25.4.4.1-graalce") + ) + assert(download("25-graalce") == oracle, "mirror " .. tostring(case[1]) .. " changed a download from Oracle") + assert(download("21-tem") == "https://download.invalid/21.0.12.1+1.tar.gz", "mirror changed a download of Temurin") + cases = cases + 3 +end +-- Windows downloads the zip of the same file. +RUNTIME.osType = "windows" +mirror.value = "https://gh-proxy.org/" +assert( + download("25.4.4.1-graalce") + == "https://gh-proxy.org/" .. GITHUB .. "github/graalvm-community-jdk-25i4-25.0.4.1.1_windows-x64_bin.zip", + "mirror on Windows: " .. download("25.4.4.1-graalce") +) +RUNTIME.osType = "linux" +mirror.value = nil +cases = cases + 1 + -- Early-access builds go by their Java version. check("26.ea.13-graal", "26-ea+13-graal", false) -- Anything longer than a major still matches exactly. @@ -700,4 +749,15 @@ for _, version in ipairs({ "23.1.3-mandrel", "23.1.8+1-nik", "26-mandrel" }) do assert(#requests == 1, version .. ": expected one lookup, got " .. #requests) end +-- A mirror that vfox would not download from stops a download from GitHub, with +-- a message that names the variable, and leaves the others alone. +mirror.value = "gh-proxy.org" +local ok, err = pcall(PLUGIN.PreInstall, PLUGIN, { version = "25.4.4.1-graalce" }) +assert( + not ok and tostring(err):find("VFOX_JAVA_GITHUB_MIRROR must start with http:// or https://", 1, true), + "a mirror without a scheme: unexpected " .. tostring(err) +) +assert(download("25-graalce") == oracle, "a mirror without a scheme stopped a download from Oracle") +cases = cases + 2 + print("PASS: " .. cases .. " PreInstall Foojay lookup cases")