diff --git a/README.md b/README.md index 9b53905..802901d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ # vfox-flutter -Flutter plugin for [vfox](https://vfox.dev/). +[中文](README_CN.md) | [English](README.md) + +[Flutter](https://flutter.dev/) plugin for [vfox](https://vfox.dev/). + +It installs the Flutter SDK from the release archives that Google publishes for Linux x64, macOS +and Windows x64, checked against the SHA-256 checksums of Flutter's release index. On Linux ARM64 +and Windows ARM64, where there is no archive, it installs the official `flutter/flutter` git tag +instead. It also installs the OpenHarmony builds of Flutter from gitcode.com. ## Install @@ -10,6 +17,9 @@ After installing [vfox](https://github.com/version-fox/vfox), install the plugin vfox add flutter ``` +vfox has to be hooked into your shell for `flutter` to be on the `PATH`, see its +[quick start](https://vfox.dev/guides/quick-start.html). + The Flutter SDK includes the Dart SDK; if you install the Flutter SDK via the current vfox plugin, there is no need to separately install the Dart SDK using the vfox plugin from @@ -31,21 +41,59 @@ flutter --version --no-version-check dart --version ``` +`vfox search flutter` lists the versions you can install. In an interactive terminal it lets you +pick one and installs it. + +Behavior that only exists on `main` and not in the latest release is marked "Unreleased" in the +pages below. [docs/changes.md](docs/changes.md) lists it, and +[docs/install-from-source.md](docs/install-from-source.md) gets you `main`. + +## Versions and names + +A version is a release as `vfox search flutter` prints it: `3.47.4`, or a beta such as +`3.50.0-0.1.pre`. `-x64` or `-arm64` after it asks for that architecture instead of the +machine's, and `stable` and `beta` stand for the release the channel points to. Versions are +matched whole, so `flutter@3.47` does not select the newest 3.47.x. `vfox use`, `vfox uninstall` +and `vfox exec` take the name that is installed, as `vfox list flutter` shows it. +`flutter@latest` is the first line of the list, the newest stable release (Unreleased: release +0.2.4 installs the newest beta). See [docs/versions.md](docs/versions.md) for the names, how +installs are named and the order of the list. + ## Channels -See [docs/channels.md](docs/channels.md) for details. +`vfox install flutter@stable` and `flutter@beta` install the release the channel points to today, +and name the install after that release (`3.47.7`), so `vfox use flutter@stable` fails: use the +version. Flutter retired the `dev` channel. See [docs/channels.md](docs/channels.md) for details. + +## Platforms + +Linux, macOS and Windows on x64 and ARM64. See [docs/platforms.md](docs/platforms.md) for what +installs from where, what vfox and `git` have to be there, which hosts the plugin connects to +and what the tests cover. ## Architecture selection -See [docs/arm64.md](docs/arm64.md) for details. +`flutter@3.47.4-x64` and `flutter@3.47.4-arm64` install a specific build, and a plain name takes +the one for the machine. Linux ARM64 and Windows ARM64 install from a git tag, and offer the +releases that run with a native Dart SDK (Unreleased: release 0.2.4 offers older ones, with an +x64 Dart SDK). See [docs/arm64.md](docs/arm64.md) for details. ## Mirror -See [docs/mirror.md](docs/mirror.md) for details. +`FLUTTER_STORAGE_BASE_URL` selects a mirror such as CFUG's or CERNET's for the release index and the +archives, and `VFOX_FLUTTER_GITHUB_MIRROR` one for the GitHub clone of the source installs. See +[docs/mirror.md](docs/mirror.md) for details. ## OpenHarmony builds -See [docs/ohos.md](docs/ohos.md) for details. +`vfox install flutter@3.41.10-ohos-1.0.0` installs the OpenHarmony fork of Flutter from gitcode.com +as a git checkout. These builds are listed after the official releases, and `@latest` never +selects one. See [docs/ohos.md](docs/ohos.md) for details. + +## Troubleshooting + +See [docs/troubleshooting.md](docs/troubleshooting.md) for the messages the plugin and vfox print, +proxies, a rate-limited gitcode.com, the things that look wrong and are not, and where Flutter is. ## FAQ @@ -57,9 +105,14 @@ See [docs/flutter-upgrade.md](docs/flutter-upgrade.md) for details. See [docs/install-from-source.md](docs/install-from-source.md) for details. -### Testing +### Development + +See [docs/development.md](docs/development.md) for how the plugin works and how to change it, and +[docs/e2e.md](docs/e2e.md) for the container tests. -See [docs/e2e.md](docs/e2e.md) for details. +### Changes + +See [docs/changes.md](docs/changes.md) for what changed in each release. ## Releasing this plugin @@ -75,3 +128,9 @@ the tag. If publication fails, re-run the original failed job to resume it. The workflow follows the shared `@v1` release-tool version. Updating the tool does not release this plugin. See the [shared workflow documentation](https://github.com/version-fox/plugin-manifest-action) for the package contract and first-rollout requirements. + +## Thanks + +- [Flutter](https://github.com/flutter/flutter), whose release index and archives the plugin installs from +- [CFUG and CERNET](docs/mirror.md), whose mirrors of Flutter's storage the plugin can use +- [CPF-Flutter](https://gitcode.com/CPF-Flutter/flutter_flutter), whose OpenHarmony builds of Flutter the plugin installs diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..1bc3be4 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,99 @@ +# vfox-flutter + +[中文](README_CN.md) | [English](README.md) + +[vfox](https://vfox.dev/) 的 [Flutter](https://flutter.dev/) 插件。 + +它从 Google 发布的压缩包安装 Flutter SDK(Linux x64、macOS 和 Windows x64),并用 Flutter 发布索引里的 SHA-256 校验和做校验。Linux ARM64 和 Windows ARM64 没有现成的压缩包,插件改为安装官方 `flutter/flutter` 仓库的 git 标签。它还会从 gitcode.com 安装 OpenHarmony 版本的 Flutter。 + +## 安装 + +先安装 [vfox](https://github.com/version-fox/vfox),然后执行下面的命令安装插件: + +```bash +vfox add flutter +``` + +要让 `flutter` 出现在 `PATH` 里,需要先把 vfox 挂载到 Shell,见 vfox 的[快速入门](https://vfox.dev/zh-hans/guides/quick-start.html)。 + +Flutter SDK 自带 Dart SDK;如果通过本插件安装 Flutter SDK,就不需要再用 https://github.com/version-fox/vfox-dart 里的 vfox 插件单独安装 Dart SDK。 + +### 示例:Ubuntu 26.04.1 + +```bash +vfox add flutter +vfox install flutter@3.47.4 +sudo apt install --assume-yes curl git unzip xz-utils zip libglu1-mesa +vfox use --global flutter@3.47.4 +``` + +可以用下面的命令验证: + +```bash +flutter --version --no-version-check +dart --version +``` + +`vfox search flutter` 会列出可以安装的版本,在交互式终端里还可以直接选一个并安装。 + +只存在于 `main`、还没进入最新发布版的行为,在下面各页里标有“未发布”。完整列表见 [docs/zh-CN/changes.md](docs/zh-CN/changes.md),想直接用上 `main`,见 [docs/zh-CN/install-from-source.md](docs/zh-CN/install-from-source.md)。 + +## 版本与命名 + +版本就是 `vfox search flutter` 打印出的发布版本:`3.47.4`,或者 `3.50.0-0.1.pre` 这样的 beta。后面加 `-x64` 或 `-arm64`,表示要这个架构而不是本机架构;`stable` 和 `beta` 代表该渠道当前指向的版本。版本必须整串匹配,所以 `flutter@3.47` 不会选中最新的 3.47.x。`vfox use`、`vfox uninstall` 和 `vfox exec` 要写已安装的名字,也就是 `vfox list flutter` 里显示的那个。`flutter@latest` 是列表的第一行,即最新的稳定版(未发布:0.2.4 装的是最新的 beta)。名字、安装后的命名和列表的顺序见 [docs/zh-CN/versions.md](docs/zh-CN/versions.md)。 + +## 渠道 + +`vfox install flutter@stable` 和 `flutter@beta` 安装该渠道当前指向的版本,并按那个版本命名(`3.47.7`),所以 `vfox use flutter@stable` 会失败:要写版本号。Flutter 已经废弃 `dev` 渠道。详见 [docs/zh-CN/channels.md](docs/zh-CN/channels.md)。 + +## 平台 + +x64 和 ARM64 上的 Linux、macOS 和 Windows。各平台从哪里安装、需要具备哪些条件(vfox 和 `git`)、插件会连接哪些地址,以及测试覆盖哪些内容,见 [docs/zh-CN/platforms.md](docs/zh-CN/platforms.md)。 + +## 架构选择 + +`flutter@3.47.4-x64` 和 `flutter@3.47.4-arm64` 安装指定架构的构建,不带后缀的名字则安装本机架构的那个。Linux ARM64 和 Windows ARM64 从 git 标签安装,只提供能用原生 Dart SDK 运行的版本(未发布:0.2.4 还会提供更老的版本,它们用的是 x64 的 Dart SDK)。详见 [docs/zh-CN/arm64.md](docs/zh-CN/arm64.md)。 + +## 镜像 + +`FLUTTER_STORAGE_BASE_URL` 指定发布索引和压缩包的镜像,比如 CFUG 或 CERNET 的镜像;`VFOX_FLUTTER_GITHUB_MIRROR` 指定源码安装时 GitHub 克隆用的镜像。详见 [docs/zh-CN/mirror.md](docs/zh-CN/mirror.md)。 + +## OpenHarmony 构建版本 + +`vfox install flutter@3.41.10-ohos-1.0.0` 从 gitcode.com 以 git 检出的方式安装 OpenHarmony 分支版的 Flutter。这些版本排在官方版本之后,`@latest` 永远不会选中它们。详见 [docs/zh-CN/ohos.md](docs/zh-CN/ohos.md)。 + +## 问题排查 + +插件和 vfox 打印的各种提示、代理、被限流的 gitcode.com、看起来不对但其实正常的现象,以及 Flutter 的位置,见 [docs/zh-CN/troubleshooting.md](docs/zh-CN/troubleshooting.md)。 + +## 常见问题 + +### 关于 `flutter upgrade` + +详见 [docs/zh-CN/flutter-upgrade.md](docs/zh-CN/flutter-upgrade.md)。 + +### 从源码安装 + +详见 [docs/zh-CN/install-from-source.md](docs/zh-CN/install-from-source.md)。 + +### 开发 + +插件的工作方式和修改方法见 [docs/zh-CN/development.md](docs/zh-CN/development.md),容器测试见 [docs/zh-CN/e2e.md](docs/zh-CN/e2e.md)。 + +### 变更记录 + +每个版本改了什么,见 [docs/zh-CN/changes.md](docs/zh-CN/changes.md)。 + +## 发布这个插件 + +维护者在默认分支上执行 **Actions → Plugin → Run workflow**,填入不带 `v` 前缀的稳定版插件版本号即可发布。共享工作流会更新 `metadata.lua`,创建版本提交和标签,并把 ZIP 包和 manifest 发布到本仓库;不需要在本地打标签,也不需要额外的发布令牌。PR 只运行检查,PR 标题不再触发发布。 + +直接推送版本标签也可以发布,前提是 `PLUGIN.version` 已经与标签一致。发布失败时,重新运行失败的那个作业即可接着发布。 + +工作流引用的是共享的 `@v1` 发布工具,升级这个工具不会发布本插件。包的约定和首次接入的要求,见[共享工作流的文档](https://github.com/version-fox/plugin-manifest-action)。 + +## 致谢 + +- [Flutter](https://github.com/flutter/flutter),插件安装用的发布索引和压缩包都来自它 +- [CFUG 和 CERNET](docs/zh-CN/mirror.md),插件可以使用它们提供的 Flutter 存储镜像 +- [CPF-Flutter](https://gitcode.com/CPF-Flutter/flutter_flutter),插件安装的 OpenHarmony 版 Flutter 来自它 diff --git a/docs/arm64.md b/docs/arm64.md index 11d9247..2d3c91e 100644 --- a/docs/arm64.md +++ b/docs/arm64.md @@ -1,7 +1,10 @@ # Architecture selection +[中文](zh-CN/arm64.md) | [English](arm64.md) + Available releases include architecture suffixes such as `3.47.4-arm64` and -`3.47.4-x64`. You can install both builds and switch between them: +`3.47.4-x64`. You can install both builds and switch between them. On an Apple +silicon Mac: ```bash vfox install flutter@3.47.4-arm64 @@ -17,10 +20,62 @@ first, followed by other architectures, with each group ordered by version. This keeps `@latest` on the default architecture. Only architectures provided by Flutter for the current operating system are listed. +A request for another one fails with `no installable runtime provided`. + +## What each machine gets + +| Machine | `3.47.4`, `stable` | `3.47.4-x64` | `3.47.4-arm64` | +| --- | --- | --- | --- | +| Linux x64 | the Linux archive | the same archive, installed as `3.47.4-x64` | not offered | +| Linux ARM64 | a source install of the git tag | the Linux x64 archive, which does not run here | a source install, named `3.47.4-arm64` | +| macOS Apple silicon | the arm64 archive | the x64 archive, which needs Rosetta | the arm64 archive, named `3.47.4-arm64` | +| macOS Intel | the x64 archive | the same archive, named `3.47.4-x64` | the arm64 archive, which does not run here | +| Windows x64 | the Windows archive | the same archive, installed as `3.47.4-x64` | not offered | +| Windows ARM64 | a source install of the git tag | the Windows x64 archive | a source install, named `3.47.4-arm64` | + +The names in the last two columns are the installed names, see +[versions.md](versions.md#installed-names). Installing `3.47.4` and `3.47.4-x64` on an +x64 machine gives two installs of the same Flutter. Write the suffix always or never. -## MacOS ARM64 +## Releases without architecture metadata + +Flutter's release index gives an architecture (`dart_sdk_arch`) for most releases, +and none for some old ones. On 2026-10-21 these were the stable releases `1.17.0` +to `1.17.5`, `1.22.5` and `1.22.6`, and the pre-releases up to `2.10.0-0.3.pre`, +114 versions in all. The plugin lists them without a suffix on every machine and +installs the one archive Flutter published, an x64 build: + +```bash +vfox install flutter@1.22.6 +``` -On Apple Silicon, running an x64 build requires Rosetta. +They are never offered as `-arm64` or `-x64`, so `vfox install flutter@1.22.6-arm64` and +`vfox install flutter@1.22.6-x64` fail. Because they count as the default +architecture, they sort among the native builds and not after the foreign ones. +On Linux ARM64 and Windows ARM64 they get no source install either (see +[Limits](#limits)), so `vfox install flutter@1.22.6` installs the x64 archive there. + +## macOS + +### Apple silicon + +Running an x64 build requires Rosetta. + +Flutter published arm64 builds from `2.12.0-4.1.pre` (pre-releases) and `3.0.0` +(stable) on. An older release has an x64 build only, and the plain name does not fall +back to it, so ask for it: + +```bash +vfox install flutter@2.10.5-x64 +``` + +`vfox install flutter@2.10.5` fails with `no installable runtime provided`. + +### Intel + +The macOS release index holds arm64 builds whatever the Mac, so they are listed on an +Intel Mac too, and `vfox install flutter@3.47.4-arm64` installs one. It cannot run on an +Intel Mac. ## Linux ARM64 @@ -39,12 +94,63 @@ publishes no `windows-arm64` archives. The Flutter tool bootstraps the matching native `dart-sdk-windows-arm64` itself on first run, so the result is a native SDK. -Explicit `-x64` requests still resolve to the upstream archive, which cannot -run on Windows ARM64. +Explicit `-x64` requests still resolve to the upstream x64 archive. It is not a +native build, and the plugin's tests do not run it. + +## How a source install works + +Linux ARM64 and Windows ARM64 list a `-arm64` source install for the releases that have +an x64 build and run with a native Dart SDK (288 on Linux and 37 on Windows on 2026-10-21), +taken from the same release index, see [Releases that run natively](#releases-that-run-natively). +Installing one: + +1. starts from an empty directory `.vfox/tmp/` under `$VFOX_HOME`, or under your + home directory when `VFOX_HOME` is not set (`~/.vfox/tmp/3.47.4`, and for + `3.47.4-arm64` `~/.vfox/tmp/3.47.4-arm64`), +2. runs `git init` there and sets `core.longpaths=true` and `core.autocrlf=false`, +3. runs `git fetch --depth 1 https://github.com/flutter/flutter.git refs/tags/3.47.4`, + up to three times, +4. checks out `FETCH_HEAD` and checks that `bin/internal/engine.version` exists, +5. hands the directory to vfox, which moves it to the install location (vfox prints + `Moving ... to ...`). + +The result is a shallow clone of one commit, checked out at a tag and not on a branch. +It is about 225 MB, and fetching it for 3.47.7 took 10 seconds on a fast connection. That +checkout is also why `flutter --version` on such an install says `channel [user-branch]` +and `unknown source`, and not `channel stable`. + +A failed install leaves the directory behind. The next install of the same name starts +by deleting it, and it is safe to delete yourself. + +## Releases that run natively + +The point of an `-arm64` source install is a native Dart SDK. The Flutter tool takes the ARM64 +Dart SDK on its first run only when two things hold: the release's own bootstrap script +(`bin/internal/update_dart_sdk.sh`, `update_dart_sdk.ps1` on Windows) has to ask for it, and +Flutter's storage has to have it (`dart-sdk--arm64.zip` under the engine revision of the +release). This is the oldest release for which both hold, checked on 2026-10-21 for the 300 +releases that have an x64 build: + +| Machine | Native Dart SDK from | Before it | +| --- | --- | --- | +| Linux ARM64 | Flutter 2.2.0 | The script asks for the x64 SDK, `1.20.0` to `2.0.6` (17 releases). Storage has the ARM64 SDK from 2.0.0 on, but these scripts do not ask for it, and an ARM64 machine does not run the x64 SDK | +| Windows ARM64 | Flutter 3.44.0-0.1.pre | The script asks for the ARM64 SDK from `3.20.0-1.1.pre` on and, when storage has none, says `The current channel's Dart SDK does not support Windows Arm64, falling back to Windows x64...` and takes the x64 SDK, which Windows runs through its emulation. Storage has the ARM64 SDK for seven older betas, three of which the tool would use (`3.20.0-1.1.pre`, `3.21.0-0.0.pre` and `3.30.0-0.1.pre`) | + +The plugin offers a `-arm64` source install from that release on. `vfox install flutter@1.20.0` +on Linux ARM64, and `vfox install flutter@3.43.0-0.3.pre` on Windows ARM64, end with +`no installable runtime provided`. The x64 archives of the older releases are still listed: +`vfox install flutter@3.43.0-0.3.pre-x64` installs one, which Windows ARM64 runs emulated, +and `flutter@1.20.0-x64` on Linux ARM64 is for where you can run x64 programs. The table is +data in `lib/source.lua`, and [development.md](development.md#refreshing-the-dart-sdk-table) +shows how to check it. + +Unreleased: release 0.2.4 offers a `-arm64` source install of every release that has an x64 +build, and the older ones get the x64 Dart SDK. On Windows ARM64 it runs through the emulation, +under a name that says arm64. On Linux ARM64 an ARM64 machine does not run it. -# Limits +## Limits -## Linux ARM64 and Windows ARM64 +### Linux ARM64 and Windows ARM64 - **`git` is required.** The checkout runs `git init` / `fetch` / `checkout` against `https://github.com/flutter/flutter.git`. A fetch that stalls (under @@ -54,8 +160,23 @@ run on Windows ARM64. builds the Flutter tool, so it needs a network connection. - **No checksum is verified.** Git history is the integrity check. - **No `-arm64` variants for legacy releases.** Versions without architecture - metadata (such as `2.10.0`) keep their existing x64-only behavior. -- **Mirrors don't apply to archive downloads.** `FLUTTER_STORAGE_BASE_URL` - (see `mirror.md`) only affects archive downloads; source installs clone - from GitHub. Set `VFOX_FLUTTER_GITHUB_MIRROR` (see `mirror.md`) to mirror the - GitHub source install. + metadata (such as `1.22.6`, see above) keep their existing x64-only behavior. +- **Mirrors don't apply to the clone.** `FLUTTER_STORAGE_BASE_URL` + (see [mirror.md](mirror.md)) only affects the release index, the x64 archives and the + Flutter tool's own downloads; source installs clone from GitHub. Set + `VFOX_FLUTTER_GITHUB_MIRROR` (see [mirror.md](mirror.md)) to mirror the GitHub source + install. +- **Old releases are not offered.** They do not run with a native Dart SDK, see [Releases + that run natively](#releases-that-run-natively). +- **Your git configuration applies.** The plugin runs `git` as you do, except for line + endings: it sets `core.autocrlf=false` in the checkout, so that a global `true` (which the Git + for Windows installer proposes by default) cannot write Flutter's shell scripts with CRLF + line endings. Unreleased: release 0.2.4 takes your setting. With `core.autocrlf=true`, the + checkout of a Linux ARM64 install or of an OpenHarmony build on Linux has CRLF in + `bin/internal/shared.sh` and Flutter's other `.sh` files, because Flutter's `.gitattributes` + forces LF only for `bin/flutter`, `bin/flutter-dev` and `bin/dart`, and the first `flutter` run + stops with `bin/internal/shared.sh: line 5: $'\r': command not found`. Set + `git config --global core.autocrlf input` (or `false`), uninstall the version and install it + again. +- **Leftovers.** A failed install leaves `.vfox/tmp/` behind, see + [How a source install works](#how-a-source-install-works). diff --git a/docs/changes.md b/docs/changes.md new file mode 100644 index 0000000..8a92c6d --- /dev/null +++ b/docs/changes.md @@ -0,0 +1,48 @@ +# Changes + +[中文](zh-CN/changes.md) | [English](changes.md) + +What changed in each release, as a user notices it. GitHub's release pages and the pull requests linked below have the rest. The newest release is 0.2.4, and `main` is ahead of it by everything under "Unreleased". [install-from-source.md](install-from-source.md) gets you those changes now. + +## Unreleased + +- Installing an official release no longer asks gitcode.com. Release 0.2.4 asked it for every install, and when gitcode.com rate-limited its API (HTTP 429) an install or a `vfox search` waited up to 6.5 minutes and printed nothing: 212 and 392 seconds on 2026-10-21. `vfox search` and the `latest` of `vfox install` still list the OpenHarmony releases, but they make two attempts, give up at HTTP 429, and print a warning on stderr when they get no list. Installing an OpenHarmony build waits as before. See [troubleshooting.md](troubleshooting.md#gitcodecom-is-rate-limited). +- `3.13.3`, `3.9.0-0.1.pre` and `3.17.0-0.0.pre` install. Flutter uploaded their files again and left the old checksums in the index next to the new ones, and release 0.2.4 could take an old one and end with `checksum error`. The list keeps the newest entry of an archive now, which has the checksum of the file. See [troubleshooting.md](troubleshooting.md#checksum-error). +- OpenHarmony builds that are tagged `+ohos-` are listed and install: `3.41.9+ohos-1.0.2`, `3.35.7+ohos-1.0.5`, `3.27.4+ohos-1.0.9` and `3.22.3+ohos-1.1.6`. See [ohos.md](ohos.md#names-and-releases). +- The checkout of a source install sets `core.autocrlf=false`. With a global `core.autocrlf=true` release 0.2.4 wrote Flutter's shell scripts with CRLF line endings on Linux, for Linux ARM64 and for OpenHarmony builds, and `flutter` stopped with `bin/internal/shared.sh: line 5: $'\r': command not found`. See [arm64.md](arm64.md#limits). +- Linux ARM64 and Windows ARM64 offer a `-arm64` source install only from the first release that runs with a native Dart SDK there, Flutter 2.2.0 and 3.44.0-0.1.pre. Release 0.2.4 offered older ones too, with the x64 Dart SDK: an ARM64 Linux machine does not run it, and Windows runs it through its emulation under a name that says arm64. The `-x64` archives of those releases are still listed. See [arm64.md](arm64.md#releases-that-run-natively). +- `vfox search` lists the stable releases before the pre-releases, so `vfox install flutter@latest` installs the newest stable release (`3.47.7-x64` on 2026-10-21) and not the newest beta (`3.50.0-0.1.pre-x64`). See [versions.md](versions.md#searching). +- The message for a CPU the plugin does not support has its spaces: `flutter does not support the arm architecture`. + +## 0.2.4 (2026-10-17) + +- The `dev` channel is gone. Flutter retired it (its last release is `2.13.0-0.1.pre` from April 2022), and `vfox install flutter@dev` no longer resolves. `stable` and `beta` are documented in [channels.md](channels.md) ([#47](https://github.com/version-fox/vfox-flutter/pull/47)). +- Windows accounts whose home directory contains a space, such as `C:\Users\John Doe`, work with vfox 1.0.13 or later ([#48](https://github.com/version-fox/vfox-flutter/pull/48)). +- A `git fetch` that stalls, under 1 KB/s for a minute, is abandoned and tried again, up to three times. It applies to the source installs of Linux ARM64 and Windows ARM64 and to OpenHarmony builds ([#49](https://github.com/version-fox/vfox-flutter/pull/49)). + +## 0.2.3 (2026-10-10) + +- Linux ARM64 ([#23](https://github.com/version-fox/vfox-flutter/pull/23)) and Windows ARM64 ([#24](https://github.com/version-fox/vfox-flutter/pull/24)) install the official `flutter/flutter` git tag, since Google publishes no archive for them. See [arm64.md](arm64.md). +- OpenHarmony builds from gitcode.com, such as `3.41.10-ohos-1.0.0` ([#16](https://github.com/version-fox/vfox-flutter/pull/16)). Fixes for installing them on Windows ([#17](https://github.com/version-fox/vfox-flutter/pull/17), [#18](https://github.com/version-fox/vfox-flutter/pull/18)), and a wait before the next attempt when gitcode.com answers with HTTP 429 ([#31](https://github.com/version-fox/vfox-flutter/pull/31)). See [ohos.md](ohos.md). +- `vfox use` warns when `flutter upgrade` or a similar command has moved a vfox-managed SDK to another commit, and shows the commands that restore it ([#27](https://github.com/version-fox/vfox-flutter/pull/27)). See [flutter-upgrade.md](flutter-upgrade.md). +- The first `flutter` run after an install from an archive no longer rebuilds the Flutter tool ([#39](https://github.com/version-fox/vfox-flutter/pull/39), [#37](https://github.com/version-fox/vfox-flutter/issues/37)). +- `VFOX_FLUTTER_GITHUB_MIRROR` mirrors the GitHub clone of the source installs ([#34](https://github.com/version-fox/vfox-flutter/pull/34), which replaced the automatic fallback of [#29](https://github.com/version-fox/vfox-flutter/pull/29)). A fetch that fails is tried up to three times. See [mirror.md](mirror.md). +- The release index is asked up to three times, five seconds apart, and the error names the HTTP status ([#24](https://github.com/version-fox/vfox-flutter/pull/24)). +- `vfox add flutter` prints a link to the README ([#30](https://github.com/version-fox/vfox-flutter/pull/30)). + +## 0.2.2 (2026-09-12) + +- Every architecture variant is listed, such as `3.47.4-x64` and `3.47.4-arm64`, so you can install either and switch between them. Plain names keep the architecture of the machine and their installed names ([#6](https://github.com/version-fox/vfox-flutter/pull/6)). See [arm64.md](arm64.md). + +## 0.2.1 (2026-09-12) + +- `FLUTTER_STORAGE_BASE_URL` selects a mirror for the release index and the archives ([#8](https://github.com/version-fox/vfox-flutter/pull/8)), and the list of mirrors is up to date ([#10](https://github.com/version-fox/vfox-flutter/pull/10)). See [mirror.md](mirror.md). + +## 0.2.0 (2024-07-01) + +- macOS gets the build for its architecture, Apple silicon or Intel, and the list is sorted with the newest release first ([#3](https://github.com/version-fox/vfox-flutter/pull/3)). + +## 0.1.x (2024-04-01) + +- 0.1.0 is the first release. It lists the releases of Flutter's index for the operating system, installs the archive and verifies its SHA-256 checksum, and knows the `stable`, `beta` and `dev` channels. +- 0.1.1 changes the version number only. diff --git a/docs/channels.md b/docs/channels.md index 088ff9b..e1c5ea4 100644 --- a/docs/channels.md +++ b/docs/channels.md @@ -1,21 +1,65 @@ # Channels -`stable` and `beta` install the release the channel currently points to, and also -accept an architecture suffix such as `stable-x64` (see `arm64.md`): +[中文](zh-CN/channels.md) | [English](channels.md) + +`stable` and `beta` install the release the channel currently points to, and also accept an architecture suffix such as `stable-x64` (see [arm64.md](arm64.md)): ```bash vfox install flutter@stable ``` -- **Resolved at install time.** The SDK is installed under the version the channel - points to, such as `flutter@3.47.4` (`flutter@3.47.4-x64` for `stable-x64`), and - vfox prints that version when the install finishes. -- **Use the version, not the channel.** `vfox use flutter@stable` fails with - `flutter@stable is not installed`; run `vfox use --global flutter@3.47.4` instead. - `vfox uninstall` takes the version as well. -- **Channels are not followed.** Once the channel moves to a new release, - `vfox install flutter@stable` installs it next to the old one; switch to it with - `vfox use`. - -Flutter retired the `dev` channel, whose last release is `2.13.0-0.1.pre` from April 2022, -so the plugin does not accept it. +On 2026-10-21 `stable` pointed to 3.47.7 and `beta` to 3.50.0-0.1.pre. + +## How a channel is resolved + +Flutter's release index (`releases_linux.json`, `releases_macos.json` and `releases_windows.json`, see [versions.md](versions.md#installing)) names the commit each channel points to under `current_release`. The plugin reads that commit and installs the release built from it. When the request has an architecture suffix, or the machine has more than one build of the commit (macOS has an x64 and an arm64 archive of every recent release), the plugin takes the build for that architecture: + +| You run | Installs | +| --- | --- | +| `vfox install flutter@stable` | the stable release, for the architecture of this machine, as `3.47.7` | +| `vfox install flutter@stable-x64` | the x64 build of it, as `3.47.7-x64` | +| `vfox install flutter@stable-arm64` | the arm64 build of it, as `3.47.7-arm64`, where there is one, see [arm64.md](arm64.md) | +| `vfox install flutter@beta` | the beta release, as `3.50.0-0.1.pre` | + +A channel request reads the release index once more than a version request does, and it does so without retrying. A mirror (see [mirror.md](mirror.md)) serves its own copy of the index, so the channel points to what that copy says. Google's, CFUG's and CERNET's copies agreed on 2026-10-21. + +## What you get + +- **Resolved at install time.** The SDK is installed under the version the channel points to, such as `flutter@3.47.7` (`flutter@3.47.7-x64` for `stable-x64`), and vfox prints that version when the install finishes: + + ``` + $ vfox install flutter@stable + Preinstalling flutter@3.47.7... + ... + Install flutter@3.47.7 success! + Please use `vfox use flutter@3.47.7` to use it. + ``` + +- **Use the version, not the channel.** The channel name is not an installed name, see below. +- **Channels are not followed.** Once the channel moves to a new release, `vfox install flutter@stable` installs it next to the old one; switch to it with `vfox use`. Nothing changes the SDK you already use. + +## Use the version, not the channel + +vfox only knows installed names. A channel is replaced by a release while the plugin installs, so afterwards there is no `stable` in `vfox list flutter`, and everything that looks an installed name up fails with it: + +- `vfox use flutter@stable` fails with `flutter@stable is not installed`; run `vfox use --global flutter@3.47.7` instead. +- `vfox uninstall flutter@stable` fails the same way; `vfox uninstall` takes the version as well. +- `vfox exec flutter@stable -- flutter --version` installs the release (or finds it installed), prints `failed to get runtime package for flutter@stable: open .../cache/flutter/v-stable: no such file or directory`, exits with 1 and does not run the command. Write `vfox exec flutter@3.47.7 -- flutter --version`. +- `stable` in a `.vfox.toml` or a `.tool-versions` selects nothing: `vfox current` shows `flutter -> N/A` and `flutter` comes from the next scope. A project file needs the version. + +The version a channel stands for is the newest `(stable)` or `(beta)` line of `vfox search flutter` in practice, see [versions.md](versions.md#searching). To keep a project on one Flutter whatever the channel does, pin the version with `vfox use --project flutter@3.47.7` and commit the `.vfox.toml` it writes. + +## `latest` is not a channel + +`vfox install flutter@latest` is vfox's own shorthand for the first line of `vfox search flutter`. That line is the newest stable release, with an architecture suffix (`3.47.7-x64` on 2026-10-21), so `latest` and `stable` install the same Flutter under different names (`3.47.7-x64` and `3.47.7`). Prefer `stable`, see [versions.md](versions.md#searching). Unreleased: release 0.2.4 lists the pre-releases among the stable releases by their numbers, so its first line is the newest beta, and its `latest` installed `3.50.0-0.1.pre-x64`. + +## `dev` + +Flutter retired the `dev` channel, whose last release is `2.13.0-0.1.pre` from April 2022, so the plugin does not accept it. `releases_.json` still names a commit for it under `current_release`, but the plugin no longer looks at that entry, and `vfox install flutter@dev` ends with: + +``` +no installable runtime provided +failed to install flutter +``` + +Releases that were published to `dev` in the past are still listed by their version, such as `2.13.0-0.1.pre`, with `(dev)` after it in `vfox search flutter`. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..dbe54d9 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,227 @@ +# Development + +[中文](zh-CN/development.md) | [English](development.md) + +## Layout + +``` +metadata.lua plugin name, version, oldest vfox, and the note that `vfox add` prints +hooks/available.lua vfox search: the list of every name +hooks/pre_install.lua vfox install: resolve a name to a package; and PostInstall +hooks/pre_use.lua vfox use: warn when the SDK has drifted +hooks/env_keys.lua PATH +lib/util.lua the mirror, the platform, version names and their order +lib/git.lua git commands, the GitHub mirror, the work directory +lib/source.lua source installs on Linux ARM64 and Windows ARM64 +lib/ohos.lua OpenHarmony releases and checkouts +lib/manifest.lua .vfox-manifest and the pubspec.lock fix +tests/hooks_test.lua offline hook tests +tests/e2e/ container tests, see e2e.md +Injection.lua type hints for what vfox injects, never loaded +.luarc.json, stylua.toml Lua language server and formatter settings (Lua 5.1, 120 columns, 4 spaces) +.devcontainer/ the Linux development container, see e2e.md +.github/workflows/ lint, license headers, hook tests, e2e, and the release workflow +``` + +vfox loads `metadata.lua`, calls a hook when a command needs it, and puts `PLUGIN` and `RUNTIME` (`osType`, `archType`, `version`) in scope. `osType` and `archType` are Go's `GOOS` and `GOARCH` of the vfox binary. The hooks run in gopher-lua, a Lua 5.1 interpreter written in Go, with vfox's `http` and `json` modules. Files in `lib` are loaded with `require`, and `lib/util.lua` defines globals. + +## What runs for a command + +| You run | Hooks | +| --- | --- | +| `vfox search flutter` | `Available` | +| `vfox install flutter@3.47.4` | `PreInstall` returns the package, and calls `Available` itself to find it. vfox downloads, verifies and unpacks, then calls `PostInstall` | +| `vfox use flutter@3.47.4` | `PreUse` checks the SDK and returns the name. `EnvKeys` provides `PATH` | +| `vfox uninstall flutter@3.47.4` | none, the name has to be exact | + +## Contracts that cost time + +- `Available` returns one table per name with `version`, `url`, `sha256`, `key`, `note` and `addition`. `key` is the git commit of the release, which is how a channel finds its release: `current_release.stable` in the index is a commit, and `PreInstall` takes the entries whose `key` is that commit. `note` is the channel, shown in parentheses. `addition` is the Dart version, shown in brackets. `source = true` is the plugin's own mark for a `-arm64` source install. +- `PreInstall` returns `version`, `url` and `sha256`. `version` becomes the name of the install. It is the name without the architecture suffix unless the request had one (`3.47.4` stays `3.47.4`, `3.47.4-x64` stays `3.47.4-x64`), and for a channel the release it stands for. A source install returns the path of a local directory as `url` and no checksum, and vfox moves the directory into place. +- `PreInstall` calls `self:Available({ officialOnly = true })` and so does not use vfox's 12-hour cache of the list. Every install reads the index. `officialOnly` leaves out the OpenHarmony releases: an official install has no use for them, and gitcode.com rate-limits the API they come from. vfox calls `Available` with its own context (`vfox search`, and the `latest` of `vfox install`), and those lists have them. Unreleased: release 0.2.4 asks gitcode.com on every install. +- `PreInstall` returns `nil` for a name it cannot resolve, which vfox reports as `no installable runtime provided`. It raises an error for a failure: the message of `error()` shows up in the `plugin [PreInstall] method error:` line. +- The index lists some releases more than once. `newestPublications` in `hooks/available.lua` keeps the newest entry (`release_date`) of each archive of a version, because a file that was uploaded again has the checksum of the newest entry only (see [troubleshooting.md](troubleshooting.md#checksum-error)). Different versions never merge, and a release in two channels has an archive in each and keeps both. `table.sort` is not stable, so the comparator has to tell any two entries apart, or equal entries come out in an order that depends on the whole input, and `PreInstall` takes the first match. It ends with the version and the channel for that. +- `PostInstall` reads `ctx.sdkInfo[PLUGIN.name]`. It runs the manifest and the `pubspec.lock` fix under `pcall`, so a failure there never fails the install. +- vfox unpacks a `.tar.xz` with Go's `archive/tar`, which stamps files with the time of the extraction and not with the time stored in the archive. The official `tar -xf` flow keeps those times, and `packages/flutter_tools/pubspec.lock` is newer than `pubspec.yaml` after it. After a vfox install the two share the time of the extraction, and whichever is written last looks newer, so `bin/internal/shared.sh` can find `pubspec.yaml` newer than `pubspec.lock` and rebuild the tool on the first run (`Building flutter tool...`). `manifest.touchPubspecLock` rewrites `pubspec.lock`, on every platform, to restore the order ([#37](https://github.com/version-fox/vfox-flutter/issues/37)). Source installs build the tool on the first run by design. +- A Windows command line reaches `cmd.exe` as vfox 1.0.13 and later write it, unchanged ([vfox#712](https://github.com/version-fox/vfox/pull/712)), and older versions quote it again. `quote` in `lib/git.lua` and `lib/manifest.lua` puts double quotes around a path that contains whitespace, and leaves other paths bare, so that a path with a space works on 1.0.13 and a path without one still works on older versions. +- `os.execute` reports success as `0` or as `true`, depending on the Lua it runs in, and `exec` in `lib/git.lua` accepts both. `io.popen` is how the plugin reads the output of `git`; a command that prints nothing or fails reads as `nil`, which is why a missing `git` goes unnoticed in `lib/manifest.lua`. +- `lib/git.lua` takes the work directory from `VFOX_HOME`, then `HOME`, then `USERPROFILE`, and appends `.vfox/tmp/`. With `VFOX_HOME` set that is `$VFOX_HOME/.vfox/tmp/`, one `.vfox` below the vfox home. The directory is not removed when the install fails. +- `git` runs with your configuration, except for two settings that `git.init` writes into the checkout: `core.longpaths=true`, and `core.autocrlf=false`, because a global `true` writes Flutter's shell scripts with CRLF (see [arm64.md](arm64.md#limits)). On Windows `false` writes the same CRLF files `true` does, since `core.eol` is native there. That is how git works, and it was checked on Linux with `core.eol=crlf` and Flutter's `.gitattributes`, which starts with `* text=auto`: the same files came out CRLF for both settings, and `bin/flutter` stayed LF. The Windows ARM64 container tests install from a checkout like this one. Fetches get `http.lowSpeedLimit=1000` and `http.lowSpeedTime=60`, which `GIT_HTTP_LOW_SPEED_LIMIT` and `GIT_HTTP_LOW_SPEED_TIME` override. +- `lib/ohos.lua` waits for gitcode.com in two ways. Installing a build needs the releases, and makes five attempts: 10 seconds apart, and 30, 60, 120 and 180 seconds after HTTP 429. Listing them is a courtesy to `vfox search`, so `ohos.list()` makes two attempts 2 seconds apart, stops at HTTP 429, and returns `nil` and the reason. `Available` then writes one warning line to stderr and lists the official releases. +- A tag is an OpenHarmony release when its name contains `-ohos-` or `+ohos-` (`ohos.isOhosVersion`). +- `source.list` offers a `-arm64` source install of a release only from `OLDEST_NATIVE` on, the first release whose Flutter tool runs with a native Dart SDK on that machine, see [Refreshing the Dart SDK table](#refreshing-the-dart-sdk-table). +- `EnvKeys` sets `PATH` only. It sets no `FLUTTER_ROOT`. +- Lua has no sleep, so `sleep` in `lib/util.lua` is `os.execute("sleep N")`, or `timeout /t N` on Windows. The retries in `hooks/available.lua` and `lib/ohos.lua` wait with it. + +## Names and order + +`splitVersionAndArch` in `lib/util.lua` splits `3.47.4-x64` into `3.47.4` and `x64`, and only `x64` and `arm64` count as a suffix: `3.50.0-0.1.pre` has none. `compare_versions` ignores the suffix and compares the numbers in the version one by one. A version with fewer numbers sorts above one that continues the same numbers, so `3.47.0` is above `3.47.0-0.4.pre`. + +`Available` sorts by these rules, one after the other: + +1. OpenHarmony names after all others (`ohos.isOhosVersion`). +2. The architecture of the machine, and names without a suffix, before the other architecture. +3. Entries of the stable channel (`note == "stable"`) before the others, so that `@latest`, the first line, is the newest stable release and not a beta that has a higher number. Unreleased: release 0.2.4 has no such rule. +4. `compare_versions`, newest first. +5. The architecture of the machine before the other one, and then the plain string order. +6. The channel (`beta` before `dev`), for the same name in two channels. + +Rules 2 and 3 keep `@latest` on the machine's own architecture and on a stable release. The unit tests check both. + +## Resolving a name + +`PreInstall` splits the request into a name and an architecture (`requestedArch`), and then: + +1. An OpenHarmony name goes to `ohos.checkout`, which refuses a suffix. +2. `beta` and `stable` read `current_release` from the index, and its commit becomes `channelKey`. A channel that the index does not name ends the lookup with `nil`. `dev` is not a channel any more, so it is looked up as a version name and found nowhere. +3. It walks `Available`, and takes the first entry whose commit is `channelKey`, or whose name equals the request, and whose architecture is the one requested (or the machine's). An entry without architecture metadata is kept as `legacy` for a request without a suffix, and returned when nothing else matched. +4. A source entry goes to `source.checkout`. The others return `url` and `sha256`. + +## Tests + +The hook tests run offline under `gopher-lua`, the Lua interpreter vfox embeds: + +```bash +go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/hooks_test.lua +``` + +Run it from the repository root. The test stubs `http`, `json` and `os.getenv`, and keeps a small index in a table: two architectures that share a commit, a beta, a dev release and a release without architecture metadata. It checks that: + +- every variant is listed, the machine's architecture first; +- an explicit suffix selects the matching archive, and a plain name keeps the machine's; +- `@latest` stays on the machine's architecture even when the other one is newer; +- channels pick the machine's architecture although both share a commit, and `dev` resolves to nothing; +- pre-release names keep their text, and the suffix does not change the order; +- the mirror serves the index and every archive; +- a release without architecture metadata stays installable and gets no invented variant; +- a missing architecture never falls back to an incompatible archive; +- a fetch gives up on a stalled transfer and retries; +- installing an official release never asks gitcode.com, while a listing asks it once; +- a rate-limited gitcode.com costs a listing no wait and one warning, and a failed request is tried once more; +- installing an OpenHarmony build waits out a rate-limited gitcode.com (30, 60, 120 and 180 seconds); +- OpenHarmony tags with `+ohos-` are listed, sorted and installable, and refuse an architecture suffix; +- stable releases come before pre-releases, so the first line is the newest stable release; +- an archive that was published again keeps its newest entry, and different versions or channels never merge; +- ARM64 source installs start at the first release that runs with a native Dart SDK; +- a checkout does not take `core.autocrlf` from your git configuration (this one runs real `git`, and skips itself without it); +- an unsupported CPU is named in the message. + +The manifest and `PreUse` are not in this file, and source installs and OpenHarmony builds only up to their `git` commands. The container tests cover them, see [e2e.md](e2e.md). Lint, license headers and the container tests are in [e2e.md](e2e.md#local-checks). + +To call a single hook, vfox 1.0.13 and later has `vfox plugin run`. It does not register the plugin, and `--os` and `--arch` answer for another platform: + +```bash +vfox plugin run . Available --os windows --arch arm64 --json +vfox plugin run . PreInstall --input '{"version":"stable"}' --os darwin --arch arm64 --json +``` + +Both ask the real index and gitcode.com. A source install (`--os linux --arch arm64`) runs the real `git` in its work directory, whatever the operating system you are on. `vfox --debug ` shows what vfox does with a hook, which is how to find an error it swallows. + +### Trying a change without downloading gigabytes + +An archive is 1.6 to 2.3 GB, so a change to the resolution is cheaper to try against a fake mirror: a copy of the real index whose archives are one tiny SDK. The SDK holds a `flutter/bin/flutter` script and a real git repository, so the manifest and the drift check run for real. Run it on Linux, from the root of the repository. It sets `HOME` and `VFOX_HOME` to a scratch directory, which vfox needs both of, so your real vfox is left alone. + +```bash +work=$(mktemp -d) +python3 - "$work" <<'EOF' +import hashlib, io, json, os, subprocess, sys, tarfile, urllib.request + +work = sys.argv[1] +base = "https://storage.googleapis.com/flutter_infra_release/releases/" +index = json.load(urllib.request.urlopen(base + "releases_linux.json")) + +sdk = os.path.join(work, "sdk", "flutter") +os.makedirs(os.path.join(sdk, "bin")) +with open(os.path.join(sdk, "bin", "flutter"), "w") as f: + f.write('#!/bin/sh\necho "fake flutter in $(cd "$(dirname "$0")/.." && pwd)"\n') +os.chmod(os.path.join(sdk, "bin", "flutter"), 0o755) +git = ["git", "-C", sdk, "-c", "user.name=x", "-c", "user.email=x@x", "-c", "core.autocrlf=false"] +subprocess.run(["git", "init", "-q", sdk], check=True) +subprocess.run(git + ["add", "."], check=True) +subprocess.run(git + ["commit", "-q", "-m", "fake"], check=True) + +path = os.path.join(work, "flutter.tar.xz") +with tarfile.open(path, "w:xz") as tar: + tar.add(sdk, arcname="flutter") +sha = hashlib.sha256(open(path, "rb").read()).hexdigest() + +root = os.path.join(work, "mirror", "flutter_infra_release", "releases") +os.makedirs(os.path.join(root, "fake")) +os.rename(path, os.path.join(root, "fake", "flutter.tar.xz")) +for release in index["releases"]: + release["archive"], release["sha256"] = "fake/flutter.tar.xz", sha +with open(os.path.join(root, "releases_linux.json"), "w") as f: + json.dump(index, f) +EOF +``` + +The scratch directory now holds the mirror. Build the plugin the way [install-from-source.md](install-from-source.md) does: + +```bash +zip -qr "$work/flutter.zip" metadata.lua hooks lib +``` + +Serve the mirror, in the background of a shell you keep, and run vfox against it: + +```bash +python3 -m http.server 8000 --bind 127.0.0.1 --directory "$work/mirror" >/dev/null 2>&1 & +server=$! +export HOME="$work/home" VFOX_HOME="$work/vfox" FLUTTER_STORAGE_BASE_URL=http://127.0.0.1:8000 +mkdir -p "$HOME" "$VFOX_HOME" +vfox add flutter --source "$work/flutter.zip" +vfox search flutter +vfox install flutter@stable +eval "$(vfox activate bash)" +vfox use --global flutter@3.47.7 +flutter +``` + +Stop the server with `kill "$server"`, not with `pkill -f` and its command line, which matches your own shell as well. To try the next change, edit the plugin in the repository, repeat the `zip` line above, and run `vfox remove -y flutter` before `vfox add` again. The plugin asks gitcode.com for the OpenHarmony releases when `vfox search` lists versions, and an unreachable gitcode.com only costs two attempts and a warning. The real index has `3.47.7` as `stable` on 2026-10-21; use whatever `vfox install flutter@stable` printed. + +### Refreshing the Dart SDK table + +`OLDEST_NATIVE` in `lib/source.lua` holds, for each ARM64 platform, the oldest release whose Flutter tool runs with a native Dart SDK. A source install of an older release clones fine, but its tool takes the x64 Dart SDK, so the plugin does not give it an `-arm64` name. Two things make a release native: its bootstrap script asks for the ARM64 Dart SDK, and Flutter's storage has it. Old releases do not change, so the table needs a look when a platform joins, and not for every release. To check a release: + +```bash +release=2.0.6 +curl -fsSL "https://raw.githubusercontent.com/flutter/flutter/$release/bin/internal/update_dart_sdk.sh" | grep 'DART_ZIP_NAME="dart-sdk-linux' +curl -fsSL "https://raw.githubusercontent.com/flutter/flutter/$release/bin/internal/update_dart_sdk.ps1" | grep -c -i arm64 +engine=$(curl -fsSL "https://raw.githubusercontent.com/flutter/flutter/$release/bin/internal/engine.version") +for platform in linux windows; do + printf '%s ' "$platform" + curl -s -o /dev/null -I -w '%{http_code}\n' "https://storage.googleapis.com/flutter_infra_release/flutter/$engine/dart-sdk-$platform-arm64.zip" +done +``` + +The first command prints the zip that the Linux script asks for: `dart-sdk-linux-x64.zip` up to `2.0.6`, and `dart-sdk-linux-${ARCH}.zip` from `2.2.0` on, where `uname -m` decides `ARCH`. The second counts the lines about ARM64 in the Windows script: `0` up to `3.19.6`, and `6` from `3.20.0-1.1.pre` on. The loop asks the storage for the ARM64 Dart SDK of the engine, `200` for there and `404` for not there. On 2026-10-21 `2.0.6` printed `linux 200` and `windows 404`, `3.43.0-0.3.pre` the same, and `3.44.0-0.1.pre` printed `200` for both. + +The Linux row of the table is where the script starts to ask (storage has the SDK from `2.0.0`), and the Windows row is where storage starts to have it (the script asks from `3.20.0-1.1.pre` and falls back to the x64 SDK without it). The table was made by running these checks for the 300 releases of `releases_linux.json` that have `"dart_sdk_arch": "x64"`, which are the ones that get a `-arm64` source install, and it holds for every one of them: the scripts of 283 releases choose the Linux zip by CPU and the 17 oldest do not, and the Windows scripts of 148 mention ARM64 and the 152 oldest do not. On Windows seven older betas (listed in `lib/source.lua`) have the SDK in storage and are left out. The Linux scripts of `1.20.0`, `1.22.4`, `2.0.0` and `2.0.6` were also run as an ARM64 machine, with a `uname` that says `aarch64` and a `curl` that prints the URL it is asked for, and asked for `dart-sdk-linux-x64.zip`, while those of `2.2.0`, `2.5.0`, `3.0.0` and `3.24.0` asked for `dart-sdk-linux-arm64.zip`. + +## Adding to the plugin + +- **A new way to name or choose.** Change `hooks/available.lua` (the order) or `hooks/pre_install.lua` (the choice), add a case to `tests/hooks_test.lua`, and update [versions.md](versions.md), [channels.md](channels.md) and [arm64.md](arm64.md) where they describe it. +- **A new platform.** `getOsTypeAndArch` in `lib/util.lua` lists the CPUs. A platform that installs from git adds itself to `SOURCE_INSTALL_PLATFORMS` and `OLDEST_NATIVE` in `lib/source.lua`. Update [platforms.md](platforms.md) and the tables in [arm64.md](arm64.md), and add it to the container tests if a runner exists ([e2e.md](e2e.md)). +- **A new source of builds** like OpenHarmony: a module in `lib/` that lists names and checks them out, a branch in `Available`, `PreInstall` and `PostInstall`, a flavor in `tests/e2e/common/common.go` (`DefaultFlavours`, `DefaultOhosVersion`), and a page in `docs/`. +- **A message.** The container tests assert parts of the drift warning (`tests/e2e/linux/verify.go` and `tests/e2e/windows/verify.go`). Change the Lua and both files together, and the text in [flutter-upgrade.md](flutter-upgrade.md). + +## Known gaps + +- Source installs and OpenHarmony builds are not verified against a checksum. +- Only releases from Flutter 1.17 on are listed. +- `OLDEST_NATIVE` is data taken on 2026-10-21 and does not follow Flutter's storage or scripts. It is about the Dart SDK only: the plugin does not check the rest of an old release on ARM64. +- A failed source install leaves its work directory behind. +- `vfox search` marks only the names it prints as installed, see [versions.md](versions.md#searching). +- Releases without architecture metadata (stable `1.22.6` and older, and the pre-releases up to `2.10.0-0.3.pre`) install an x64 archive on every machine, ARM64 Linux and Windows included, where it does not run natively. +- The OpenHarmony fork changes its tag names (`-ohos-`, since 2026-10-15 `+ohos-`). A name with another marker is not listed. + +## Releasing + +A maintainer runs **Actions → Plugin → Run workflow** on the default branch with the version, without `v`. The [README](../README.md#releasing-this-plugin) describes what the shared workflow does. Afterwards `plugins/flutter.json` in [vfox-plugins](https://github.com/version-fox/vfox-plugins) shows the version that `vfox add flutter` serves, and [changes.md](changes.md) should have an entry for it. + +## Writing the docs + +English pages are in `docs/`, the Chinese ones in `docs/zh-CN/` under the same names (the Chinese README is `README_CN.md`), and each page starts with a link to the other language. Change both when you change one: the Chinese pages are translations, and they keep the commands, the paths and the messages of the plugin and of vfox as they are. The README is the way in: a few sentences for each subject and a link. The details are in `docs/`, one page for a subject. Pages that quote data from outside (Flutter's index, gitcode.com, vfox) give the day the data was taken, or the vfox version. Nothing in CI checks the docs, so run the commands you add. + +Commands for a platform go under a heading with the platform (`## Ubuntu 26.04.1`, `## Windows 11`) and begin with `Execute in Bash,` or `Execute in PowerShell 7,` (`在 Bash 中执行:` in Chinese), as the pages already do. The examples install `3.47.4`, the default of the container tests (`DefaultOfficialVersion` in `tests/e2e/common/common.go`); move them together when it moves. + +Behavior that is only on `main` and not in the latest release is marked "Unreleased" ("未发布" in Chinese), with an entry under "Unreleased" in [changes.md](changes.md). After a release, move those entries under the new version and take the marks out, in both languages: `grep -rn -i -e unreleased -e 未发布 docs README*`. diff --git a/docs/e2e.md b/docs/e2e.md index e88a86e..3cf6c20 100644 --- a/docs/e2e.md +++ b/docs/e2e.md @@ -1,5 +1,7 @@ # E2E +[中文](zh-CN/e2e.md) | [English](e2e.md) + The offline Lua hook tests (`tests/hooks_test.lua`) run in the **Test Plugin** workflow. The end-to-end suite runs in containers against real vfox on Linux and Windows; each `vfox` x `flavor` x `mirror` combination gets its own throwaway @@ -8,6 +10,9 @@ from `actions/setup-go`). Locally, the repository dev container reproduces the Linux run; Windows hosts use a Docker Compose runner image instead, since dev containers cannot host Windows container images (see below). +[platforms.md](platforms.md#what-the-tests-cover) summarizes what the suites cover, +and [development.md](development.md) explains how the plugin works. + ## Dev container `.devcontainer/` opens a Linux development environment that works on all four diff --git a/docs/flutter-upgrade.md b/docs/flutter-upgrade.md index c2038d7..fc5fdca 100644 --- a/docs/flutter-upgrade.md +++ b/docs/flutter-upgrade.md @@ -1,5 +1,7 @@ # About `flutter upgrade` +[中文](zh-CN/flutter-upgrade.md) | [English](flutter-upgrade.md) + The Flutter release archives this plugin installs contain a full `.git` checkout, so `flutter upgrade` (and its siblings `flutter downgrade`, `flutter channel`, and any `git checkout` / `git pull` run inside the SDK @@ -16,6 +18,9 @@ vfox install flutter@3.47.4 vfox use --global flutter@3.47.4 ``` +Source installs (Linux ARM64, Windows ARM64, OpenHarmony builds) are git checkouts +too, and the same goes for them. + ## Recovering a drifted SDK If you already upgraded in place, this plugin prints a warning on every @@ -27,13 +32,65 @@ vfox install flutter@3.47.0 vfox use flutter@3.47.0 ``` +## The warning + +This is what `vfox use flutter@3.47.4` prints once the SDK's HEAD is not the commit vfox +installed: + +``` +Warning: flutter SDK at ~/.vfox/cache/flutter/v-3.47.4 has drifted from the version vfox installed. + vfox installed: flutter@3.47.4 (git HEAD 9584c6713b324636289d067944a46fd6b49df14b) + current git HEAD: abaf9c523780a608bd46686fd5e53740a07077f8 + reason: the SDK's git HEAD no longer matches the commit vfox installed + +This typically happens when `flutter upgrade` or a similar in-place command +is run inside a vfox-managed SDK. vfox manages SDK versions by directory and +does not track in-place mutations, so the version you get may differ from +the one you asked for. + +To restore flutter@3.47.4: + vfox uninstall flutter@3.47.4 + vfox install flutter@3.47.4 + vfox use flutter@3.47.4 + +To upgrade to a newer version, use vfox directly: + vfox install flutter@ + vfox use flutter@ +``` + +There are two reasons, and the line after `reason:` says which: + +| Reason | Meaning | +| --- | --- | +| `the SDK's git HEAD no longer matches the commit vfox installed` | The commit changed. This is what `flutter upgrade`, `flutter downgrade`, `flutter channel` and `git checkout` do | +| `vfox can no longer read the SDK's git HEAD (its .git may be broken)` | `git` fails in the SDK: the `.git` directory is damaged, or `git` is not installed any more | + +The path in the first line is the directory that `vfox info flutter@3.47.4` prints. The +SDK is the `flutter-3.47.4` directory inside it. + ## How the check works On install the plugin writes a `.vfox-manifest` file next to the SDK's `.git`, recording the expected git HEAD, and re-checks it on each `vfox use`. +The manifest is a small JSON file in the root of the SDK, `flutter-3.47.4/.vfox-manifest`: + +```json +{"expected_head":"9584c6713b324636289d067944a46fd6b49df14b","installed_at":1792523751,"plugin":"vfox-flutter","plugin_version":"0.2.4","version":"3.47.4"} +``` + +`expected_head` is what `git rev-parse HEAD` printed right after vfox unpacked the SDK, and +`installed_at` is the time of the install in seconds since 1970. The check runs +`git rev-parse HEAD` again on `vfox use` and compares the two. Archive installs, source +installs and OpenHarmony builds all get a manifest. `git status` in the SDK lists the file +as untracked. + The warning is only a warning: `vfox use` still selects the version you asked -for. SDKs without a `.git` directory are left alone. +for, and exits with 0. SDKs without a `.git` directory are left alone. + +Nothing is checked when there is nothing to compare with: an SDK without a manifest (one +installed by a plugin older than 0.2.3), a manifest without `expected_head` (the install +ran where `git` was missing), and a version vfox does not know are all silent. ## Disabling the check @@ -42,12 +99,17 @@ written by a real install, so it comes back only after you uninstall and reinstall that version; `vfox install` alone skips an already-installed version and leaves it absent. +`vfox info --format '{{.Path}}' flutter@3.47.0` prints the directory above the SDK, so the +manifest is one level further down, in `flutter-3.47.0`. The directory is named after the +installed name, which carries an architecture suffix if you installed with one +(`flutter-3.47.0-x64`). + ### Ubuntu 26.04.1 Execute in Bash, ```bash -rm "$(vfox info --format '{{.Path}}' flutter@3.47.0)/.vfox-manifest" +rm "$(vfox info --format '{{.Path}}' flutter@3.47.0)/flutter-3.47.0/.vfox-manifest" ``` ### Windows 11 @@ -55,5 +117,5 @@ rm "$(vfox info --format '{{.Path}}' flutter@3.47.0)/.vfox-manifest" Assume that PowerShell 7 is already installed. Execute in PowerShell 7, ```powershell -Remove-Item "$(vfox info --format '{{.Path}}' flutter@3.47.0)/.vfox-manifest" +Remove-Item "$(vfox info --format '{{.Path}}' flutter@3.47.0)/flutter-3.47.0/.vfox-manifest" ``` diff --git a/docs/install-from-source.md b/docs/install-from-source.md index 0ba3b88..6a89af9 100644 --- a/docs/install-from-source.md +++ b/docs/install-from-source.md @@ -1,11 +1,17 @@ # Install from source +[中文](zh-CN/install-from-source.md) | [English](install-from-source.md) + +`vfox add flutter` installs the latest release. To try `main` before its next release, or a +change you are working on, build the plugin from source. A plugin is a `metadata.lua` plus +the `hooks` and `lib` directories, and vfox adds one from a zip of them. + ## Ubuntu 26.04.1 Execute in Bash, ```bash -sudo apt install --assume-yes git +sudo apt install --assume-yes git zip git clone --depth 1 https://github.com/version-fox/vfox-flutter.git cd ./vfox-flutter/ zip -qr ./flutter.zip metadata.lua hooks lib @@ -22,6 +28,8 @@ flutter --version --no-version-check dart --version ``` +macOS is the same, with the `git` and `zip` you already have. + ## Windows 11 Assume that PowerShell 7 is already installed. Execute in PowerShell 7, @@ -43,3 +51,44 @@ vfox use --global flutter@3.47.4 flutter --version --no-version-check dart --version ``` + +## When flutter is already added + +`vfox add` refuses with `plugin named flutter already exists`. You can replace the plugin or +add the source build next to it. + +To replace it, `vfox remove flutter` comes first, and it removes the Flutter SDKs installed +through the plugin as well: vfox says `Removing this plugin will remove the installed sdk +along with the plugin.` before it asks. Reinstall them afterwards. An archive alone is 1.6 to +2.3 GB, so this is the expensive way. + +To keep both, give the new one another name: + +```bash +vfox add flutter --source ./flutter.zip --alias flutter-dev +vfox install flutter-dev@3.47.4 +vfox remove -y flutter-dev # removes only flutter-dev and what it installed +``` + +The alias has its own list of installed Flutters, and its own downloads under +`cache/flutter-dev` in the vfox home. The link that `vfox use` makes is `~/.vfox/sdks/flutter` +for both, and so is the `bin` directory that lands in your `PATH`. Select a version from only +one of them per scope: for the global scope, `vfox unuse --global flutter` before you +`vfox use --global flutter-dev@...`, or the other way round. + +## Updating + +Pull, build the zip again and add it again. A plugin added under its own name has to be +removed first, and that takes the installed Flutters with it, so the alias is the easier route +when you update often: + +```bash +vfox remove -y flutter-dev +vfox add flutter --source ./flutter.zip --alias flutter-dev +``` + +`vfox update flutter` compares the version in `metadata.lua` with the registry's and prints +`flutter is already the latest version` while they are equal, so it does not pick up a change +that has the same version number as the release. Add the source build again for that. + +To work on the plugin, see [development.md](development.md). diff --git a/docs/mirror.md b/docs/mirror.md index 4cdb080..0e88a01 100644 --- a/docs/mirror.md +++ b/docs/mirror.md @@ -1,5 +1,7 @@ # Mirror +[中文](zh-CN/mirror.md) | [English](mirror.md) + By default, Flutter SDK is downloaded from `https://storage.googleapis.com`. If you have difficulty accessing it, you can set the `FLUTTER_STORAGE_BASE_URL` environment variable to use a mirror. @@ -13,6 +15,53 @@ Common mirror values: For an up-to-date list of available mirrors, refer to the MirrorZ Help site: https://help.mirrors.cernet.edu.cn/flutter/ . +## What the variable does + +`FLUTTER_STORAGE_BASE_URL` is Flutter's own variable, and it matters in three places: + +- **The plugin reads the release index from it**: `/flutter_infra_release/releases/releases_linux.json` + (`releases_macos.json` and `releases_windows.json` on those systems). The index decides + what `vfox search flutter` lists and what a channel stands for, see + [channels.md](channels.md). +- **The archive comes from it.** The plugin builds the address that vfox downloads from the + index's `archive` field, `/flutter_infra_release/releases/`, and does not + use the `base_url` that the index itself carries. The checksum is the one from the same + index. +- **The `flutter` tool reads it too.** Downloads that the tool makes itself, such as the + Dart SDK of a source install (see [arm64.md](arm64.md)) and engine artifacts, go through + it. That is why it has to stay set in your shell, not only while you run `vfox install`. + +A trailing slash is removed, so `https://storage.flutter-io.cn/` works, and an empty value +counts as unset. A mirror has to keep Google's layout under `flutter_infra_release/` and +answer for the index and the archives. The mirror's index is the list of what you can +install: a release it has not copied yet is missing, and `stable` points where its copy +says. On 2026-10-21 Google, CFUG and CERNET served the same index. + +To check a mirror before you use it, ask it for the index of your system: + +```bash +curl --fail --silent --show-error --location --output /dev/null \ + https://storage.flutter-io.cn/flutter_infra_release/releases/releases_linux.json +``` + +The test suite runs the same request against `https://storage.flutter-io.cn` before it +installs through it. + +## Which installs use which mirror + +| Install | Release index | Download | +| --- | --- | --- | +| Archive: Linux x64, macOS, Windows x64, and `-x64` anywhere | `FLUTTER_STORAGE_BASE_URL` | `FLUTTER_STORAGE_BASE_URL` | +| Source: Linux ARM64 and Windows ARM64 | `FLUTTER_STORAGE_BASE_URL` | GitHub, with `VFOX_FLUTTER_GITHUB_MIRROR` | +| OpenHarmony | gitcode.com, no mirror | gitcode.com, no mirror | + +Installing an OpenHarmony build does not read the release index at all, but +`vfox search flutter` does, because it lists the official releases next to them. +`PUB_HOSTED_URL`, the address that `flutter pub get` fetches packages from, is not read by +the plugin. If you use CFUG's mirror, Flutter's +[guide for China](https://docs.flutter.dev/community/china) sets it to +`https://pub.flutter-io.cn` next to `FLUTTER_STORAGE_BASE_URL`. + ## Ubuntu 26.04.1 For Bash, @@ -33,6 +82,17 @@ EOF source /etc/profile.d/myenvvars.sh ``` +A file in `/etc/profile.d` is read by login shells, so other terminals pick it up after you +log in again. + +## macOS + +For zsh, add the line to `~/.zshrc` and open a new terminal: + +```bash +echo 'export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn' >> ~/.zshrc +``` + ## Windows 11 For PowerShell 7, @@ -49,6 +109,9 @@ $env:FLUTTER_STORAGE_BASE_URL = "https://storage.flutter-io.cn" [Environment]::SetEnvironmentVariable('FLUTTER_STORAGE_BASE_URL', 'https://storage.flutter-io.cn', 'Machine') ``` +The `Machine` scope needs a PowerShell started as administrator, and applies to programs +you start afterwards. Use `'User'` instead to set it for your account only. + ## GitHub mirror for source installs On Linux ARM64 and Windows ARM64, @@ -97,3 +160,21 @@ $env:VFOX_FLUTTER_GITHUB_MIRROR = "https://gh-proxy.org/" The mirror only rewrites URLs that start with `https://github.com/`, so the `FLUTTER_STORAGE_BASE_URL` variable above is unaffected. + +A value without a trailing slash gets one, and the install prints the address it +clones from: + +``` +Using GitHub mirror https://gh-proxy.org/https://github.com/flutter/flutter.git +``` + +Only the clone of `flutter/flutter` goes through it. The mirror has to be a service that +takes the full GitHub address after its prefix, as gh-proxy does. A mirror that does not +answer makes the install fail with `failed to fetch tag 3.47.4 from
`, after three +attempts, see [troubleshooting.md](troubleshooting.md#source-installs-linux-arm64-and-windows-arm64). + +## Proxies + +A proxy is not a mirror. vfox's own proxy setting covers the plugin's requests and the +archive download, and `git` reads its own (`HTTPS_PROXY`, `http.proxy`), see +[troubleshooting.md](troubleshooting.md#network). diff --git a/docs/ohos.md b/docs/ohos.md index d765a52..7b64d9f 100644 --- a/docs/ohos.md +++ b/docs/ohos.md @@ -1,5 +1,11 @@ # OpenHarmony builds +[中文](zh-CN/ohos.md) | [English](ohos.md) + +OpenHarmony builds are a third-party fork of Flutter that targets OpenHarmony. The +plugin lists the releases of [CPF-Flutter/flutter_flutter](https://gitcode.com/CPF-Flutter/flutter_flutter/releases) +on gitcode.com next to the official ones and installs them as source checkouts. + ## Prerequisites `git` is required: @@ -18,19 +24,76 @@ vfox install flutter@3.41.10-ohos-1.0.0 vfox use --global flutter@3.41.10-ohos-1.0.0 ``` -Known versions are `3.41.10-ohos-1.0.0`, `3.35.8-ohos-1.0.3` and -`3.27.5-ohos-1.0.7`. List the current ones with, +The newest releases of the 3.41, 3.35 and 3.27 lines are `3.41.10-ohos-1.0.0`, +`3.35.8-ohos-1.0.3` and `3.27.5-ohos-1.0.7`, and [Names and releases](#names-and-releases) +lists all of them. List the current ones with, ```bash vfox search flutter ``` +## Names and releases + +A name is a version number, `-ohos-` or `+ohos-`, and the fork's own release number: `3.41.10-ohos-1.0.0` +is release 1.0.0 of the fork's 3.41.10 line. In a `-ohos-` name the first number is the fork's own label, and +it does not always name a Flutter release: Flutter has no 3.41.10, 3.35.8, 3.27.5 or 3.22.4, and the branch +behind `3.41.10-ohos-1.0.0` is called `oh-3.41.9-release`. The fork's newer tags, from 2026-10-15, use the number +of the official release instead, with `+ohos-` after it (`3.41.9+ohos-1.0.2`). These were the 25 +releases the plugin listed on 2026-10-21, newest first within each line: + +| Starts with | Releases (after `-ohos-` or `+ohos-`) | +| --- | --- | +| 3.41.10-ohos- | `1.0.0`, `0.0.1-canary1` | +| 3.41.9+ohos- | `1.0.2` | +| 3.35.8-ohos- | `1.0.3`, `1.0.1`, `0.0.3` | +| 3.35.7+ohos- | `1.0.5` | +| 3.27.5-ohos- | `1.0.7`, `1.0.6`, `1.0.5`, `1.0.3`, `1.0.0` | +| 3.27.4+ohos- | `1.0.9` | +| 3.22.4-ohos- | `1.1.4`, `1.1.3` | +| 3.22.3+ohos- | `1.1.6` | +| 3.22.1-ohos- | `1.1.1`, `1.0.9`, `1.0.1`, `0.1.2`, `0.1.1` | +| 3.7.12-ohos- | `1.1.7`, `1.1.1`, `1.0.7`, `1.0.6` | + +`vfox search flutter` shows them last, after every official release, each as +`3.41.10-ohos-1.0.0 (OpenHarmony)` with no Dart version. That order is why +`vfox install flutter@latest` never selects one. + +The plugin recognizes a release by `-ohos-` or `+ohos-` in the name of its tag. Unreleased: release 0.2.4 +knows `-ohos-` only. It lists the 21 releases above that have it, and +`vfox install flutter@3.41.9+ohos-1.0.2` ends with `no installable runtime provided`. + +## How an install works + +1. The plugin asks `https://gitcode.com/api/v5/repos/CPF-Flutter/flutter_flutter/releases` + for the releases, up to five times, and finds the release by the name of its tag. Its + `target_commitish` is the commit to install. +2. It runs `git init` in `.vfox/tmp/` under `$VFOX_HOME`, or under your home + directory when `VFOX_HOME` is not set (`~/.vfox/tmp/3.41.10-ohos-1.0.0`), then + `git fetch --depth 1 https://gitcode.com/CPF-Flutter/flutter_flutter.git `, up to + three times, and `git checkout FETCH_HEAD`. +3. It checks that `bin/internal/engine.version` exists, and hands the directory to vfox, + which moves it to the install location. + +The result is a shallow clone of one commit, 224 MB for 3.41.10-ohos-1.0.0, and the install +took 19 seconds on a fast connection. An install waits for gitcode.com: when its API answers +HTTP 429 (rate limited) the plugin waits 30, 60, 120 and 180 seconds between its five attempts, +and ten seconds between attempts after any other error. + +`vfox search flutter` asks the same API for the list of releases, but it does not wait for it: +two attempts, two seconds apart, and HTTP 429 ends them. When the API does not answer, the list +has no OpenHarmony names and says so on stderr. Installing an official release does not ask +gitcode.com at all. Unreleased: release 0.2.4 asks it with the same five attempts for every +command that lists versions, and for every install of an official release. + ## Limits - **Install by exact version.** OpenHarmony versions sort after the official releases, - so `@latest` never selects one. The `stable` and `beta` channels (see `channels.md`) + so `@latest` never selects one. The `stable` and `beta` channels (see [channels.md](channels.md)) resolve through Flutter's official release index, which lists no OpenHarmony builds. -- **No architecture variants.** `flutter@3.41.10-ohos-1.0.0-x64` is not supported. +- **No architecture variants.** `flutter@3.41.10-ohos-1.0.0-x64` is not supported, it fails + with `flutter 3.41.10-ohos-1.0.0 has no architecture variants`. +- **Only the names above.** `flutter@3.47.7-ohos-1.0.0` fails with + `flutter 3.47.7-ohos-1.0.0 is not an OpenHarmony release`. - **`linux-arm64` and `windows-arm64`.** Upstream publishes only x64 Dart SDKs, so the first `flutter` run fails there (`It appears that the downloaded file is corrupt`). The ARM64 e2e runs the official flavor only. @@ -38,12 +101,18 @@ vfox search flutter from Huawei's OBS, so it needs a network connection. - **No checksum is verified.** Git history is the integrity check, and this is a third-party fork, so review its source before trusting it. -- **Mirrors don't apply.** `FLUTTER_STORAGE_BASE_URL` and `VFOX_FLUTTER_GITHUB_MIRROR` (see `mirror.md`) only affect +- **Mirrors don't apply.** `FLUTTER_STORAGE_BASE_URL` and `VFOX_FLUTTER_GITHUB_MIRROR` (see [mirror.md](mirror.md)) only affect official SDK downloads; OpenHarmony builds are cloned from gitcode, so both variables are ignored. - **Stalled clones are retried.** A fetch from gitcode that stalls (under 1 KB/s for a minute) is abandoned and retried, up to three attempts; `GIT_HTTP_LOW_SPEED_LIMIT` / `GIT_HTTP_LOW_SPEED_TIME` change the threshold. +- **gitcode.com rate-limits its API.** An install of an OpenHarmony build waits for it, see + above. See [troubleshooting.md](troubleshooting.md#gitcodecom-is-rate-limited). +- **Line endings.** The checkout sets `core.autocrlf=false`. Unreleased: release 0.2.4 takes your + git configuration, and with `core.autocrlf=true` a checkout on Linux has CRLF line endings in + `bin/internal/shared.sh`, so that `flutter` stops with `$'\r': command not found`, see + [arm64.md](arm64.md#limits). ### Building OpenHarmony applications diff --git a/docs/platforms.md b/docs/platforms.md new file mode 100644 index 0000000..75251b4 --- /dev/null +++ b/docs/platforms.md @@ -0,0 +1,50 @@ +# Platform support + +[中文](zh-CN/platforms.md) | [English](platforms.md) + +The plugin installs Flutter on Linux, macOS and Windows, on x64 and ARM64. It takes the operating system and the CPU architecture from the vfox binary that runs it (`osType` and `archType` are Go's `GOOS` and `GOARCH`): `darwin` becomes `macos`, `amd64` becomes `x64` and `arm64` stays. Any other CPU stops with a message that names it, see [troubleshooting.md](troubleshooting.md#flutter-does-not-support-the-386-architecture). + +| Machine | Installs from | Release index | +| --- | --- | --- | +| Linux x64 | `.tar.xz` archives Google publishes | `releases_linux.json` | +| Linux ARM64 | the `flutter/flutter` git tag, see [arm64.md](arm64.md#linux-arm64) | `releases_linux.json` | +| macOS Apple silicon | `.zip` archives, arm64 and x64 | `releases_macos.json` | +| macOS Intel | `.zip` archives, x64 and arm64 | `releases_macos.json` | +| Windows x64 | `.zip` archives | `releases_windows.json` | +| Windows ARM64 | the `flutter/flutter` git tag, see [arm64.md](arm64.md#windows-arm64) | `releases_windows.json` | + +OpenHarmony builds are a checkout from gitcode.com on every machine, see [ohos.md](ohos.md). The index is read from `https://storage.googleapis.com/flutter_infra_release/releases/`, or from the mirror named by `FLUTTER_STORAGE_BASE_URL`, see [mirror.md](mirror.md). + +What each machine gets for `3.47.4`, `3.47.4-x64` and `3.47.4-arm64` is in [arm64.md](arm64.md#what-each-machine-gets). + +## Requirements + +- **vfox**, hooked into your shell, so that `flutter` is on the `PATH` after `vfox use`, see vfox's [quick start](https://vfox.dev/guides/quick-start.html). The plugin declares vfox 0.3.0 as the oldest it loads on. The tests run against the latest vfox release and against vfox's `main`, and a Windows account whose home directory contains a space needs vfox 1.0.13 or later ([vfox#712](https://github.com/version-fox/vfox/pull/712)). +- **`git`**, for Linux ARM64 and Windows ARM64 (every install), for OpenHarmony builds, and for the drift check of [flutter-upgrade.md](flutter-upgrade.md). That check asks `git` for the SDK's commit when it installs; without `git` the install still succeeds, writes a `.vfox-manifest` that has no commit, and `vfox use` never warns. Flutter itself needs `git` as well. +- **Flutter's own tools.** Android, iOS, Linux, Windows and web development have requirements of their own (Android SDK, Xcode, Visual Studio, GTK and more). vfox does not install them, see [Flutter's installation guide](https://docs.flutter.dev/install). The example in the [README](../README.md#install) installs `curl git unzip xz-utils zip libglu1-mesa` for Ubuntu. +- **Disk space.** An archive is 1.6 to 2.3 GB (see [versions.md](versions.md#installing)), and vfox removes it after unpacking. The SDK grows as the `flutter` tool downloads what it needs for the platforms you build for. A source install needs room for the checkout as well. Every name you install is a separate SDK, see [versions.md](versions.md#installed-names). + +## Where the plugin connects + +| Host | What for | When | +| --- | --- | --- | +| `storage.googleapis.com`, or your mirror | the release index, the archives, and what the `flutter` tool downloads itself, such as the Dart SDK of a source install and the engine artifacts | always | +| `gitcode.com` | the list of OpenHarmony releases (an API request), and the checkout of an OpenHarmony build | the list when `vfox search` or the `latest` of `vfox install` lists versions, and to install an OpenHarmony build; the checkout for OpenHarmony builds only | +| `github.com` | the checkout of `flutter/flutter` | Linux ARM64 and Windows ARM64 installs | + +`pub.dev` is not the plugin's, but the Flutter tool reaches it when it boots, and the test suite checks that it answers. Installing an official release does not need `gitcode.com`, and a firewall that blocks it costs `vfox search` two quick attempts and a warning, see [troubleshooting.md](troubleshooting.md#gitcodecom-is-rate-limited). Unreleased: release 0.2.4 asks `gitcode.com` for every install as well, and waits for it. + +## What the tests cover + +The Lua hook tests (`tests/hooks_test.lua`) run offline for every pull request. They simulate Linux, macOS and Windows on x64 and arm64 against a fixture of the release index. + +The container suites install a real Flutter with real vfox, in a throwaway container for each combination, see [e2e.md](e2e.md): + +| Machine | Container | Flutter | vfox | +| --- | --- | --- | --- | +| Linux x64 and Linux ARM64 | Ubuntu 26.04, driven from bash. A second test uses zsh and a Homebrew vfox | 3.47.4 | the latest release and `main` | +| Windows x64 and Windows ARM64 | Windows Server 2025 Server Core, driven from PowerShell 7, with a vfox home in a path that contains a space | 3.47.4 | the latest release and `main` | + +On x64 the runs repeat for the OpenHarmony build 3.41.10-ohos-1.0.0, and, with the latest vfox only, with the CFUG mirror set. ARM64 runs the official build only. Each run installs the version, checks the drift warning, and boots `flutter` and `dart`. + +macOS is not in the container suites, because a container cannot run it. Its architecture selection is covered by the offline tests only. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..cfc28c8 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,200 @@ +# Troubleshooting + +[中文](zh-CN/troubleshooting.md) | [English](troubleshooting.md) + +## Messages + +The messages that vfox prints itself are the ones of vfox 1.0.12 and 1.0.13; other versions may word them differently. + +### `no installable runtime provided` + +``` +$ vfox install flutter@dev +no installable runtime provided +failed to install flutter +``` + +The plugin found nothing for the name you typed. The usual reasons: + +- The name is not a release. `flutter@3.47` and `flutter@3` are not completed to the newest 3.47.x or 3.x, and names are case sensitive (`flutter@Stable`). Look the name up in `vfox search flutter`, see [versions.md](versions.md). +- `dev`: Flutter retired the channel, see [channels.md](channels.md#dev). +- The architecture suffix does not exist for the machine: `-arm64` on Linux x64 and Windows x64, any suffix on a release without architecture metadata (`1.22.6-x64`), or no suffix for an old release on Apple silicon that only has an x64 build (`flutter@2.10.5` needs `flutter@2.10.5-x64`). See [arm64.md](arm64.md). +- Unreleased: a release that is older than the first that runs with a native Dart SDK, on Linux ARM64 (`flutter@1.20.0`) or Windows ARM64 (`flutter@3.43.0-0.3.pre`). Release 0.2.4 installs them with the x64 Dart SDK, see [arm64.md](arm64.md#releases-that-run-natively). The `-x64` name installs the archive. +- An OpenHarmony name with `+ohos-` (`3.41.9+ohos-1.0.2`) on release 0.2.4, which only knows `-ohos-` names. Unreleased: it knows both, see [ohos.md](ohos.md#names-and-releases). + +### `flutter@stable is not installed` + +`vfox use` and `vfox uninstall` only know installed names, and a channel is replaced by a release while it installs. `vfox list flutter` shows the names, see [channels.md](channels.md#use-the-version-not-the-channel). `vfox exec flutter@stable` ends with `failed to get runtime package for flutter@stable: open .../cache/flutter/v-stable: no such file or directory` for the same reason. + +### `flutter@3.47.4 is already installed` + +The name is installed. vfox does not download again, and does not write the `.vfox-manifest` again, see [flutter-upgrade.md](flutter-upgrade.md#disabling-the-check). + +### `get version failed: ... (status ...)` + +``` +plugin [PreInstall] method error: ~/.vfox/plugin/flutter/hooks/available.lua:35: get version failed: Get "
": (status nil) +stack traceback: + ... +failed to install flutter +``` + +The plugin could not read Flutter's release index, and it tried three times, five seconds apart, so the install fails ten seconds after it started. After `get version failed:` comes the error of the request, then the HTTP status in parentheses: + +- `(status nil)` means no HTTP answer came: DNS, connection, TLS or proxy problems. A refused connection reads `dial tcp 127.0.0.1:1: connect: connection refused`, and a proxy that does not answer `proxyconnect tcp: dial tcp 127.0.0.1:9: connect: connection refused`. +- `get version failed: nil (status 404)`, or another number, means the server answered and refused. With a mirror, a 404 usually means a wrong address: the plugin asks for `/flutter_infra_release/releases/releases_linux.json` (`releases_macos.json`, `releases_windows.json`). + +See [Network](#network) and [mirror.md](mirror.md). A channel (`flutter@stable`) reads the index once before that, without retrying, and the message then names `hooks/pre_install.lua`. + +### `checksum error` + +``` +Verifying checksum c53c8aeff17d13c4c0b47bff2c54a293e48286e03dd089f37d561737e41a8c2c... +Checksum error, file: ~/.vfox/cache/flutter/flutter_linux_3.13.3-stable.tar.xz +checksum error +failed to install flutter +``` + +vfox downloaded the archive and its SHA-256 differs from the one in Flutter's release index. For a mirror, check that it serves the files of the index it serves. Without a mirror this was the data of the index itself: it lists `3.13.3`, `3.9.0-0.1.pre` and `3.17.0-0.0.pre` twice or three times for one file, with a different checksum for each entry. Flutter uploaded the file again and kept the old entries. The checksum of the newest entry is the right one (checked on Linux by downloading the three archives). + +The plugin lists only the newest entry of such a file, so these three install. Unreleased: release 0.2.4 lists every entry and installs the one that sorts first, which is arbitrary for entries that are equal, and which also depends on the rest of the list, such as how many OpenHarmony releases it holds. On 2026-10-21 it took an old entry for all three on Linux x64, and for some of them on macOS and Windows, and the install ended as above. Install a release next to it (`3.13.2` or `3.13.4` instead of `3.13.3`), or update the plugin. There is no switch to skip the check. + +### `Warning: flutter SDK at ... has drifted from the version vfox installed.` + +`flutter upgrade`, `flutter channel` or a `git` command moved the SDK. `vfox use` still selects it. See [flutter-upgrade.md](flutter-upgrade.md). + +### `flutter does not support the 386 architecture` + +The plugin runs on x64 and ARM64 only. vfox tells it the CPU of its own binary (`386`, `arm`, `riscv64`, `loong64`, ...) and the message names it, as in `flutter does not support the arm architecture`. It stops `vfox search` and the install of an official release. See [platforms.md](platforms.md). Unreleased: release 0.2.4 prints the same without the spaces, `flutter does not support386architecture`. + +### Source installs: Linux ARM64 and Windows ARM64 + +The messages come from the plugin and name the step that failed, see [arm64.md](arm64.md#how-a-source-install-works). + +| Message | Meaning | +| --- | --- | +| `cannot resolve the vfox home directory` | Neither `VFOX_HOME`, `HOME` nor `USERPROFILE` is set, so the plugin does not know where to make `.vfox/tmp/` | +| `failed to initialize git in (is git installed?)` | `git init` failed: `git` is missing, or the directory cannot be written | +| `failed to fetch tag 3.47.4 from https://github.com/flutter/flutter.git` | All three fetches failed. `git` printed its own error above the message. Check that GitHub answers, or set `VFOX_FLUTTER_GITHUB_MIRROR`, see [mirror.md](mirror.md#github-mirror-for-source-installs) | +| `failed to check out 3.47.4 in ` | `git checkout FETCH_HEAD` failed | +| `the checkout at 3.47.4 has no engine version pin` | The checkout has no `bin/internal/engine.version`, so it is not a Flutter SDK | +| `Using GitHub mirror https://gh-proxy.org/https://github.com/flutter/flutter.git` | Not an error: `VFOX_FLUTTER_GITHUB_MIRROR` is in use | + +A failed install leaves `.vfox/tmp/` under `$VFOX_HOME`, or under your home directory, behind. Delete it. The next install of the same name deletes it anyway. + +A fetch that moves less than 1 KB/s for a minute is abandoned and counts as a failed attempt, which is how a stalled connection ends up in `failed to fetch tag`. + +### OpenHarmony builds + +| Message | Meaning | +| --- | --- | +| `OpenHarmony releases are unavailable from https://gitcode.com/api/v5/...: HTTP 429` | Installing an OpenHarmony build: five attempts to read the releases failed. The text after the colon is the last error, here gitcode.com's rate limit, see [below](#gitcodecom-is-rate-limited). Listing versions never prints this message, it prints the warning below and goes on without the OpenHarmony ones | +| `Warning: the OpenHarmony releases could not be read from gitcode.com (), so only the official releases are listed.` | Printed on stderr by `vfox search`, and by the `latest` of `vfox install`, when two attempts did not get the list. The official releases are listed as usual, and an OpenHarmony build still installs by its name. Unreleased: release 0.2.4 prints nothing, and waits up to 6.5 minutes first | +| `flutter 3.47.7-ohos-1.0.0 is not an OpenHarmony release` | The name has `-ohos-` in it, and gitcode.com has no release by that name | +| `flutter 3.41.10-ohos-1.0.0 has no architecture variants` | The name had `-x64` or `-arm64` after it | +| `failed to fetch from https://gitcode.com/CPF-Flutter/flutter_flutter.git` | All three fetches failed | + +See [ohos.md](ohos.md). + +### `bin/internal/shared.sh: line 5: $'\r': command not found` + +A source install on Linux (Linux ARM64, or an OpenHarmony build) whose first `flutter` run fails like this was checked out with CRLF line endings, because of `core.autocrlf=true` in your git configuration. The plugin now sets `core.autocrlf=false` in the checkout. Unreleased: release 0.2.4 takes your setting, so run `git config --global core.autocrlf input`, uninstall the version and install it again, see [arm64.md](arm64.md#limits). + +### `Building flutter tool...` and `It appears that the downloaded file is corrupt` + +The first `flutter` run of a source install builds the Flutter tool and downloads the Dart SDK, so it is slow and needs the network. An install from an archive does not build the tool on its first run: the plugin touches `packages/flutter_tools/pubspec.lock` after unpacking for that, see [development.md](development.md#contracts-that-cost-time). + +`It appears that the downloaded file is corrupt; please try again.` is what the Flutter tool says when the Dart SDK it downloaded is not a valid zip file, which is what a missing file (HTTP 404) gives. That happens for OpenHarmony builds on ARM64. + +## Network + +The plugin reaches `storage.googleapis.com` (or your mirror), `gitcode.com` and, for source installs, `github.com`, see [platforms.md](platforms.md#where-the-plugin-connects). It repeats a request that fails: + +| Request | Attempts | Waits between attempts | +| --- | --- | --- | +| The release index, when listing or installing a version | 3 | 5 seconds | +| The release index, once before that for a channel | 1 | none | +| OpenHarmony releases from gitcode.com, to install an OpenHarmony build | 5 | 10 seconds, but 30, 60, 120 and 180 seconds after HTTP 429 | +| OpenHarmony releases from gitcode.com, to list versions | 2 | 2 seconds. HTTP 429 ends the attempts | +| `git fetch` of a source install or an OpenHarmony build | 3 | none. A transfer under 1 KB/s for a minute counts as failed | + +vfox's own proxy setting covers the plugin's requests and the archive download: + +```bash +vfox config proxy.enable true +vfox config proxy.url http://127.0.0.1:7890 +``` + +While `proxy.enable` is `false`, the usual `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` environment variables apply. `git`, which source installs and OpenHarmony builds run, does not read vfox's setting. It reads its own: `HTTPS_PROXY` or `git config --global http.proxy`. + +### gitcode.com is rate-limited + +The plugin asks gitcode.com for the OpenHarmony releases in two cases. Installing an OpenHarmony build needs them, and waits for them. `vfox search flutter` (vfox answers from its cache for 12 hours, so only the first one asks), and the `latest` of `vfox install`, list them, and do not wait. Installing an official release does not ask gitcode.com at all. + +gitcode.com limits what anonymous clients may ask. Over the limit it answers HTTP 429: + +``` +{"error_code":429,"error_code_name":"UN_KNOW","error_message":"429 The number of times the user invokes this API is too large. Threshold: 10W times per user per hour. Limited account:p_repoRobot.", ...} +``` + +This is gitcode.com's limit for requests without a login, not a limit of yours: the message names the account the request is counted against, `p_repoRobot`. What the plugin does with it: + +- A listing makes two attempts, two seconds apart, and HTTP 429 ends them at once. When it gets no list it prints one line on stderr and goes on with the official releases: + + ``` + Warning: the OpenHarmony releases could not be read from gitcode.com (HTTP 429), so only the official releases are listed. + ``` + + An unreachable gitcode.com costs the same two attempts, so about 2 seconds with a connection that fails at once (measured through a proxy that refused it: 2.1 seconds). `vfox search flutter` is then cached without the OpenHarmony names for 12 hours, and `vfox config cache.availableHookDuration 0` turns the cache off. +- Installing an OpenHarmony build makes up to five attempts and waits 30, 60, 120 and 180 seconds between them after a 429, so it can wait for 6.5 minutes and print nothing. + +Unreleased: release 0.2.4 waits like that for every command that lists versions, and for every install, official releases included. Measured on 2026-10-21, while gitcode.com was answering 429: + +| Command | Took | Result | +| --- | --- | --- | +| `vfox search flutter` | 392 seconds | 434 official releases, no OpenHarmony ones, exit status 0 | +| `vfox install flutter@3.47.4` | 212 seconds | installed. The fourth attempt got through | + +Minutes later the same API answered in a second. Without a 429 a failing request cost 10 seconds per retry, so 40 seconds in all. + +## Things that look wrong and are not + +- **`vfox install flutter@latest` installs `3.47.7-x64`, not `3.47.7`.** `latest` is the first line of `vfox search flutter`, the newest stable release, and it carries the architecture suffix, so the same Flutter installs twice if you also install `stable`, see [versions.md](versions.md#searching). Unreleased: release 0.2.4 installed the newest beta. +- **A version appears more than once in `vfox search flutter`.** Flutter's index lists it in more than one channel, see [versions.md](versions.md#searching). +- **Unreleased: `vfox search flutter` or `vfox install` waits for minutes and prints nothing.** Release 0.2.4 waits for gitcode.com when it limits its API, see [gitcode.com is rate-limited](#gitcodecom-is-rate-limited). The command finishes by itself. +- **`(installed)` is missing for a version you installed.** The mark follows listed names, such as `3.47.4-x64`, and not the bare name you installed. `vfox list flutter` is the truth. +- **A release you expect is missing from `vfox search`.** vfox keeps the result for 12 hours. `vfox config cache.availableHookDuration 0` turns the cache off. Flutter's index may not have it yet either, or your mirror's copy may not. +- **`3.47.4` and `3.47.4-x64` are two installs.** On an x64 machine they are the same Flutter, see [versions.md](versions.md#installed-names). +- **Uninstalling a version switched the global one.** vfox selects the first entry left in `vfox list flutter` when you uninstall the version in use, and that can be a beta. +- **`flutter` is a different Flutter than `vfox use` says.** Another Flutter earlier in your `PATH` wins, such as a manual install in `~/develop/flutter` or one from a package manager. `command -v flutter` shows which one runs, and its path should start with `~/.vfox/sdks/flutter/bin` or, in a project, `.vfox/sdks/flutter/bin`. +- **Flutter says "A new version of Flutter is available!" and suggests `flutter upgrade`.** Ignore it: `flutter upgrade` moves the SDK under vfox, see [flutter-upgrade.md](flutter-upgrade.md). `vfox install flutter@stable` is the upgrade. `flutter --no-version-check` leaves the banner out. +- **`FLUTTER_ROOT` is not set.** The plugin sets only `PATH`. The `flutter` script finds its own directory. +- **`dart` is there without a Dart plugin.** The Flutter SDK includes the Dart SDK, and `dart` is in its `bin`. There is no need to add the [vfox-dart](https://github.com/version-fox/vfox-dart) plugin next to it. +- **`.vfox-manifest` is untracked in the SDK's git.** The plugin writes it, see [flutter-upgrade.md](flutter-upgrade.md). +- **`flutter pub get` is slow or fails in China.** Packages come from `PUB_HOSTED_URL`, which the plugin does not touch, see [mirror.md](mirror.md#which-installs-use-which-mirror). +- **The first `flutter` command downloads a lot.** The Flutter tool fetches what it needs the first time you build for a platform, and it reads `FLUTTER_STORAGE_BASE_URL` for it, so keep the mirror variable set, see [mirror.md](mirror.md#what-the-variable-does). + +## Where Flutter is + +Each install is a directory of its own in the vfox home, `~/.vfox` unless `VFOX_HOME` is set (`storage.sdkPath` moves the SDKs elsewhere, see vfox's [configuration](https://vfox.dev/guides/configuration.html)). `vfox info flutter@3.47.4` prints the directory above the SDK, and the SDK is the `flutter-3.47.4` directory inside it: + +``` +$ vfox info flutter@3.47.4 +/home/user/.vfox/cache/flutter/v-3.47.4 +$ ls /home/user/.vfox/cache/flutter/v-3.47.4 +flutter-3.47.4 +``` + +`vfox use` also makes a link to the SDK you chose, which is the same path whichever version it points at: + +| Scope | Link | +| --- | --- | +| `--global` | `~/.vfox/sdks/flutter` (`%USERPROFILE%\.vfox\sdks\flutter` on Windows) | +| `--project` | `.vfox/sdks/flutter` in the project directory | +| `--session` | `flutter` in a directory under `~/.vfox/tmp` | + +`PATH` holds the `bin` directory of that link, so `flutter` and `dart` come from it. An IDE or a build tool that should follow your global choice can use the link as its Flutter SDK path (Android Studio's *Flutter SDK path*, the `dart.flutterSdkPath` setting of VS Code), and then needs no change when you switch versions. For a fixed version use the directory `vfox info` prints and its `flutter-` directory. Where there is no shell hook, `vfox exec flutter@3.47.4 -- ` runs a command with that Flutter, see [versions.md](versions.md#using). + +## Reporting a problem + +Open an issue at [version-fox/vfox-flutter](https://github.com/version-fox/vfox-flutter/issues) with the output of `vfox --version` and `vfox info flutter`, the operating system and CPU, the command, and everything it printed. `vfox --debug ` prints more. A problem of Flutter itself belongs with [flutter/flutter](https://github.com/flutter/flutter/issues), and one of the OpenHarmony fork with [CPF-Flutter/flutter_flutter](https://gitcode.com/CPF-Flutter/flutter_flutter). diff --git a/docs/versions.md b/docs/versions.md new file mode 100644 index 0000000..5e8663a --- /dev/null +++ b/docs/versions.md @@ -0,0 +1,153 @@ +# Versions and names + +[中文](zh-CN/versions.md) | [English](versions.md) + +The part after `flutter@` in `vfox install`, `vfox use` and `vfox uninstall` is a version: the name of a release as `vfox search flutter` prints it, with or without an architecture suffix, or a channel. + +```bash +vfox install flutter@3.47.4 # a stable release, for the architecture of this machine +vfox install flutter@3.47.4-x64 # the same release, as an x64 build +vfox install flutter@3.50.0-0.1.pre # a beta release +vfox install flutter@stable # the release the stable channel points to today +vfox install flutter@3.41.10-ohos-1.0.0 # an OpenHarmony build +``` + +A version is matched as a whole. `flutter@3.47` and `flutter@3` do not select the newest 3.47.x or 3.x, they fail with `no installable runtime provided`. Names are case sensitive (`flutter@Stable` fails), and vfox itself drops a leading `v`, so `flutter@v3.47.4` is `flutter@3.47.4`. + +## Release names + +A release is named after the Flutter version it contains, as Flutter's release index spells it. `vfox search` shows the channel it was published to next to it, see [Searching](#searching). + +| Name | What it is | +| --- | --- | +| `3.47.7` | A stable release | +| `3.50.0-0.1.pre` | A beta release. Flutter's pre-releases end in `.pre` | +| `3.47.4-x64`, `3.47.4-arm64` | The same release as an x64 or an arm64 build, see [arm64.md](arm64.md) | +| `1.22.6` | An old release without architecture metadata, which installs the same everywhere, see [arm64.md](arm64.md#releases-without-architecture-metadata) | +| `3.41.10-ohos-1.0.0` | An OpenHarmony build: release 1.0.0 of the fork's 3.41.10 line, see [ohos.md](ohos.md) | +| `stable`, `beta` | Channels, resolved to a release when you install, see [channels.md](channels.md) | + +The plugin lists the releases from Flutter 1.17 (April 2020) on. Flutter published its older releases with a `v` prefix (`v1.12.13+hotfix.9` is the first one the plugin stops at), and they are not offered. + +## Installing + +`vfox install` asks the plugin which package matches, then vfox downloads and unpacks it. + +- The plugin reads Flutter's release index for the operating system: `releases_linux.json`, `releases_macos.json` or `releases_windows.json` under `https://storage.googleapis.com/flutter_infra_release/releases/`, or under the mirror that `FLUTTER_STORAGE_BASE_URL` names (see [mirror.md](mirror.md)). It asks up to three times, five seconds apart. +- The version has to equal a name in that index, see [Release names](#release-names). A release nobody published fails with `no installable runtime provided`. +- Without an architecture suffix the plugin picks the build for the architecture of this machine. A release that has no architecture metadata has a single archive, which is the one it takes. +- vfox verifies the archive against the SHA-256 checksum from the index and prints `Verifying checksum ...`. It removes the archive after unpacking. +- On Linux ARM64 and Windows ARM64 the plugin installs a git tag instead of an archive, see [arm64.md](arm64.md). OpenHarmony builds are git checkouts as well, see [ohos.md](ohos.md). Neither is verified against a checksum. +- Installing a name that is already there prints `flutter@3.47.4 is already installed` and downloads nothing. +- Installing an official release does not contact gitcode.com. Listing versions does: `vfox search`, and the `latest` of `vfox install`, ask it for the OpenHarmony releases, twice at most, and say so when it does not answer, see [troubleshooting.md](troubleshooting.md#gitcodecom-is-rate-limited). Unreleased: release 0.2.4 asks it for every install as well, waits up to 6.5 minutes when gitcode.com is rate-limited, and says nothing. + +An archive is large: 3.47.7 on 2026-10-21 was 1.6 GB for Linux x64, 1.9 GB for Windows x64 and 2.3 GB for macOS. The first `flutter` run downloads more, see [troubleshooting.md](troubleshooting.md#things-that-look-wrong-and-are-not). + +## Installed names + +The plugin names an install after the version you asked for, with the architecture suffix only if you wrote one. These are the Linux x64 results on 2026-10-21; a channel or `latest` is replaced by the release it stands for: + +| You run | Installed name | +| --- | --- | +| `vfox install flutter@3.47.4` | `3.47.4` | +| `vfox install flutter@3.47.4-x64` | `3.47.4-x64` | +| `vfox install flutter@stable` | `3.47.7` | +| `vfox install flutter@stable-x64` | `3.47.7-x64` | +| `vfox install flutter@beta` | `3.50.0-0.1.pre` | +| `vfox install flutter@latest` | `3.47.7-x64`, the first line of `vfox search flutter`, see [Searching](#searching) | +| `vfox install flutter@3.41.10-ohos-1.0.0` | `3.41.10-ohos-1.0.0` | + +These are the names `vfox list flutter` shows and `vfox use` and `vfox uninstall` need: + +``` +$ vfox list flutter +-> 3.50.0-0.1.pre-x64 +-> 3.47.4-x64 +-> 3.47.4 <— current +``` + +`3.47.4` and `3.47.4-x64` are the same Flutter on an x64 machine, and vfox keeps them as two installs, each a full SDK. Write the suffix always or never. Uninstalling one leaves the other alone: + +```bash +vfox uninstall flutter@3.47.4-x64 +``` + +`vfox uninstall flutter@stable` answers `flutter@stable is not installed`; `vfox uninstall` takes the installed name, as `vfox use` does. When you uninstall the version in use, vfox switches to the first entry left in `vfox list flutter`, which can be a beta. + +Each install lives in `cache/flutter/v-/flutter-/` under the vfox home (`~/.vfox` unless `VFOX_HOME` is set), see [troubleshooting.md](troubleshooting.md#where-flutter-is). + +## Using + +`vfox use` takes an installed name as it is. It does not complete or search: + +| You run | Result | +| --- | --- | +| `vfox use flutter@3.47.4` | selects `3.47.4` | +| `vfox use flutter@3.47.4-x64` | selects `3.47.4-x64` | +| `vfox use flutter@3.47` | `flutter@3.47 is not installed` | +| `vfox use flutter@stable` | `flutter@stable is not installed`, see [channels.md](channels.md) | +| `vfox use flutter@latest` | the first entry of `vfox list flutter`, which vfox sorts by itself, so it is a beta if you have one installed | + +Pick the scope with `vfox use`'s flags: `--global`, `--project` or `--session`. Without a flag the scope is the session on Linux and macOS and the global one on Windows. Where the choice is written: + +| Scope | Written to | `flutter` comes from | +| --- | --- | --- | +| `--global` | `~/.vfox/.vfox.toml` | `~/.vfox/sdks/flutter/bin` | +| `--project` | `.vfox.toml` in the current directory | `.vfox/sdks/flutter/bin` in that directory | +| `--session` | the current shell only | a directory under `~/.vfox/tmp` | + +`.vfox.toml` holds the installed name (`flutter = "3.47.4"`). Commit it, so that the project builds with one Flutter for everybody, and keep `.vfox/` out of git: vfox does not add it to your `.gitignore`. vfox also reads an asdf `.tool-versions` (`flutter 3.47.4`) when its `legacyVersionFile.enable` setting is on, which it is by default in vfox 1.0.13. The plugin has no legacy file of its own, so `.fvmrc` and FVM's `.fvm` directory are not read. + +The plugin adds `/bin` to `PATH`. That puts `flutter` and `dart` on it. It sets no `FLUTTER_ROOT`, and `~/.pub-cache/bin`, where `dart pub global activate` puts executables, stays yours to add. + +To run one command with a Flutter without selecting it, or in a script, a container or an IDE task where the shell hook is not active, use `vfox exec` with an installed name: + +```bash +vfox exec flutter@3.47.4 -- flutter --version +``` + +`vfox exec flutter@stable` does not work, see [channels.md](channels.md#use-the-version-not-the-channel). + +## Searching + +``` +$ vfox search flutter +Available versions: + - 3.47.7-x64 (stable) [dart 3.13.5] + - 3.47.6-x64 (stable) [dart 3.13.5] + ... + - 1.17.0 (stable) [dart ] + - 3.50.0-0.1.pre-x64 (beta) [dart 3.14.0 (build 3.14.0-315.1.beta)] + - 3.49.0-0.2.pre-x64 (beta) [dart 3.14.0 (build 3.14.0-211.1.beta)] + ... + - 1.17.0-3.2.pre (beta) [dart ] + - 3.41.10-ohos-1.0.0 (OpenHarmony) + ... +``` + +Each line is a name that can follow `flutter@`, with the channel the release was published to in parentheses, and the Dart SDK that comes with it in brackets. A mark `(installed)` follows the names that are installed. Flutter's index has no Dart version for the releases up to `2.9.0-0.1.pre`, which leaves the brackets empty. OpenHarmony builds carry `(OpenHarmony)` and no Dart version. + +The mark only follows names the list prints. On x64 Linux and Windows nearly every official release is listed as `3.47.4-x64`, so an install made with `vfox install flutter@3.47.4` (installed name `3.47.4`) shows no mark, while `vfox install flutter@3.47.4-x64` does. Check `vfox list flutter` for what is installed. + +In an interactive terminal `vfox search flutter` lets you pick a version and installs it; elsewhere it prints the list. vfox keeps the answer for 12 hours (`vfox config cache.availableHookDuration`), so a release published a minute ago may be missing; `vfox config cache.availableHookDuration 0` turns the cache off. + +The list is sorted as follows: + +1. Official releases come before OpenHarmony builds. +2. The architecture of this machine comes before the others. A release without architecture metadata counts as the architecture of this machine. +3. Releases of the stable channel come before the pre-releases of the beta and dev channels. +4. The newest comes first, going by the numbers in the version. `3.47.0` sorts above `3.47.0-0.4.pre`. +5. Two builds of one version put the architecture of this machine first, and a version in two channels lists beta before dev. + +So the first line is the newest stable release, and that is what `vfox install flutter@latest` installs, with the architecture suffix: + +``` +$ vfox install flutter@latest +Using latest version: 3.47.7-x64 +``` + +`latest` keeps to the architecture of this machine, as [arm64.md](arm64.md) explains, and never selects an OpenHarmony build. Unreleased: release 0.2.4 sorts every release by its numbers alone, so a beta of a later minor release (`3.50.0-0.1.pre`) comes before every `3.47.x`, and its `latest` installs that beta, as `3.50.0-0.1.pre-x64`. + +A few versions appear more than once. They are in Flutter's index more than once, because a release was promoted from one channel to another and has an archive in each (`2.13.0-0.1.pre` is listed for beta and for dev), and the lines install the same name. A release that was published again has the same archive under two checksums, and the plugin lists only the newest entry of it (`3.13.3`, `3.9.0-0.1.pre` and `3.17.0-0.0.pre` are the three that Flutter published again). Unreleased: release 0.2.4 lists all the entries, takes the one that happens to sort first, and an install that takes an old one ends with `checksum error`, see [troubleshooting.md](troubleshooting.md#checksum-error). + +The length of the list depends on the machine. On 2026-10-21 it had 455 names on Linux x64 (430 official releases and 25 OpenHarmony builds), 717 on macOS (692 and 25, with both architectures), 453 on Windows x64, and 743 and 490 on Linux ARM64 and Windows ARM64, which add 288 and 37 source installs. A list that is much shorter than that means the plugin could not read part of its sources, see [troubleshooting.md](troubleshooting.md#network). diff --git a/docs/zh-CN/arm64.md b/docs/zh-CN/arm64.md new file mode 100644 index 0000000..f5e2f12 --- /dev/null +++ b/docs/zh-CN/arm64.md @@ -0,0 +1,108 @@ +# 架构选择 + +[中文](arm64.md) | [English](../arm64.md) + +可安装的版本里有带架构后缀的,比如 `3.47.4-arm64` 和 `3.47.4-x64`。两种构建可以都装上,再来回切换。以 Apple 芯片的 Mac 为例: + +```bash +vfox install flutter@3.47.4-arm64 +vfox install flutter@3.47.4-x64 +vfox use --global flutter@3.47.4-x64 +``` + +不带架构的命令,比如 `vfox install flutter@3.47.4` 或 `vfox install flutter@stable`,仍然选本机架构,安装后的版本名也和以前一样。渠道也可以带后缀,比如 `vfox install flutter@stable-x64`。版本列表先列默认架构,再列其他架构,每一组内部按版本号排序。这样 `@latest` 就会保持在默认架构上。 + +列表里只有 Flutter 为当前操作系统提供的架构。请求其他架构会报 `no installable runtime provided`。 + +## 各种机器分别得到什么 + +| 机器 | `3.47.4`、`stable` | `3.47.4-x64` | `3.47.4-arm64` | +| --- | --- | --- | --- | +| Linux x64 | Linux 压缩包 | 同一个压缩包,装成 `3.47.4-x64` | 不提供 | +| Linux ARM64 | 从 git 标签源码安装 | Linux x64 压缩包,在这里运行不了 | 源码安装,名字是 `3.47.4-arm64` | +| macOS Apple 芯片 | arm64 压缩包 | x64 压缩包,需要 Rosetta | arm64 压缩包,名字是 `3.47.4-arm64` | +| macOS Intel | x64 压缩包 | 同一个压缩包,名字是 `3.47.4-x64` | arm64 压缩包,在这里运行不了 | +| Windows x64 | Windows 压缩包 | 同一个压缩包,装成 `3.47.4-x64` | 不提供 | +| Windows ARM64 | 从 git 标签源码安装 | Windows x64 压缩包 | 源码安装,名字是 `3.47.4-arm64` | + +最后两列里的名字是安装后的名字,见 [versions.md](versions.md#已安装的名称)。在 x64 机器上同时装 `3.47.4` 和 `3.47.4-x64`,得到的是同一个 Flutter 的两份安装。要么总是写后缀,要么总是不写。 + +## 没有架构元数据的发布版本 + +Flutter 的发布索引给大多数版本标了架构(`dart_sdk_arch`),有些老版本没有。2026-10-21 时,它们是稳定版 `1.17.0` 到 `1.17.5`、`1.22.5` 和 `1.22.6`,以及到 `2.10.0-0.3.pre` 为止的预发布版本,一共 114 个。插件在所有机器上都不带后缀地列出它们,安装的是 Flutter 发布的那一个压缩包,是 x64 构建: + +```bash +vfox install flutter@1.22.6 +``` + +它们不会以 `-arm64` 或 `-x64` 的形式提供,所以 `vfox install flutter@1.22.6-arm64` 和 `vfox install flutter@1.22.6-x64` 都会失败。因为它们算作默认架构,所以排在原生构建中间,不会排到外来架构的后面。在 Linux ARM64 和 Windows ARM64 上,它们也没有源码安装(见[限制](#限制)),所以 `vfox install flutter@1.22.6` 在那里装的是 x64 压缩包。 + +## macOS + +### Apple 芯片 + +运行 x64 构建需要 Rosetta。 + +Flutter 从 `2.12.0-4.1.pre`(预发布版本)和 `3.0.0`(稳定版)开始才有 arm64 构建。更老的版本只有 x64 构建,而且不带后缀的名字不会退回到它,所以要明确地要: + +```bash +vfox install flutter@2.10.5-x64 +``` + +`vfox install flutter@2.10.5` 会报 `no installable runtime provided`。 + +### Intel + +macOS 的发布索引里不管是哪种 Mac 都带 arm64 构建,所以在 Intel 的 Mac 上也会列出它们,`vfox install flutter@3.47.4-arm64` 也能装上一个。它在 Intel 的 Mac 上运行不了。 + +## Linux ARM64 + +在 Linux ARM64 上,插件安装的是官方 `flutter/flutter` 仓库的 git 标签,而不是预编译的包。Flutter 工具首次运行时会自己引导出对应的原生 `dart-sdk-linux-arm64`,所以得到的是原生 SDK。 + +明确写 `-x64` 的请求仍然解析到上游的压缩包,它在 Linux ARM64 上运行不了。 + +## Windows ARM64 + +在 Windows ARM64 上,插件同样安装官方 `flutter/flutter` 仓库的 git 标签,而不是预编译的包,原因和 Linux ARM64 一样:上游没有发布 `windows-arm64` 的压缩包。Flutter 工具首次运行时会自己引导出对应的原生 `dart-sdk-windows-arm64`,所以得到的是原生 SDK。 + +明确写 `-x64` 的请求仍然解析到上游的 x64 压缩包。它不是原生构建,插件的测试也不会运行它。 + +## 源码安装如何进行 + +Linux ARM64 和 Windows ARM64 会为“有 x64 构建、并且能用原生 Dart SDK 运行”的版本列出一个 `-arm64` 源码安装(2026-10-21 时 Linux 上有 288 个,Windows 上有 37 个),数据取自同一份发布索引,见[能原生运行的发布版本](#能原生运行的发布版本)。安装其中一个的过程是: + +1. 从一个空目录 `.vfox/tmp/<名称>` 开始,它在 `$VFOX_HOME` 下;没设置 `VFOX_HOME` 就在你的主目录下(`~/.vfox/tmp/3.47.4`,`3.47.4-arm64` 则是 `~/.vfox/tmp/3.47.4-arm64`), +2. 在那里执行 `git init`,并设置 `core.longpaths=true` 和 `core.autocrlf=false`, +3. 执行 `git fetch --depth 1 https://github.com/flutter/flutter.git refs/tags/3.47.4`,最多三次, +4. 检出 `FETCH_HEAD`,并检查 `bin/internal/engine.version` 是否存在, +5. 把这个目录交给 vfox,由它移动到安装位置(vfox 会打印 `Moving ... to ...`)。 + +得到的是只含一个提交的浅克隆,检出在一个标签上,而不在某个分支上。它大约 225 MB,3.47.7 在网速很快时拉取用了 10 秒。这样的检出也是 `flutter --version` 在这种安装上显示 `channel [user-branch]` 和 `unknown source`,而不是 `channel stable` 的原因。 + +安装失败会把这个目录留下。下次安装同名版本时会先删掉它,你自己删也没问题。 + +## 能原生运行的发布版本 + +`-arm64` 源码安装的意义,就是用上原生的 Dart SDK。Flutter 工具在第一次运行时,只有两个条件同时成立才会取 ARM64 的 Dart SDK:这个版本自己的引导脚本(`bin/internal/update_dart_sdk.sh`,Windows 上是 `update_dart_sdk.ps1`)要去请求它,Flutter 的存储里也要有它(该版本引擎修订号下的 `dart-sdk--arm64.zip`)。下表是这两个条件都成立的最老版本,2026-10-21 对 300 个有 x64 构建的版本逐个检查的结果: + +| 机器 | 从哪个版本开始有原生 Dart SDK | 在它之前 | +| --- | --- | --- | +| Linux ARM64 | Flutter 2.2.0 | 脚本请求的是 x64 的 SDK,即 `1.20.0` 到 `2.0.6`(17 个版本)。存储里从 2.0.0 起就有 ARM64 的 SDK,只是这些脚本不去请求它,而 ARM64 的机器运行不了 x64 的 SDK | +| Windows ARM64 | Flutter 3.44.0-0.1.pre | 脚本从 `3.20.0-1.1.pre` 起会请求 ARM64 的 SDK,存储里没有时会说 `The current channel's Dart SDK does not support Windows Arm64, falling back to Windows x64...`,改用 x64 的 SDK,Windows 靠它的模拟层运行它。存储里有 ARM64 SDK 的更老版本只有七个 beta,其中三个工具会用上(`3.20.0-1.1.pre`、`3.21.0-0.0.pre` 和 `3.30.0-0.1.pre`) | + +插件从这个版本开始提供 `-arm64` 源码安装。Linux ARM64 上的 `vfox install flutter@1.20.0`,以及 Windows ARM64 上的 `vfox install flutter@3.43.0-0.3.pre`,都会以 `no installable runtime provided` 结束。更老版本的 x64 压缩包仍然在列表里:`vfox install flutter@3.43.0-0.3.pre-x64` 装的是其中一个,Windows ARM64 靠模拟运行它;Linux ARM64 上的 `flutter@1.20.0-x64` 适合能运行 x64 程序的环境。这张表是 `lib/source.lua` 里的数据,检查方法见 [development.md](development.md#刷新-dart-sdk-表)。 + +未发布:0.2.4 为每个有 x64 构建的版本都提供 `-arm64` 源码安装,更老的那些拿到的是 x64 的 Dart SDK。在 Windows ARM64 上它靠模拟运行,名字却写着 arm64;在 Linux ARM64 上,ARM64 的机器运行不了它。 + +## 限制 + +### Linux ARM64 和 Windows ARM64 + +- **需要 `git`。** 检出会对 `https://github.com/flutter/flutter.git` 执行 `git init` / `fetch` / `checkout`。拉取卡住(一分钟内速度低于 1 KB/s)时会被放弃并重试,最多三次;`GIT_HTTP_LOW_SPEED_LIMIT` / `GIT_HTTP_LOW_SPEED_TIME` 可以改这个阈值。 +- **第一次运行 `flutter` 很慢。** 它要下载原生的 Dart SDK 并构建 Flutter 工具,所以需要网络。 +- **不做校验和验证。** git 的历史就是完整性检查。 +- **老版本没有 `-arm64` 变体。** 没有架构元数据的版本(比如 `1.22.6`,见上文)保持原有的、只有 x64 的行为。 +- **镜像对克隆不起作用。** `FLUTTER_STORAGE_BASE_URL`(见 [mirror.md](mirror.md))只影响发布索引、x64 压缩包和 Flutter 工具自己的下载;源码安装从 GitHub 克隆。要给 GitHub 的源码安装用镜像,请设置 `VFOX_FLUTTER_GITHUB_MIRROR`(见 [mirror.md](mirror.md))。 +- **老版本不提供。** 它们用不上原生的 Dart SDK,见[能原生运行的发布版本](#能原生运行的发布版本)。 +- **你的 git 配置同样生效。** 插件以你的身份运行 `git`,只有换行符例外:它会在检出里设置 `core.autocrlf=false`,这样全局的 `true`(Git for Windows 的安装程序默认建议这样设)就不会让 Flutter 的 shell 脚本带上 CRLF 换行。未发布:0.2.4 用的是你自己的设置。设成 `core.autocrlf=true` 时,Linux ARM64 的安装和 Linux 上的 OpenHarmony 构建版本,检出的 `bin/internal/shared.sh` 和 Flutter 其他 `.sh` 文件里都是 CRLF,因为 Flutter 的 `.gitattributes` 只对 `bin/flutter`、`bin/flutter-dev` 和 `bin/dart` 强制使用 LF;第一次运行 `flutter` 就会停在 `bin/internal/shared.sh: line 5: $'\r': command not found`。请执行 `git config --global core.autocrlf input`(或 `false`),卸载这个版本再重新安装。 +- **残留。** 安装失败会把 `.vfox/tmp/<名称>` 留下,见[源码安装如何进行](#源码安装如何进行)。 diff --git a/docs/zh-CN/changes.md b/docs/zh-CN/changes.md new file mode 100644 index 0000000..8e171e8 --- /dev/null +++ b/docs/zh-CN/changes.md @@ -0,0 +1,48 @@ +# 变更记录 + +[中文](changes.md) | [English](../changes.md) + +每个版本改了什么,写成用户能感受到的样子。其余的细节见 GitHub 的发布页面和下面链接的 pull request。最新的发布版是 0.2.4,`main` 比它多出“未发布”下的所有内容。想现在就用上这些改动,见 [install-from-source.md](install-from-source.md)。 + +## 未发布 + +- 安装官方版本不再访问 gitcode.com。0.2.4 每次安装都要问它,而 gitcode.com 对它的 API 限流(HTTP 429)时,一次安装或一次 `vfox search` 最多会等 6.5 分钟,什么也不打印:2026-10-21 测得 212 秒和 392 秒。`vfox search` 和 `vfox install` 的 `latest` 仍然会列出 OpenHarmony 的发布版本,但它们只尝试两次,遇到 HTTP 429 就放弃,拿不到列表时在 stderr 上打印一条警告。安装 OpenHarmony 构建版本还是和以前一样会等。见 [troubleshooting.md](troubleshooting.md#gitcodecom-被限流)。 +- `3.13.3`、`3.9.0-0.1.pre` 和 `3.17.0-0.0.pre` 可以安装了。Flutter 重新上传了它们的文件,却把旧的校验和留在索引里,挨着新的,0.2.4 可能取到旧的,以 `checksum error` 结束。现在列表会保留一个压缩包最新的条目,它才有文件的校验和。见 [troubleshooting.md](troubleshooting.md#checksum-error)。 +- 标签是 `+ohos-` 的 OpenHarmony 构建版本会被列出,也可以安装:`3.41.9+ohos-1.0.2`、`3.35.7+ohos-1.0.5`、`3.27.4+ohos-1.0.9` 和 `3.22.3+ohos-1.1.6`。见 [ohos.md](ohos.md#名称与发布版本)。 +- 源码安装的检出会设置 `core.autocrlf=false`。全局 `core.autocrlf=true` 时,0.2.4 在 Linux 上(Linux ARM64 和 OpenHarmony 构建版本)会让 Flutter 的 shell 脚本带上 CRLF 换行,`flutter` 就会停在 `bin/internal/shared.sh: line 5: $'\r': command not found`。见 [arm64.md](arm64.md#限制)。 +- Linux ARM64 和 Windows ARM64 只从第一个能用原生 Dart SDK 运行的版本起提供 `-arm64` 源码安装,分别是 Flutter 2.2.0 和 3.44.0-0.1.pre。0.2.4 也提供更老的版本,用的是 x64 的 Dart SDK:ARM64 的 Linux 机器运行不了它,Windows 靠模拟层运行它,名字却写着 arm64。这些版本的 `-x64` 压缩包仍然在列表里。见 [arm64.md](arm64.md#能原生运行的发布版本)。 +- `vfox search` 把稳定版列在预发布版本之前,所以 `vfox install flutter@latest` 装的是最新的稳定版(2026-10-21 是 `3.47.7-x64`),而不是最新的 beta(`3.50.0-0.1.pre-x64`)。见 [versions.md](versions.md#搜索)。 +- 遇到插件不支持的 CPU 时,提示有了空格:`flutter does not support the arm architecture`。 + +## 0.2.4 (2026-10-17) + +- `dev` 渠道没有了。Flutter 已经废弃它(最后一个版本是 2022 年 4 月的 `2.13.0-0.1.pre`),`vfox install flutter@dev` 不再能解析。`stable` 和 `beta` 的说明写在 [channels.md](channels.md) 里([#47](https://github.com/version-fox/vfox-flutter/pull/47))。 +- 主目录带空格的 Windows 账户,比如 `C:\Users\John Doe`,在 vfox 1.0.13 或更新的版本上可以用了([#48](https://github.com/version-fox/vfox-flutter/pull/48))。 +- 卡住的 `git fetch`(一分钟内速度低于 1 KB/s)会被放弃并重试,最多三次。它适用于 Linux ARM64 和 Windows ARM64 的源码安装,以及 OpenHarmony 构建版本([#49](https://github.com/version-fox/vfox-flutter/pull/49))。 + +## 0.2.3 (2026-10-10) + +- Linux ARM64([#23](https://github.com/version-fox/vfox-flutter/pull/23))和 Windows ARM64([#24](https://github.com/version-fox/vfox-flutter/pull/24))安装官方 `flutter/flutter` 的 git 标签,因为 Google 没有为它们发布压缩包。见 [arm64.md](arm64.md)。 +- 从 gitcode.com 安装 OpenHarmony 构建版本,比如 `3.41.10-ohos-1.0.0`([#16](https://github.com/version-fox/vfox-flutter/pull/16))。修复了在 Windows 上安装它们的问题([#17](https://github.com/version-fox/vfox-flutter/pull/17)、[#18](https://github.com/version-fox/vfox-flutter/pull/18)),gitcode.com 回答 HTTP 429 时,会先等一会儿再试下一次([#31](https://github.com/version-fox/vfox-flutter/pull/31))。见 [ohos.md](ohos.md)。 +- 当 `flutter upgrade` 或类似的命令把 vfox 管理的 SDK 移到了别的提交,`vfox use` 会发出警告,并给出恢复它的命令([#27](https://github.com/version-fox/vfox-flutter/pull/27))。见 [flutter-upgrade.md](flutter-upgrade.md)。 +- 从压缩包安装之后第一次运行 `flutter`,不再重新构建 Flutter 工具([#39](https://github.com/version-fox/vfox-flutter/pull/39)、[#37](https://github.com/version-fox/vfox-flutter/issues/37))。 +- `VFOX_FLUTTER_GITHUB_MIRROR` 为源码安装的 GitHub 克隆指定镜像([#34](https://github.com/version-fox/vfox-flutter/pull/34),它取代了 [#29](https://github.com/version-fox/vfox-flutter/pull/29) 的自动回退)。失败的拉取最多重试三次。见 [mirror.md](mirror.md)。 +- 发布索引最多请求三次,每次间隔五秒,错误信息里会写出 HTTP 状态([#24](https://github.com/version-fox/vfox-flutter/pull/24))。 +- `vfox add flutter` 会打印一个指向 README 的链接([#30](https://github.com/version-fox/vfox-flutter/pull/30))。 + +## 0.2.2 (2026-09-12) + +- 列出每个架构的变体,比如 `3.47.4-x64` 和 `3.47.4-arm64`,这样你可以安装其中任何一个,并来回切换。不带后缀的名字仍然是本机架构,安装后的名字也不变([#6](https://github.com/version-fox/vfox-flutter/pull/6))。见 [arm64.md](arm64.md)。 + +## 0.2.1 (2026-09-12) + +- `FLUTTER_STORAGE_BASE_URL` 为发布索引和压缩包指定镜像([#8](https://github.com/version-fox/vfox-flutter/pull/8)),镜像列表也更新到了最新([#10](https://github.com/version-fox/vfox-flutter/pull/10))。见 [mirror.md](mirror.md)。 + +## 0.2.0 (2024-07-01) + +- macOS 得到对应自己架构的构建,Apple 芯片或 Intel,列表也改为新版本在前([#3](https://github.com/version-fox/vfox-flutter/pull/3))。 + +## 0.1.x (2024-04-01) + +- 0.1.0 是第一个发布版。它列出 Flutter 索引里适用于当前操作系统的版本,安装压缩包并验证它的 SHA-256 校验和,认得 `stable`、`beta` 和 `dev` 渠道。 +- 0.1.1 只改了版本号。 diff --git a/docs/zh-CN/channels.md b/docs/zh-CN/channels.md new file mode 100644 index 0000000..049f149 --- /dev/null +++ b/docs/zh-CN/channels.md @@ -0,0 +1,65 @@ +# 渠道 + +[中文](channels.md) | [English](../channels.md) + +`stable` 和 `beta` 安装该渠道当前指向的版本,也可以带架构后缀,比如 `stable-x64`(见 [arm64.md](arm64.md)): + +```bash +vfox install flutter@stable +``` + +2026-10-21 时,`stable` 指向 3.47.7,`beta` 指向 3.50.0-0.1.pre。 + +## 渠道如何解析 + +Flutter 的发布索引(`releases_linux.json`、`releases_macos.json` 和 `releases_windows.json`,见 [versions.md](versions.md#安装))在 `current_release` 下写明了每个渠道指向哪个提交。插件读出这个提交,安装由它构建出的版本。如果请求带了架构后缀,或者机器上同一个提交有不止一个构建(macOS 上每个较新的版本都有 x64 和 arm64 两个压缩包),插件会取对应架构的那个构建: + +| 你执行 | 安装的是 | +| --- | --- | +| `vfox install flutter@stable` | 本机架构的稳定版,名字是 `3.47.7` | +| `vfox install flutter@stable-x64` | 它的 x64 构建,名字是 `3.47.7-x64` | +| `vfox install flutter@stable-arm64` | 它的 arm64 构建(如果有的话),名字是 `3.47.7-arm64`,见 [arm64.md](arm64.md) | +| `vfox install flutter@beta` | beta 版,名字是 `3.50.0-0.1.pre` | + +渠道请求比版本请求多读一次发布索引,而且这一次不会重试。镜像(见 [mirror.md](mirror.md))提供的是它自己的那份索引,所以渠道指向哪里,以那份索引为准。2026-10-21 时 Google、CFUG 和 CERNET 的索引是一致的。 + +## 你会得到什么 + +- **安装时解析。** SDK 按渠道指向的版本命名,比如 `flutter@3.47.7`(`stable-x64` 则是 `flutter@3.47.7-x64`),安装结束时 vfox 会打印这个版本: + + ``` + $ vfox install flutter@stable + Preinstalling flutter@3.47.7... + ... + Install flutter@3.47.7 success! + Please use `vfox use flutter@3.47.7` to use it. + ``` + +- **要用版本号,不要用渠道名。** 渠道名不是已安装的名字,见下文。 +- **渠道不会自动跟随。** 渠道移到新版本之后,`vfox install flutter@stable` 会把新版本装在旧版本旁边,再用 `vfox use` 切过去。已经在用的 SDK 不会被改动。 + +## 用版本号,不要用渠道名 + +vfox 只认已安装的名字。渠道在插件安装的过程中就换成了具体的版本,所以安装之后 `vfox list flutter` 里没有 `stable`,所有要按已安装的名字查找的命令,用它都会失败: + +- `vfox use flutter@stable` 报 `flutter@stable is not installed`;改用 `vfox use --global flutter@3.47.7`。 +- `vfox uninstall flutter@stable` 同样失败;`vfox uninstall` 也要写版本号。 +- `vfox exec flutter@stable -- flutter --version` 会把这个版本装上(或发现已经装了),然后打印 `failed to get runtime package for flutter@stable: open .../cache/flutter/v-stable: no such file or directory`,以退出码 1 结束,命令并没有运行。要写成 `vfox exec flutter@3.47.7 -- flutter --version`。 +- `.vfox.toml` 或 `.tool-versions` 里写 `stable` 什么也选不中:`vfox current` 显示 `flutter -> N/A`,`flutter` 会来自下一个作用域。项目文件里要写版本号。 + +实际上,渠道代表的版本就是 `vfox search flutter` 里最新的 `(stable)` 或 `(beta)` 那一行,见 [versions.md](versions.md#搜索)。想让项目不受渠道变化影响,始终用同一个 Flutter,就用 `vfox use --project flutter@3.47.7` 固定版本,并把它写出的 `.vfox.toml` 提交进仓库。 + +## `latest` 不是渠道 + +`vfox install flutter@latest` 是 vfox 自己的简写,指的是 `vfox search flutter` 的第一行。那一行是最新的稳定版,带着架构后缀(2026-10-21 时是 `3.47.7-x64`),所以 `latest` 和 `stable` 装的是同一个 Flutter,只是名字不同(`3.47.7-x64` 和 `3.47.7`)。优先用 `stable`,见 [versions.md](versions.md#搜索)。未发布:0.2.4 按版本号里的数字把预发布版本和稳定版混在一起排,所以它的第一行是最新的 beta,它的 `latest` 装的是 `3.50.0-0.1.pre-x64`。 + +## `dev` + +Flutter 已经废弃 `dev` 渠道,它的最后一个版本是 2022 年 4 月的 `2.13.0-0.1.pre`,所以插件不再接受它。`releases_.json` 里 `current_release` 下仍然给 `dev` 写了一个提交,但插件不再看这一项,`vfox install flutter@dev` 会以下面的输出结束: + +``` +no installable runtime provided +failed to install flutter +``` + +过去发布到 `dev` 渠道的版本仍然会按各自的版本号列出,比如 `2.13.0-0.1.pre`,在 `vfox search flutter` 里它后面带着 `(dev)`。 diff --git a/docs/zh-CN/development.md b/docs/zh-CN/development.md new file mode 100644 index 0000000..27b836a --- /dev/null +++ b/docs/zh-CN/development.md @@ -0,0 +1,227 @@ +# 开发 + +[中文](development.md) | [English](../development.md) + +## 目录布局 + +``` +metadata.lua 插件名、版本、最老的 vfox,以及 `vfox add` 打印的提示 +hooks/available.lua vfox search:所有名字的列表 +hooks/pre_install.lua vfox install:把名字解析成一个包;还有 PostInstall +hooks/pre_use.lua vfox use:SDK 漂移时给出警告 +hooks/env_keys.lua PATH +lib/util.lua 镜像、平台、版本名及其顺序 +lib/git.lua git 命令、GitHub 镜像、工作目录 +lib/source.lua Linux ARM64 和 Windows ARM64 的源码安装 +lib/ohos.lua OpenHarmony 发布版本和检出 +lib/manifest.lua .vfox-manifest 和 pubspec.lock 的修正 +tests/hooks_test.lua 离线的 hook 测试 +tests/e2e/ 容器测试,见 e2e.md +Injection.lua vfox 注入内容的类型提示,从不加载 +.luarc.json, stylua.toml Lua 语言服务器和格式化工具的设置(Lua 5.1,120 列,4 个空格) +.devcontainer/ Linux 开发容器,见 e2e.md +.github/workflows/ lint、许可证头、hook 测试、e2e 和发布工作流 +``` + +vfox 加载 `metadata.lua`,在命令需要时调用 hook,并把 `PLUGIN` 和 `RUNTIME`(`osType`、`archType`、`version`)放进作用域。`osType` 和 `archType` 是 vfox 二进制的 Go `GOOS` 和 `GOARCH`。这些 hook 运行在 gopher-lua 里,这是一个用 Go 写的 Lua 5.1 解释器,带有 vfox 的 `http` 和 `json` 模块。`lib` 里的文件用 `require` 加载,`lib/util.lua` 定义的是全局变量。 + +## 一条命令会运行什么 + +| 你执行 | 用到的 hook | +| --- | --- | +| `vfox search flutter` | `Available` | +| `vfox install flutter@3.47.4` | `PreInstall` 返回包,并自己调用 `Available` 来找它。vfox 下载、验证并解压,然后调用 `PostInstall` | +| `vfox use flutter@3.47.4` | `PreUse` 检查 SDK 并返回名字。`EnvKeys` 提供 `PATH` | +| `vfox uninstall flutter@3.47.4` | 没有,名字必须准确 | + +## 容易踩坑的约定 + +- `Available` 为每个名字返回一个表,里面有 `version`、`url`、`sha256`、`key`、`note` 和 `addition`。`key` 是这个版本的 git 提交,渠道就是靠它找到自己的版本:索引里的 `current_release.stable` 是一个提交,`PreInstall` 取 `key` 等于这个提交的条目。`note` 是渠道,显示在圆括号里。`addition` 是 Dart 版本,显示在方括号里。`source = true` 是插件自己给 `-arm64` 源码安装做的标记。 +- `PreInstall` 返回 `version`、`url` 和 `sha256`。`version` 会成为安装的名字。除非请求带了架构后缀,名字里就不带后缀(`3.47.4` 还是 `3.47.4`,`3.47.4-x64` 还是 `3.47.4-x64`),渠道则换成它代表的版本。源码安装返回的 `url` 是本地目录的路径,没有校验和,由 vfox 把这个目录移动到位。 +- `PreInstall` 调用的是 `self:Available({ officialOnly = true })`,所以用不到 vfox 对列表的 12 小时缓存。每次安装都要读索引。`officialOnly` 会略去 OpenHarmony 发布版本:官方版本的安装用不着它们,而且它们来自的 API 被 gitcode.com 限流。vfox 用它自己的上下文调用 `Available`(`vfox search`,以及 `vfox install` 的 `latest`),那样的列表里有它们。未发布:0.2.4 每次安装都会去问 gitcode.com。 +- `PreInstall` 对解析不了的名字返回 `nil`,vfox 会把它报告成 `no installable runtime provided`。失败时它会抛出错误:`error()` 的内容会出现在 `plugin [PreInstall] method error:` 那一行里。 +- 索引里有些版本不止列了一次。`hooks/available.lua` 里的 `newestPublications` 对一个版本的每个压缩包只保留最新(`release_date`)的那条,因为重新上传过的文件,只有最新那条的校验和才是对的(见 [troubleshooting.md](troubleshooting.md#checksum-error))。不同的版本绝不会合并,在两个渠道里的版本各有一个压缩包,两条都保留。`table.sort` 不是稳定排序,所以比较函数必须能区分任意两个条目,否则相等的条目出来的顺序取决于整个输入,而 `PreInstall` 取第一个匹配的。为此比较函数最后还比较版本和渠道。 +- `PostInstall` 读取 `ctx.sdkInfo[PLUGIN.name]`。清单和 `pubspec.lock` 的修正都在 `pcall` 里运行,所以那里的失败永远不会让安装失败。 +- vfox 用 Go 的 `archive/tar` 解压 `.tar.xz`,它给文件打上的是解压时的时间,而不是压缩包里存的时间。官方的 `tar -xf` 流程会保留那些时间,于是 `packages/flutter_tools/pubspec.lock` 比 `pubspec.yaml` 新。vfox 安装之后,两者共用解压的时间,谁最后写入谁就显得更新,所以 `bin/internal/shared.sh` 可能发现 `pubspec.yaml` 比 `pubspec.lock` 新,在第一次运行时重新构建工具(`Building flutter tool...`)。`manifest.touchPubspecLock` 在所有平台上重写 `pubspec.lock`,把顺序恢复过来([#37](https://github.com/version-fox/vfox-flutter/issues/37))。源码安装按设计就要在第一次运行时构建工具。 +- Windows 上的命令行,在 vfox 1.0.13 及更新的版本里原样交给 `cmd.exe`([vfox#712](https://github.com/version-fox/vfox/pull/712)),更老的版本会再加一层引号。`lib/git.lua` 和 `lib/manifest.lua` 里的 `quote` 只给含有空白字符的路径加双引号,其他路径保持原样,这样带空格的路径在 1.0.13 上可用,不带空格的路径在更老的版本上也依然可用。 +- `os.execute` 报告成功的方式,视所用的 Lua 而定,可能是 `0`,也可能是 `true`,`lib/git.lua` 里的 `exec` 两者都接受。插件读取 `git` 的输出靠的是 `io.popen`;没有输出或失败的命令读出来是 `nil`,所以 `lib/manifest.lua` 里缺少 `git` 时不会有任何察觉。 +- `lib/git.lua` 的工作目录依次取自 `VFOX_HOME`、`HOME`、`USERPROFILE`,再加上 `.vfox/tmp/<名称>`。设置了 `VFOX_HOME` 时就是 `$VFOX_HOME/.vfox/tmp/<名称>`,比 vfox 主目录低一层 `.vfox`。安装失败时这个目录不会被删除。 +- `git` 用的是你的配置,只有 `git.init` 写进检出里的两项设置例外:`core.longpaths=true`,以及 `core.autocrlf=false`,因为全局的 `true` 会让 Flutter 的 shell 脚本带上 CRLF(见 [arm64.md](arm64.md#限制))。在 Windows 上 `false` 写出的 CRLF 文件和 `true` 一样,因为那里的 `core.eol` 是 native。这是 git 本身的工作方式,我们在 Linux 上用 `core.eol=crlf` 和 Flutter 的 `.gitattributes`(第一条是 `* text=auto`)检查过:两种设置写出的文件同样是 CRLF,`bin/flutter` 则保持 LF。Windows ARM64 的容器测试装的就是这样的检出。拉取时会加上 `http.lowSpeedLimit=1000` 和 `http.lowSpeedTime=60`,`GIT_HTTP_LOW_SPEED_LIMIT` 和 `GIT_HTTP_LOW_SPEED_TIME` 可以覆盖它们。 +- `lib/ohos.lua` 等 gitcode.com 有两种方式。安装构建版本需要发布版本列表,所以尝试五次:间隔 10 秒,遇到 HTTP 429 之后是 30、60、120 和 180 秒。列出它们只是给 `vfox search` 帮个忙,所以 `ohos.list()` 只尝试两次,间隔 2 秒,遇到 HTTP 429 就停,并返回 `nil` 和原因。然后 `Available` 往 stderr 写一行警告,再列出官方版本。 +- 标签名里含有 `-ohos-` 或 `+ohos-`,就是一个 OpenHarmony 发布版本(`ohos.isOhosVersion`)。 +- `source.list` 只对不早于 `OLDEST_NATIVE` 的版本提供 `-arm64` 源码安装,那是 Flutter 工具在这台机器上第一个用原生 Dart SDK 运行的版本,见[刷新 Dart SDK 表](#刷新-dart-sdk-表)。 +- `EnvKeys` 只设置 `PATH`,不设置 `FLUTTER_ROOT`。 +- Lua 没有 sleep,所以 `lib/util.lua` 里的 `sleep` 是 `os.execute("sleep N")`,Windows 上是 `timeout /t N`。`hooks/available.lua` 和 `lib/ohos.lua` 里的重试靠它来等待。 + +## 名称与排序 + +`lib/util.lua` 里的 `splitVersionAndArch` 把 `3.47.4-x64` 拆成 `3.47.4` 和 `x64`,只有 `x64` 和 `arm64` 算后缀:`3.50.0-0.1.pre` 就没有后缀。`compare_versions` 忽略后缀,逐个比较版本里的数字。数字少的版本,排在在相同数字后面还有数字的版本之前,所以 `3.47.0` 在 `3.47.0-0.4.pre` 之前。 + +`Available` 按下面的规则依次排序: + +1. OpenHarmony 的名字排在所有其他名字之后(`ohos.isOhosVersion`)。 +2. 本机架构,以及没有后缀的名字,排在另一个架构之前。 +3. stable 渠道的条目(`note == "stable"`)排在其他条目之前,这样第一行 `@latest` 就是最新的稳定版,而不是数字更大的 beta。未发布:0.2.4 没有这条规则。 +4. `compare_versions`,新的在前。 +5. 本机架构排在另一个之前,然后是普通的字符串顺序。 +6. 渠道(`beta` 在 `dev` 之前),用于同一个名字在两个渠道里的情况。 + +规则 2 和 3 让 `@latest` 保持在本机架构和稳定版上。单元测试检查了这两点。 + +## 解析一个名称 + +`PreInstall` 把请求拆成名字和架构(`requestedArch`),然后: + +1. OpenHarmony 的名字交给 `ohos.checkout`,它不接受后缀。 +2. `beta` 和 `stable` 从索引读出 `current_release`,把它的提交作为 `channelKey`。索引里没有写明的渠道,查找以 `nil` 结束。`dev` 不再是渠道,所以它被当作版本名查找,哪里也找不到。 +3. 它遍历 `Available`,取第一个提交等于 `channelKey`、或者名字等于请求,并且架构是所请求的(或者本机的)那个条目。没有架构元数据的条目,对不带后缀的请求会被留作 `legacy`,在没有别的匹配时返回。 +4. 源码条目交给 `source.checkout`,其他的返回 `url` 和 `sha256`。 + +## 测试 + +hook 测试在 `gopher-lua` 下离线运行,它是 vfox 内嵌的 Lua 解释器: + +```bash +go run github.com/yuin/gopher-lua/cmd/glua@v1.1.1 tests/hooks_test.lua +``` + +在仓库根目录运行。测试替换了 `http`、`json` 和 `os.getenv`,并在一张表里放了一份小索引:共用一个提交的两种架构、一个 beta、一个 dev 版本,以及一个没有架构元数据的版本。它检查: + +- 每个变体都被列出,本机架构在前; +- 明确的后缀选中匹配的压缩包,不带后缀的名字保持本机架构; +- 另一个架构的版本更新时,`@latest` 也保持在本机架构上; +- 两个架构共用一个提交时,渠道依然选本机架构,`dev` 解析不到任何东西; +- 预发布版本的名字保持原样,后缀不改变顺序; +- 镜像同时提供索引和每个压缩包; +- 没有架构元数据的版本仍然可以安装,而且不会凭空多出变体; +- 缺少的架构绝不会退回到不兼容的压缩包; +- 拉取在传输卡住时放弃并重试; +- 安装官方版本永远不会访问 gitcode.com,而列出版本会访问一次; +- gitcode.com 限流时,列出版本不用等,只给一条警告,失败的请求会再试一次; +- 安装 OpenHarmony 构建版本时,会等过被限流的 gitcode.com(30、60、120 和 180 秒); +- 带 `+ohos-` 的 OpenHarmony 标签会被列出、排序并且可以安装,还会拒绝架构后缀; +- 稳定版排在预发布版本之前,所以第一行是最新的稳定版; +- 重新发布过的压缩包保留它最新的条目,不同的版本或渠道绝不会合并; +- ARM64 的源码安装从第一个能用原生 Dart SDK 运行的版本开始; +- 检出不会采用你 git 配置里的 `core.autocrlf`(这一项运行真正的 `git`,没有它就跳过); +- 不支持的 CPU 会在提示里点出名字。 + +清单和 `PreUse` 不在这个文件里,源码安装和 OpenHarmony 构建版本也只测到它们的 `git` 命令为止。这些由容器测试覆盖,见 [e2e.md](e2e.md)。lint、许可证头和容器测试见 [e2e.md](e2e.md#本地检查)。 + +要单独调用一个 hook,vfox 1.0.13 及更新的版本有 `vfox plugin run`。它不会注册插件,`--os` 和 `--arch` 可以替另一个平台作答: + +```bash +vfox plugin run . Available --os windows --arch arm64 --json +vfox plugin run . PreInstall --input '{"version":"stable"}' --os darwin --arch arm64 --json +``` + +两条命令都会访问真实的索引和 gitcode.com。源码安装(`--os linux --arch arm64`)会在它的工作目录里运行真实的 `git`,不管你自己在什么操作系统上。`vfox --debug <命令>` 会显示 vfox 对一个 hook 做了什么,这是发现被它吞掉的错误的办法。 + +### 不下载几个 GB 就试验改动 + +一个压缩包有 1.6 到 2.3 GB,所以试验解析方面的改动,用一个假镜像更省事:它是真实索引的副本,里面所有的压缩包都换成同一个很小的 SDK。这个 SDK 里有一个 `flutter/bin/flutter` 脚本和一个真正的 git 仓库,所以清单和漂移检查都是真的在运行。请在 Linux 上、仓库根目录里运行。它会把 `HOME` 和 `VFOX_HOME` 设成一个临时目录(vfox 需要这两个都设),所以你真正的 vfox 不会受影响。 + +```bash +work=$(mktemp -d) +python3 - "$work" <<'EOF' +import hashlib, io, json, os, subprocess, sys, tarfile, urllib.request + +work = sys.argv[1] +base = "https://storage.googleapis.com/flutter_infra_release/releases/" +index = json.load(urllib.request.urlopen(base + "releases_linux.json")) + +sdk = os.path.join(work, "sdk", "flutter") +os.makedirs(os.path.join(sdk, "bin")) +with open(os.path.join(sdk, "bin", "flutter"), "w") as f: + f.write('#!/bin/sh\necho "fake flutter in $(cd "$(dirname "$0")/.." && pwd)"\n') +os.chmod(os.path.join(sdk, "bin", "flutter"), 0o755) +git = ["git", "-C", sdk, "-c", "user.name=x", "-c", "user.email=x@x", "-c", "core.autocrlf=false"] +subprocess.run(["git", "init", "-q", sdk], check=True) +subprocess.run(git + ["add", "."], check=True) +subprocess.run(git + ["commit", "-q", "-m", "fake"], check=True) + +path = os.path.join(work, "flutter.tar.xz") +with tarfile.open(path, "w:xz") as tar: + tar.add(sdk, arcname="flutter") +sha = hashlib.sha256(open(path, "rb").read()).hexdigest() + +root = os.path.join(work, "mirror", "flutter_infra_release", "releases") +os.makedirs(os.path.join(root, "fake")) +os.rename(path, os.path.join(root, "fake", "flutter.tar.xz")) +for release in index["releases"]: + release["archive"], release["sha256"] = "fake/flutter.tar.xz", sha +with open(os.path.join(root, "releases_linux.json"), "w") as f: + json.dump(index, f) +EOF +``` + +临时目录里现在有了这个镜像。按 [install-from-source.md](install-from-source.md) 的办法构建插件: + +```bash +zip -qr "$work/flutter.zip" metadata.lua hooks lib +``` + +在一个你会一直开着的 Shell 里,把镜像放到后台提供服务,再对着它运行 vfox: + +```bash +python3 -m http.server 8000 --bind 127.0.0.1 --directory "$work/mirror" >/dev/null 2>&1 & +server=$! +export HOME="$work/home" VFOX_HOME="$work/vfox" FLUTTER_STORAGE_BASE_URL=http://127.0.0.1:8000 +mkdir -p "$HOME" "$VFOX_HOME" +vfox add flutter --source "$work/flutter.zip" +vfox search flutter +vfox install flutter@stable +eval "$(vfox activate bash)" +vfox use --global flutter@3.47.7 +flutter +``` + +用 `kill "$server"` 停掉服务,不要用带着它命令行的 `pkill -f`,那样会把你自己的 Shell 也匹配进去。要试验下一个改动,就在仓库里改插件,重复上面的 `zip` 那一行,再在重新 `vfox add` 之前执行 `vfox remove -y flutter`。`vfox search` 列出版本时,插件会向 gitcode.com 要 OpenHarmony 的发布版本,gitcode.com 无法访问时只会花两次尝试和一条警告。真实的索引在 2026-10-21 时 `stable` 是 `3.47.7`;以 `vfox install flutter@stable` 打印出的版本为准。 + +### 刷新 Dart SDK 表 + +`lib/source.lua` 里的 `OLDEST_NATIVE` 为每个 ARM64 平台记录了 Flutter 工具用原生 Dart SDK 运行的最老版本。更老的版本做源码安装,克隆没问题,但它的工具取的是 x64 的 Dart SDK,所以插件不给它们 `-arm64` 这个名字。一个版本要原生,得有两个条件:它的引导脚本会请求 ARM64 的 Dart SDK,Flutter 的存储里也有它。老版本不会变,所以这张表在有新平台加入时才需要看一眼,不用每个版本都看。要检查一个版本: + +```bash +release=2.0.6 +curl -fsSL "https://raw.githubusercontent.com/flutter/flutter/$release/bin/internal/update_dart_sdk.sh" | grep 'DART_ZIP_NAME="dart-sdk-linux' +curl -fsSL "https://raw.githubusercontent.com/flutter/flutter/$release/bin/internal/update_dart_sdk.ps1" | grep -c -i arm64 +engine=$(curl -fsSL "https://raw.githubusercontent.com/flutter/flutter/$release/bin/internal/engine.version") +for platform in linux windows; do + printf '%s ' "$platform" + curl -s -o /dev/null -I -w '%{http_code}\n' "https://storage.googleapis.com/flutter_infra_release/flutter/$engine/dart-sdk-$platform-arm64.zip" +done +``` + +第一条命令打印 Linux 脚本请求的 zip:到 `2.0.6` 为止是 `dart-sdk-linux-x64.zip`,从 `2.2.0` 起是 `dart-sdk-linux-${ARCH}.zip`,其中 `ARCH` 由 `uname -m` 决定。第二条数出 Windows 脚本里提到 ARM64 的行数:到 `3.19.6` 为止是 `0`,从 `3.20.0-1.1.pre` 起是 `6`。循环向存储询问这个引擎的 ARM64 Dart SDK,`200` 表示有,`404` 表示没有。2026-10-21 时,`2.0.6` 打印的是 `linux 200` 和 `windows 404`,`3.43.0-0.3.pre` 也一样,`3.44.0-0.1.pre` 两个都是 `200`。 + +表里 Linux 一行是脚本开始请求的位置(存储里从 `2.0.0` 起就有 SDK),Windows 一行是存储开始有的位置(脚本从 `3.20.0-1.1.pre` 起请求,没有时回退到 x64 的 SDK)。这张表是对 `releases_linux.json` 里 300 个 `"dart_sdk_arch": "x64"` 的版本(也就是会得到 `-arm64` 源码安装的那些)逐个做上面的检查得到的,对其中每一个都成立:283 个版本的 Linux 脚本按 CPU 选 zip,最老的 17 个不是;148 个版本的 Windows 脚本提到 ARM64,最老的 152 个没有。Windows 上有七个更老的 beta 在存储里有这个 SDK,它们和其余的一起被略去(列在 `lib/source.lua` 里)。`1.20.0`、`1.22.4`、`2.0.0` 和 `2.0.6` 的 Linux 脚本还被当作 ARM64 机器运行过,用的是一个说 `aarch64` 的 `uname` 和一个只打印它被要求下载的地址的 `curl`,它们请求的是 `dart-sdk-linux-x64.zip`;`2.2.0`、`2.5.0`、`3.0.0` 和 `3.24.0` 请求的是 `dart-sdk-linux-arm64.zip`。 + +## 给插件添加功能 + +- **新的命名或选择方式。** 改 `hooks/available.lua`(顺序)或 `hooks/pre_install.lua`(选择),在 `tests/hooks_test.lua` 里加一个用例,并更新 [versions.md](versions.md)、[channels.md](channels.md) 和 [arm64.md](arm64.md) 里描述它的地方。 +- **新平台。** `lib/util.lua` 里的 `getOsTypeAndArch` 列出了支持的 CPU。从 git 安装的平台,要把自己加进 `lib/source.lua` 的 `SOURCE_INSTALL_PLATFORMS` 和 `OLDEST_NATIVE`。更新 [platforms.md](platforms.md) 和 [arm64.md](arm64.md) 里的表,有运行器的话,还要把它加进容器测试([e2e.md](e2e.md))。 +- **新的构建来源**,像 OpenHarmony 那样:在 `lib/` 里加一个列出名字并负责检出的模块,在 `Available`、`PreInstall` 和 `PostInstall` 里加一个分支,在 `tests/e2e/common/common.go` 里加一个版本类别(`DefaultFlavours`、`DefaultOhosVersion`),再在 `docs/` 里加一页。 +- **某条提示。** 容器测试断言了漂移警告的一部分(`tests/e2e/linux/verify.go` 和 `tests/e2e/windows/verify.go`)。要把 Lua 和这两个文件一起改,还有 [flutter-upgrade.md](flutter-upgrade.md) 里的文字。 + +## 已知缺口 + +- 源码安装和 OpenHarmony 构建版本不做校验和验证。 +- 只列出 Flutter 1.17 及以后的版本。 +- `OLDEST_NATIVE` 是 2026-10-21 取得的数据,不会跟随 Flutter 的存储或脚本变化。它只管 Dart SDK:插件不检查老版本在 ARM64 上的其他方面。 +- 失败的源码安装会把它的工作目录留下。 +- `vfox search` 只会把它打印出的名字标记为已安装,见 [versions.md](versions.md#搜索)。 +- 没有架构元数据的版本(稳定版 `1.22.6` 及更早的,以及到 `2.10.0-0.3.pre` 为止的预发布版本)在每台机器上装的都是 x64 压缩包,包括 ARM64 的 Linux 和 Windows,在那里它不能原生运行。 +- OpenHarmony 分支会改它的标签名(`-ohos-`,2026-10-15 起是 `+ohos-`)。带别的标记的名字不会被列出。 + +## 发布 + +维护者在默认分支上执行 **Actions → Plugin → Run workflow**,填入不带 `v` 的版本号。共享工作流做了什么,见 [README](../../README_CN.md#发布这个插件)。之后 [vfox-plugins](https://github.com/version-fox/vfox-plugins) 里的 `plugins/flutter.json` 会显示 `vfox add flutter` 提供的版本,[changes.md](changes.md) 里也应当有这个版本的条目。 + +## 编写文档 + +英文页面在 `docs/`,中文页面在 `docs/zh-CN/`,文件名相同(中文的 README 是 `README_CN.md`),每页的开头都有指向另一种语言的链接。改了一边,两边都要改:中文页面是译文,其中的命令、路径,以及插件和 vfox 的提示信息都保持原样。README 是入口:每个主题几句话和一个链接。细节在 `docs/` 里,每个主题一页。引用外部数据(Flutter 的索引、gitcode.com、vfox)的页面,要写明取得数据的日期或 vfox 的版本。CI 不检查文档,所以你加的命令都要自己运行一遍。 + +某个平台的命令放在带平台名的标题下(`## Ubuntu 26.04.1`、`## Windows 11`),开头写 `在 Bash 中执行:` 或 `在 PowerShell 7 中执行:`(英文是 `Execute in Bash,` 或 `Execute in PowerShell 7,`),和现有的页面一样。示例安装的是 `3.47.4`,也就是容器测试的默认版本(`tests/e2e/common/common.go` 里的 `DefaultOfficialVersion`);它变了,就一起改。 + +只在 `main` 上、还没进入最新发布版的行为,要标上“未发布”(英文是 "Unreleased"),并在 [changes.md](changes.md) 的“未发布”下写一条。发布之后,把这些条目挪到新版本下面,并去掉标记,两种语言都要:`grep -rn -i -e unreleased -e 未发布 docs README*`。 diff --git a/docs/zh-CN/e2e.md b/docs/zh-CN/e2e.md new file mode 100644 index 0000000..4e14f84 --- /dev/null +++ b/docs/zh-CN/e2e.md @@ -0,0 +1,162 @@ +# E2E + +[中文](e2e.md) | [English](../e2e.md) + +离线的 Lua hook 测试(`tests/hooks_test.lua`)在 **Test Plugin** 工作流里运行。端到端套件在容器里对着真实的 vfox 运行,覆盖 Linux 和 Windows;每个 `vfox` × `flavor` × `mirror` 的组合都有自己的一次性容器。CI 在标准运行器上原生运行这两套测试(Docker,加上 `actions/setup-go` 提供的 Go)。在本地,仓库的开发容器可以复现 Linux 的运行;Windows 主机改用 Docker Compose 运行器镜像,因为开发容器无法承载 Windows 容器镜像(见下文)。 + +[platforms.md](platforms.md#测试覆盖的范围) 概括了这些套件覆盖什么,[development.md](development.md) 说明了插件如何工作。 + +## 开发容器 + +`.devcontainer/` 会打开一个 Linux 开发环境,四种主机平台都能用: + +| 主机 | 开发容器怎么运行 | +| --- | --- | +| Linux amd64 / arm64 | 原生 | +| Windows amd64 / arm64 | Docker Desktop 的 Linux 容器模式(WSL 2) | + +里面有 Go 1.26.8(CI 安装的版本)、Docker CLI、stylua、lua-language-server 和许可证工具,版本都固定为 `.github/workflows/lint.yml` 和 `license.yml` 里的那些,并且挂载了主机 Docker 守护进程的 socket。套件随后让 testcontainers 对着那个守护进程运行(Docker-out-of-Docker),启动的目标容器与原生 CI 运行器启动的相同;这里有意不用 Docker-in-Docker,所以镜像缓存和网络仍然是主机的。镜像不需要工作区里的任何文件,所以构建上下文只是 `.devcontainer/`;`postCreateCommand` 会为 `tests/e2e` 预热 Go 模块缓存,这样第一次运行测试时不会停下来下载。 + +用任何支持 Dev Containers 规范的工具都能打开它:在 VS Code/Cursor 里执行 **Dev Containers: Reopen in Container**,或者用命令行: + +```bash +devcontainer up --workspace-folder . +devcontainer exec --workspace-folder . bash +``` + +`TESTCONTAINERS_RYUK_DISABLED=true` 已经预先设好:Ryuk 会在主机守护进程上发布一个端口,并等套件回连它,而 Docker-out-of-Docker 容器做不到这一点。套件改为通过 `t.Cleanup` 显式终止每个容器;被杀掉的运行留下的容器都带 `vfox-flutter-e2e-` 这个名字前缀,可以据此清理。CI 设置了 `E2E_ASYNC_CLEANUP=true`,让清理在后台进行,而不是拖住每个组合,因为它的运行器本来就会被丢弃;最后几个容器因此可能比套件活得更久。使用 rootless Docker 的用户,在打开容器之前先在主机上执行 `export DOCKER_SOCK="$XDG_RUNTIME_DIR/docker.sock"`。在 Windows 主机上,把检出放在 WSL 2 里面(比如 `\\wsl$` 下)能让文件和 bind mount 的访问保持很快;放在 Windows 文件系统上也能用。 + +Windows *容器*的覆盖(`tests/e2e/windows`)无法在开发容器里运行:Dev Containers 规范不支持 Windows 容器镜像([vscode-remote-release#445](https://github.com/microsoft/vscode-remote-release/issues/445)),而 Windows 容器只能在 Windows 守护进程上运行。那个套件仍然在 Windows 主机上使用它的 Docker Compose 运行器,见下文的 [Windows 11 Pro](#windows-11-pro)。 + +## Linux (Ubuntu 26.04) + +在开发容器里,从仓库根目录执行: + +```bash +cd tests/e2e +go test -v -timeout 55m -run TestE2E ./linux +``` + +迭代时,用同样的办法缩小范围: + +```bash +VFOX_VERSION=latest FLAVOR=official MIRROR=default E2E_MAX_JOBS=1 go test -v -timeout 55m -run TestE2E ./linux +``` + +在原生主机上,同样的命令也能用,前提有两个:Docker Engine(rootful 或 rootless)和 Go 1.26.8,这正是 CI 准备的环境。使用 rootless Docker 的用户要把 Docker 客户端指向自己的 socket(`export DOCKER_HOST="unix://$XDG_RUNTIME_DIR/docker.sock"`);开发容器的 socket 挂载已经替你处理了这种情况。ARM64 上只运行 `official` flavor:OpenHarmony 没有发布 `linux-arm64` 的 Dart SDK(见 [ohos.md](ohos.md#限制)),所以 `ohos` flavor 在那里无法引导。 + +## Windows 11 Pro + +Windows 容器无法跨架构模拟,所以 ARM64 套件需要一台 ARM64 主机(Copilot+ PC,或者 CI 的 `windows-11-arm` 运行器)。 + +这个套件没有开发容器:Dev Containers 规范不接受 Windows 容器镜像(见[开发容器](#开发容器)),所以它保留了自己的 Docker Compose 运行器。运行器镜像(`Dockerfile-runner`:标准的 servercore 加上固定版本的 Go MSI)通过挂载进来的命名管道驱动主机守护进程;对主机的要求是 Windows 容器模式的 Docker,加上 Compose v5 插件(下面按每台机器装一次)。Go 从不接触主机,所以不会和 vfox 管理的工具链冲突。 + +CI 跳过这个运行器镜像(每次构建要花好几分钟),直接在运行器上用 `actions/setup-go` 提供的 Go 运行 `go test`,和 Linux 的作业一样。它还把引擎的数据根目录从运行器那块很慢的 C: 系统盘上挪走了,否则拉取镜像、提交层,以及容器里安装 Flutter,都会慢得像爬:在 `windows-latest` 上放到工作区旁边的本地 D: 盘,在 `windows-11-arm` 上放到它出厂时没有格式化的本地 NVMe 盘。这样一来,那里的 `E2E_MAX_JOBS` 是 4。两个套件都先启动最慢的组合(CN 镜像,然后是官方 SDK),所以最后启动的几个都很短。解包 servercore 基础镜像仍然是最长的准备步骤,所以 CI 在设置 Go 和编译套件之前,就先在后台开始拉取它。 + +目标镜像(`tests/e2e/windows/Dockerfile`)是从便携的 zip 解包 PowerShell 7 和 MinGit,而不是运行它们的安装程序:Git for Windows 的安装程序是 x86 程序,arm64 容器只能在模拟下运行它。MinGit 是同一个 `git.exe`,只是没有 bash、Perl 和凭据管理器,而 vfox 和 Flutter 都不会调用它们。 + +`main` 的几个组合共用一个 `vfox.exe`:镜像构建的同时,套件用自己的 Go 工具链从 vfox 的 main 分支构建它,并在容器启动之前把它复制进每个 `main` 容器。 + +`windows-11-arm` 运行器没有 Windows Hypervisor Platform 功能,所以 Hyper-V 隔离不可用;进程隔离只需要 Containers 功能。 + +每个组合运行时,`VFOX_HOME` 都指向一个含空格的路径(`C:\Users\John Doe\.vfox`),对应 [vfox#723](https://github.com/version-fox/vfox/issues/723)。vfox 从 1.0.13 起能处理这样的路径([vfox#712](https://github.com/version-fox/vfox/pull/712)),所以 `latest` 和 `main` 都带着这个修复。 + +### x64 + +1. 在 Windows PowerShell 5.1 中执行: + +```powershell +winget install --id Microsoft.PowerShell --source winget --exact +``` + +2. 在 PowerShell 7 中执行: + +```powershell +winget install --id Git.Git --source winget --exact +# 按需配置 Git。 + +curl.exe -fsSL ` + "https://raw.githubusercontent.com/microsoft/Windows-Containers/Main/helpful_tools/Install-DockerCE/install-docker-ce.ps1" ` + -o ./install-docker-ce.ps1 +``` + +3. 在以管理员身份启动的 PowerShell 7 中执行: + +```powershell +./install-docker-ce.ps1 +``` + +4. 在 PowerShell 7 中执行: + +```powershell +New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.docker\cli-plugins" | Out-Null + +curl.exe -fsSL ` + "https://github.com/docker/compose/releases/download/v5.5.1/docker-compose-windows-x86_64.exe" ` + -o "$env:USERPROFILE\.docker\cli-plugins\docker-compose.exe" + +git clone git@github.com:version-fox/vfox-flutter.git +cd ./vfox-flutter/ +docker compose -f tests/e2e/windows/compose.amd64.yaml run --rm --build e2e +``` + +### arm64 + +1. 在 Windows PowerShell 5.1 中执行: + +```powershell +winget install --id Microsoft.PowerShell --source winget --exact +``` + +2. 在 PowerShell 7 中执行: + +```powershell +winget install --id Git.Git --source winget --exact +# 按需配置 Git。 +winget install --id GoLang.Go --source winget --exact +git clone git@github.com:version-fox/vfox-flutter.git +cd ./vfox-flutter/ +pwsh -NoProfile -File .\tests\e2e\windows\install-docker-arm64.ps1 +New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.docker\cli-plugins" | Out-Null + +curl.exe -fsSL ` + "https://github.com/docker/compose/releases/download/v5.5.1/docker-compose-windows-aarch64.exe" ` + -o "$env:USERPROFILE\.docker\cli-plugins\docker-compose.exe" + +docker compose -f tests/e2e/windows/compose.arm64.yaml run --rm --build e2e +``` + +在 arm64 上,构建引擎用的也是 Go,所以 `install-docker-arm64.ps1` 要在运行套件之前先运行;之后 compose 的运行器镜像会带着它自己固定版本的 Go 来跑测试,主机上的 Go 不会再用到。这个脚本把引擎的二进制放在 `C:\vfox-docker\bin`,之后的运行只要它们与脚本固定的版本一致就会复用(CI 缓存的就是这个目录);`-DataRoot` 可以把引擎的镜像和容器从默认的 `C:\vfox-docker\data` 挪到别处。要缩小运行范围,在 compose 命令前面加上 `$env:VFOX_VERSION = 'latest'; $env:FLAVOR = 'official'`,和其他平台一样。 + +## 本地检查 + +在开发容器里执行(下面所有工具都已预装,并且固定为工作流使用的版本),或者在主机上准备同样的工具。 + +```bash +# Lua 和 Go 的检查(.github/workflows/lint.yml)。 +cd tests/e2e +go test -short ./... +test -z "$(gofmt -l .)" +go vet ./... +cd ../.. + +stylua --check . +lua-language-server --check . --checklevel=Warning +``` + +```bash +# 许可证头(.github/workflows/license.yml)。 +license-eye header check +license-eye header fix +addlicense -check -f .license.tpl tests/e2e/windows/*.ps1 +addlicense -f .license.tpl tests/e2e/windows/*.ps1 +``` + +在只有 Docker 的主机上,许可证检查改为以容器方式运行(和 CI 用的是同样的镜像): + +```bash +docker run --rm -v "$PWD:/work" -w /work apache/skywalking-eyes:0.9.0 header check +docker run --rm -v "$PWD:/work" -w /work apache/skywalking-eyes:0.9.0 header fix +docker run --rm -v "$PWD:/src" ghcr.io/google/addlicense:v1.2.0 -check -f .license.tpl tests/e2e/windows/*.ps1 +docker run --rm -v "$PWD:/src" ghcr.io/google/addlicense:v1.2.0 -f .license.tpl tests/e2e/windows/*.ps1 +``` diff --git a/docs/zh-CN/flutter-upgrade.md b/docs/zh-CN/flutter-upgrade.md new file mode 100644 index 0000000..c2c97a7 --- /dev/null +++ b/docs/zh-CN/flutter-upgrade.md @@ -0,0 +1,96 @@ +# 关于 `flutter upgrade` + +[中文](flutter-upgrade.md) | [English](../flutter-upgrade.md) + +这个插件安装的 Flutter 发布压缩包里带有完整的 `.git` 检出,所以 `flutter upgrade`(以及它的同类 `flutter downgrade`、`flutter channel`,还有在 SDK 目录里执行的任何 `git checkout` / `git pull`)可以就地把 SDK 的 HEAD 移到更新的提交。vfox 是按目录管理 SDK 版本的,不会跟踪这种就地的改动,所以下一次 `vfox use flutter@3.47.0` 会悄悄给你一个已经漂移到别处的 SDK,而不是 3.47.0。 + +不要在 vfox 管理的 Flutter SDK 里运行 `flutter upgrade`。要换版本,通过 vfox: + +```bash +vfox install flutter@3.47.4 +vfox use --global flutter@3.47.4 +``` + +源码安装(Linux ARM64、Windows ARM64、OpenHarmony 构建版本)也是 git 检出,同样适用。 + +## 恢复已漂移的 SDK + +如果你已经就地升级过了,这个插件会在每次 `vfox use` 那个版本时打印一条警告,并给出恢复它的命令: + +```bash +vfox uninstall flutter@3.47.0 +vfox install flutter@3.47.0 +vfox use flutter@3.47.0 +``` + +## 警告内容 + +SDK 的 HEAD 不再是 vfox 安装时的那个提交之后,`vfox use flutter@3.47.4` 打印的就是这样: + +``` +Warning: flutter SDK at ~/.vfox/cache/flutter/v-3.47.4 has drifted from the version vfox installed. + vfox installed: flutter@3.47.4 (git HEAD 9584c6713b324636289d067944a46fd6b49df14b) + current git HEAD: abaf9c523780a608bd46686fd5e53740a07077f8 + reason: the SDK's git HEAD no longer matches the commit vfox installed + +This typically happens when `flutter upgrade` or a similar in-place command +is run inside a vfox-managed SDK. vfox manages SDK versions by directory and +does not track in-place mutations, so the version you get may differ from +the one you asked for. + +To restore flutter@3.47.4: + vfox uninstall flutter@3.47.4 + vfox install flutter@3.47.4 + vfox use flutter@3.47.4 + +To upgrade to a newer version, use vfox directly: + vfox install flutter@ + vfox use flutter@ +``` + +原因有两种,`reason:` 后面的那行说明是哪一种: + +| 原因 | 含义 | +| --- | --- | +| `the SDK's git HEAD no longer matches the commit vfox installed` | 提交变了。`flutter upgrade`、`flutter downgrade`、`flutter channel` 和 `git checkout` 都会这样 | +| `vfox can no longer read the SDK's git HEAD (its .git may be broken)` | `git` 在 SDK 里执行失败:`.git` 目录损坏了,或者 `git` 已经不在了 | + +第一行里的路径,就是 `vfox info flutter@3.47.4` 打印的目录。SDK 是它里面的 `flutter-3.47.4` 目录。 + +## 检查如何进行 + +安装时,插件会在 SDK 的 `.git` 旁边写一个 `.vfox-manifest` 文件,记录预期的 git HEAD,之后每次 `vfox use` 都会重新检查。 + +清单是 SDK 根目录里的一个小 JSON 文件,`flutter-3.47.4/.vfox-manifest`: + +```json +{"expected_head":"9584c6713b324636289d067944a46fd6b49df14b","installed_at":1792523751,"plugin":"vfox-flutter","plugin_version":"0.2.4","version":"3.47.4"} +``` + +`expected_head` 是 vfox 刚解压完 SDK 时 `git rev-parse HEAD` 打印的结果,`installed_at` 是安装时间,单位是自 1970 年以来的秒数。`vfox use` 时检查会再执行一次 `git rev-parse HEAD`,并比较两者。压缩包安装、源码安装和 OpenHarmony 构建版本都会有清单。在 SDK 里执行 `git status`,这个文件会显示为未跟踪。 + +警告只是警告:`vfox use` 仍然会选中你要的版本,退出码是 0。没有 `.git` 目录的 SDK 不检查。 + +没有可比较的东西时就什么都不检查:没有清单的 SDK(由早于 0.2.3 的插件安装的)、清单里没有 `expected_head` 的(安装时没有 `git`),以及 vfox 不认识的版本,都不会有任何提示。 + +## 关闭检查 + +删掉受影响那份安装里的 `.vfox-manifest` 文件。这个文件是真正的安装才会写出来的,所以只有卸载再重新安装那个版本,它才会回来;单独执行 `vfox install` 会跳过已经安装的版本,文件依然不存在。 + +`vfox info --format '{{.Path}}' flutter@3.47.0` 打印的是 SDK 上一层的目录,所以清单在更下一层的 `flutter-3.47.0` 里。这个目录以已安装的名字命名,如果你安装时带了架构后缀,它也会带(`flutter-3.47.0-x64`)。 + +### Ubuntu 26.04.1 + +在 Bash 中执行: + +```bash +rm "$(vfox info --format '{{.Path}}' flutter@3.47.0)/flutter-3.47.0/.vfox-manifest" +``` + +### Windows 11 + +这里假设已经装好了 PowerShell 7,在 PowerShell 7 中执行: + +```powershell +Remove-Item "$(vfox info --format '{{.Path}}' flutter@3.47.0)/flutter-3.47.0/.vfox-manifest" +``` diff --git a/docs/zh-CN/install-from-source.md b/docs/zh-CN/install-from-source.md new file mode 100644 index 0000000..9ca717a --- /dev/null +++ b/docs/zh-CN/install-from-source.md @@ -0,0 +1,80 @@ +# 从源码安装 + +[中文](install-from-source.md) | [English](../install-from-source.md) + +`vfox add flutter` 安装的是最新的发布版。想在下一个版本发布之前用上 `main`,或者试用你正在改的代码,就从源码构建插件。插件就是一个 `metadata.lua` 加上 `hooks` 和 `lib` 两个目录,vfox 可以从它们的 zip 包添加插件。 + +## Ubuntu 26.04.1 + +在 Bash 中执行: + +```bash +sudo apt install --assume-yes git zip +git clone --depth 1 https://github.com/version-fox/vfox-flutter.git +cd ./vfox-flutter/ +zip -qr ./flutter.zip metadata.lua hooks lib +vfox add flutter --source ./flutter.zip +rm ./flutter.zip +``` + +可以用下面的命令验证: + +```bash +vfox install flutter@3.47.4 +vfox use --global flutter@3.47.4 +flutter --version --no-version-check +dart --version +``` + +macOS 的步骤相同,用你机器上已有的 `git` 和 `zip` 即可。 + +## Windows 11 + +这里假设已经装好了 PowerShell 7,在 PowerShell 7 中执行: + +```powershell +winget install --id Git.Git --source winget --exact +git clone --depth 1 https://github.com/version-fox/vfox-flutter.git +cd ./vfox-flutter/ +tar -a -cf ./flutter.zip metadata.lua hooks lib +vfox add flutter --source ./flutter.zip +Remove-Item ./flutter.zip +``` + +可以用下面的命令验证: + +```powershell +vfox install flutter@3.47.4 +vfox use --global flutter@3.47.4 +flutter --version --no-version-check +dart --version +``` + +## 已经添加过 flutter 插件时 + +`vfox add` 会拒绝,并报 `plugin named flutter already exists`。你可以替换原有的插件,也可以把源码构建的版本另起个名字,与原插件并存。 + +替换要先执行 `vfox remove flutter`,它会连同通过这个插件装的 Flutter SDK 一起删掉,vfox 会在确认之前先说 `Removing this plugin will remove the installed sdk along with the plugin.`。之后需要重新安装这些 SDK。光是一个压缩包就有 1.6 到 2.3 GB,所以这是代价最高的办法。 + +想两个都留着,就给新的起个别名: + +```bash +vfox add flutter --source ./flutter.zip --alias flutter-dev +vfox install flutter-dev@3.47.4 +vfox remove -y flutter-dev # 只删除 flutter-dev 和它装的东西 +``` + +别名有它自己的已安装列表,它的下载也在 vfox 主目录的 `cache/flutter-dev` 下。`vfox use` 建的链接对两者都是 `~/.vfox/sdks/flutter`,进入 `PATH` 的 `bin` 目录也是同一个。在每个作用域里,只从其中一个选版本:全局作用域下,先执行 `vfox unuse --global flutter`,再 `vfox use --global flutter-dev@...`,反过来也一样。 + +## 更新 + +拉取代码,重新打 zip 包,再添加一次。以原名添加的插件必须先删除,这会连已安装的 Flutter 一起删掉,所以经常更新时用别名更省事: + +```bash +vfox remove -y flutter-dev +vfox add flutter --source ./flutter.zip --alias flutter-dev +``` + +`vfox update flutter` 会拿 `metadata.lua` 里的版本号和插件注册表里的比较,两者相同时只会打印 `flutter is already the latest version`,所以版本号和发布版相同的改动,它不会替你装上。要装上这样的改动,请再添加一次源码构建的版本。 + +开发插件请看 [development.md](development.md)。 diff --git a/docs/zh-CN/mirror.md b/docs/zh-CN/mirror.md new file mode 100644 index 0000000..54353af --- /dev/null +++ b/docs/zh-CN/mirror.md @@ -0,0 +1,147 @@ +# 镜像 + +[中文](mirror.md) | [English](../mirror.md) + +默认情况下,Flutter SDK 从 `https://storage.googleapis.com` 下载。如果访问它有困难,可以设置环境变量 `FLUTTER_STORAGE_BASE_URL` 来使用镜像。 + +常见的镜像: + +| 镜像 | 地址 | +|------|------| +| CFUG(中国 Flutter 用户组) | `https://storage.flutter-io.cn` | +| CERNET(中国教育和科研计算机网) | `https://mirrors.cernet.edu.cn/flutter` | + +最新的镜像列表见 MirrorZ Help 站点:https://help.mirrors.cernet.edu.cn/flutter/ 。 + +## 这个变量的作用 + +`FLUTTER_STORAGE_BASE_URL` 是 Flutter 自己的变量,有三个地方用到它: + +- **插件从它读取发布索引**:`<镜像>/flutter_infra_release/releases/releases_linux.json`(macOS 和 Windows 上是 `releases_macos.json` 和 `releases_windows.json`)。`vfox search flutter` 列出什么、渠道指向哪个版本,都由这份索引决定,见 [channels.md](channels.md)。 +- **压缩包从它下载。** 插件用索引里的 `archive` 字段拼出 vfox 要下载的地址 `<镜像>/flutter_infra_release/releases/`,不用索引自己带的 `base_url`。校验和则是同一份索引里的那个。 +- **`flutter` 工具自己也读它。** 工具自己发起的下载,比如源码安装的 Dart SDK(见 [arm64.md](arm64.md))和引擎产物,都会走它。所以它要一直保持在你的 Shell 里,而不只是在执行 `vfox install` 的时候。 + +末尾的斜杠会被去掉,所以 `https://storage.flutter-io.cn/` 也可以用,空值等同于没设置。镜像必须保持 Google 在 `flutter_infra_release/` 下的目录结构,并且能同时提供索引和压缩包。镜像里的索引就是你能安装的版本的列表:它还没同步的版本就看不到,`stable` 指向哪里也以它那份为准。2026-10-21 时 Google、CFUG 和 CERNET 提供的索引是一样的。 + +使用镜像之前,可以先检查一下它能否提供你所在系统的索引: + +```bash +curl --fail --silent --show-error --location --output /dev/null \ + https://storage.flutter-io.cn/flutter_infra_release/releases/releases_linux.json +``` + +测试套件在通过 `https://storage.flutter-io.cn` 安装之前,也会先发同样的请求。 + +## 哪种安装用哪个镜像 + +| 安装方式 | 发布索引 | 下载 | +| --- | --- | --- | +| 压缩包:Linux x64、macOS、Windows x64,以及各处的 `-x64` | `FLUTTER_STORAGE_BASE_URL` | `FLUTTER_STORAGE_BASE_URL` | +| 源码:Linux ARM64 和 Windows ARM64 | `FLUTTER_STORAGE_BASE_URL` | GitHub,用 `VFOX_FLUTTER_GITHUB_MIRROR` | +| OpenHarmony | gitcode.com,没有镜像 | gitcode.com,没有镜像 | + +安装 OpenHarmony 构建版本完全不读发布索引,但 `vfox search flutter` 会读,因为它要把官方版本和它们一起列出来。`PUB_HOSTED_URL` 是 `flutter pub get` 拉取包的地址,插件不读它。如果你用 CFUG 的镜像,Flutter 的[中国用户指南](https://docs.flutter.cn/community/china)里,除了 `FLUTTER_STORAGE_BASE_URL`,还把它设成了 `https://pub.flutter-io.cn`。 + +## Ubuntu 26.04.1 + +在 Bash 中: + +1. 用下面的命令可以让设置在当前 Shell 里临时生效。 + +```bash +export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn +``` + +2. 要让环境变量一直生效,可以这样做: + +```bash +sudo tee /etc/profile.d/myenvvars.sh <> ~/.zshrc +``` + +## Windows 11 + +在 PowerShell 7 中: + +1. 用下面的命令可以让设置在当前 Shell 里临时生效。 + +```powershell +$env:FLUTTER_STORAGE_BASE_URL = "https://storage.flutter-io.cn" +``` + +2. 要让环境变量一直生效,可以这样做: + +```powershell +[Environment]::SetEnvironmentVariable('FLUTTER_STORAGE_BASE_URL', 'https://storage.flutter-io.cn', 'Machine') +``` + +`Machine` 作用域需要以管理员身份启动的 PowerShell,对之后启动的程序生效。改用 `'User'` 则只对你的账户生效。 + +## 源码安装用的 GitHub 镜像 + +在 Linux ARM64 和 Windows ARM64 上,插件是从 git 源码而不是预编译的压缩包安装 Flutter 的。如果访问 `https://github.com` 有困难,可以设置环境变量 `VFOX_FLUTTER_GITHUB_MIRROR` 来改用镜像。它的值是一个 URL 前缀,会加在 GitHub 的地址前面。 + +常见的镜像: + +| 镜像 | 值 | +|------|----| +| gh-proxy | `https://gh-proxy.org/` | + +在 Bash 中: + +1. 用下面的命令可以让设置在当前 Shell 里临时生效。 + +```bash +export VFOX_FLUTTER_GITHUB_MIRROR=https://gh-proxy.org/ +``` + +2. 要让环境变量一直生效,可以这样做: + +```bash +sudo tee /etc/profile.d/myenvvars.sh <` 失败,见 [troubleshooting.md](troubleshooting.md#源码安装linux-arm64-和-windows-arm64)。 + +## 代理 + +代理不是镜像。vfox 自己的代理设置对插件的请求和压缩包的下载都有效,`git` 读的是它自己的设置(`HTTPS_PROXY`、`http.proxy`),见 [troubleshooting.md](troubleshooting.md#网络)。 diff --git a/docs/zh-CN/ohos.md b/docs/zh-CN/ohos.md new file mode 100644 index 0000000..97aa6f9 --- /dev/null +++ b/docs/zh-CN/ohos.md @@ -0,0 +1,75 @@ +# OpenHarmony 构建版本 + +[中文](ohos.md) | [English](../ohos.md) + +OpenHarmony 构建版本来自一个面向 OpenHarmony 的第三方 Flutter 分支。插件会把 gitcode.com 上 [CPF-Flutter/flutter_flutter](https://gitcode.com/CPF-Flutter/flutter_flutter/releases) 的发布版本和官方版本一起列出来,并以源码检出的方式安装它们。 + +## 前提条件 + +需要 `git`: + +1. Ubuntu 26.04.1 上执行 `sudo apt install --assume-yes git` +2. Windows 11 上执行 `winget install --id Git.Git --source winget --exact` + +## 使用方法 + +gitcode.com 上的 [OpenHarmony 版 Flutter 发布版本](https://gitcode.com/CPF-Flutter/flutter_flutter/releases)以源码检出的方式安装: + +```bash +vfox install flutter@3.41.10-ohos-1.0.0 +vfox use --global flutter@3.41.10-ohos-1.0.0 +``` + +3.41、3.35 和 3.27 这几条线上最新的版本分别是 `3.41.10-ohos-1.0.0`、`3.35.8-ohos-1.0.3` 和 `3.27.5-ohos-1.0.7`,全部版本见[名称与发布版本](#名称与发布版本)。要看当前有哪些,执行: + +```bash +vfox search flutter +``` + +## 名称与发布版本 + +名称由一个版本号、`-ohos-` 或 `+ohos-`,以及这个分支自己的版本号组成:`3.41.10-ohos-1.0.0` 是这个分支 3.41.10 线上的 1.0.0 版。`-ohos-` 名称里的第一个数字是这个分支自己的标记,并不总是对应某个 Flutter 版本:Flutter 没有 3.41.10、3.35.8、3.27.5 或 3.22.4,`3.41.10-ohos-1.0.0` 背后的分支叫 `oh-3.41.9-release`。这个分支 2026-10-15 起的新标签改用官方版本的版本号,后面跟 `+ohos-`(`3.41.9+ohos-1.0.2`)。2026-10-21 时插件列出了下面这 25 个发布版本,每条线内部新的在前: + +| 开头 | 发布版本(`-ohos-` 或 `+ohos-` 之后的部分) | +| --- | --- | +| 3.41.10-ohos- | `1.0.0`、`0.0.1-canary1` | +| 3.41.9+ohos- | `1.0.2` | +| 3.35.8-ohos- | `1.0.3`、`1.0.1`、`0.0.3` | +| 3.35.7+ohos- | `1.0.5` | +| 3.27.5-ohos- | `1.0.7`、`1.0.6`、`1.0.5`、`1.0.3`、`1.0.0` | +| 3.27.4+ohos- | `1.0.9` | +| 3.22.4-ohos- | `1.1.4`、`1.1.3` | +| 3.22.3+ohos- | `1.1.6` | +| 3.22.1-ohos- | `1.1.1`、`1.0.9`、`1.0.1`、`0.1.2`、`0.1.1` | +| 3.7.12-ohos- | `1.1.7`、`1.1.1`、`1.0.7`、`1.0.6` | + +`vfox search flutter` 把它们排在最后,在所有官方版本之后,每个显示为 `3.41.10-ohos-1.0.0 (OpenHarmony)`,没有 Dart 版本。因为这个顺序,`vfox install flutter@latest` 永远不会选中其中一个。 + +插件靠标签名里有 `-ohos-` 或 `+ohos-` 来认出一个发布版本。未发布:0.2.4 只认 `-ohos-`,列出的是上表里带它的 21 个,`vfox install flutter@3.41.9+ohos-1.0.2` 会以 `no installable runtime provided` 结束。 + +## 安装如何进行 + +1. 插件向 `https://gitcode.com/api/v5/repos/CPF-Flutter/flutter_flutter/releases` 请求发布版本列表,最多五次,再按标签名找到对应的发布版本。它的 `target_commitish` 就是要安装的提交。 +2. 在 `$VFOX_HOME` 下的 `.vfox/tmp/<名称>` 里(没设置 `VFOX_HOME` 就在你的主目录下,即 `~/.vfox/tmp/3.41.10-ohos-1.0.0`)执行 `git init`,然后 `git fetch --depth 1 https://gitcode.com/CPF-Flutter/flutter_flutter.git <提交>`,最多三次,再执行 `git checkout FETCH_HEAD`。 +3. 检查 `bin/internal/engine.version` 是否存在,然后把这个目录交给 vfox,由它移动到安装位置。 + +得到的是只含一个提交的浅克隆,3.41.10-ohos-1.0.0 是 224 MB,网速很快时安装用了 19 秒。安装会等待 gitcode.com:它的 API 回答 HTTP 429(被限流)时,插件在五次尝试之间依次等 30、60、120 和 180 秒,遇到其他错误则每次间隔十秒。 + +`vfox search flutter` 向同一个 API 取发布版本的列表,但不会等它:最多两次,间隔两秒,遇到 HTTP 429 就停。API 没有响应时,列表里没有 OpenHarmony 的名字,并在 stderr 上说明这一点。安装官方版本完全不会访问 gitcode.com。未发布:0.2.4 对每个列出版本的命令,以及每次官方版本的安装,都会用同样的五次尝试去问它。 + +## 限制 + +- **只能按准确的版本号安装。** OpenHarmony 版本排在官方版本之后,所以 `@latest` 不会选中其中任何一个。`stable` 和 `beta` 渠道(见 [channels.md](channels.md))通过 Flutter 官方的发布索引解析,而那里没有 OpenHarmony 构建版本。 +- **没有架构变体。** `flutter@3.41.10-ohos-1.0.0-x64` 不支持,会失败并报 `flutter 3.41.10-ohos-1.0.0 has no architecture variants`。 +- **只认上面这些名字。** `flutter@3.47.7-ohos-1.0.0` 会失败并报 `flutter 3.47.7-ohos-1.0.0 is not an OpenHarmony release`。 +- **`linux-arm64` 和 `windows-arm64`。** 上游只发布了 x64 的 Dart SDK,所以第一次运行 `flutter` 会在那里失败(`It appears that the downloaded file is corrupt`)。ARM64 的 e2e 只跑官方版本。 +- **第一次运行 `flutter` 命令很慢。** 它要构建工具,并从华为的 OBS 下载 Dart SDK,所以需要网络。 +- **不做校验和验证。** git 的历史就是完整性检查,而且这是个第三方分支,信任它之前请先审查它的源码。 +- **镜像不起作用。** `FLUTTER_STORAGE_BASE_URL` 和 `VFOX_FLUTTER_GITHUB_MIRROR`(见 [mirror.md](mirror.md))只影响官方 SDK 的下载;OpenHarmony 构建版本是从 gitcode 克隆的,所以这两个变量都会被忽略。 +- **卡住的克隆会重试。** 从 gitcode 拉取时如果卡住(一分钟内速度低于 1 KB/s),会被放弃并重试,最多三次;`GIT_HTTP_LOW_SPEED_LIMIT` / `GIT_HTTP_LOW_SPEED_TIME` 可以改这个阈值。 +- **gitcode.com 会对它的 API 限流。** 安装 OpenHarmony 构建版本时会等它,见上文。另见 [troubleshooting.md](troubleshooting.md#gitcodecom-被限流)。 +- **换行符。** 检出时会设置 `core.autocrlf=false`。未发布:0.2.4 用的是你自己的 git 配置,设成 `core.autocrlf=true` 时,Linux 上的检出里 `bin/internal/shared.sh` 是 CRLF 换行,`flutter` 会停在 `$'\r': command not found`,见 [arm64.md](arm64.md#限制)。 + +### 构建 OpenHarmony 应用 + +还需要 DevEco Studio 和 OpenHarmony SDK,vfox 不会安装它们。 diff --git a/docs/zh-CN/platforms.md b/docs/zh-CN/platforms.md new file mode 100644 index 0000000..f98c8b2 --- /dev/null +++ b/docs/zh-CN/platforms.md @@ -0,0 +1,50 @@ +# 平台支持 + +[中文](platforms.md) | [English](../platforms.md) + +插件可以在 x64 和 ARM64 的 Linux、macOS 和 Windows 上安装 Flutter。操作系统和 CPU 架构取自运行插件的 vfox 二进制(`osType` 和 `archType` 就是 Go 的 `GOOS` 和 `GOARCH`):`darwin` 变成 `macos`,`amd64` 变成 `x64`,`arm64` 保持不变。其他 CPU 会报一条点出该 CPU 的提示,见 [troubleshooting.md](troubleshooting.md#flutter-does-not-support-the-386-architecture)。 + +| 机器 | 安装来源 | 发布索引 | +| --- | --- | --- | +| Linux x64 | Google 发布的 `.tar.xz` 压缩包 | `releases_linux.json` | +| Linux ARM64 | `flutter/flutter` 的 git 标签,见 [arm64.md](arm64.md#linux-arm64) | `releases_linux.json` | +| macOS Apple 芯片 | `.zip` 压缩包,arm64 和 x64 | `releases_macos.json` | +| macOS Intel | `.zip` 压缩包,x64 和 arm64 | `releases_macos.json` | +| Windows x64 | `.zip` 压缩包 | `releases_windows.json` | +| Windows ARM64 | `flutter/flutter` 的 git 标签,见 [arm64.md](arm64.md#windows-arm64) | `releases_windows.json` | + +OpenHarmony 构建版本在每种机器上都是从 gitcode.com 检出的,见 [ohos.md](ohos.md)。发布索引读自 `https://storage.googleapis.com/flutter_infra_release/releases/`,或者 `FLUTTER_STORAGE_BASE_URL` 指定的镜像,见 [mirror.md](mirror.md)。 + +`3.47.4`、`3.47.4-x64` 和 `3.47.4-arm64` 在各种机器上分别得到什么,见 [arm64.md](arm64.md#各种机器分别得到什么)。 + +## 前提条件 + +- **vfox**,并且已经挂载到 Shell,这样 `vfox use` 之后 `flutter` 才在 `PATH` 里,见 vfox 的[快速入门](https://vfox.dev/zh-hans/guides/quick-start.html)。插件声明 vfox 0.3.0 是它能加载的最老版本。测试跑的是最新的 vfox 发布版和 vfox 的 `main`;主目录里带空格的 Windows 账户需要 vfox 1.0.13 或更新的版本([vfox#712](https://github.com/version-fox/vfox/pull/712))。 +- **`git`**:Linux ARM64 和 Windows ARM64 的每次安装、OpenHarmony 构建版本,以及 [flutter-upgrade.md](flutter-upgrade.md) 里的漂移检查都要用。漂移检查在安装时向 `git` 要 SDK 的提交;没有 `git` 时安装依然会成功,写出的 `.vfox-manifest` 里没有提交,`vfox use` 永远不会警告。Flutter 本身也需要 `git`。 +- **Flutter 自己的工具。** Android、iOS、Linux、Windows 和 Web 开发各有各的要求(Android SDK、Xcode、Visual Studio、GTK 等等),vfox 不会安装它们,见 [Flutter 的安装指南](https://docs.flutter.cn/install)。[README](../../README_CN.md#安装) 里的示例为 Ubuntu 装了 `curl git unzip xz-utils zip libglu1-mesa`。 +- **磁盘空间。** 压缩包 1.6 到 2.3 GB(见 [versions.md](versions.md#安装)),vfox 解压后会把它删掉。Flutter 工具会为你要构建的平台下载所需的内容,SDK 会随之变大。源码安装还要给检出留出空间。你安装的每个名字都是一个独立的 SDK,见 [versions.md](versions.md#已安装的名称)。 + +## 插件会连接的地址 + +| 主机 | 用来做什么 | 什么时候 | +| --- | --- | --- | +| `storage.googleapis.com`,或你的镜像 | 发布索引、压缩包,以及 `flutter` 工具自己下载的东西,比如源码安装的 Dart SDK 和引擎产物 | 总是 | +| `gitcode.com` | OpenHarmony 版本的列表(一个 API 请求),以及 OpenHarmony 构建版本的检出 | `vfox search` 或 `vfox install` 的 `latest` 列出版本时要列表,安装 OpenHarmony 构建版本时也要;检出只在 OpenHarmony 构建版本时 | +| `github.com` | `flutter/flutter` 的检出 | Linux ARM64 和 Windows ARM64 的安装 | + +`pub.dev` 不是插件要连的,但 Flutter 工具启动时会连它,测试套件也会检查它能否访问。安装官方版本不需要 `gitcode.com`,防火墙挡住它,也只是让 `vfox search` 快速试两次并给出一条警告,见 [troubleshooting.md](troubleshooting.md#gitcodecom-被限流)。未发布:0.2.4 每次安装也要问 `gitcode.com`,并且会等它。 + +## 测试覆盖的范围 + +Lua 的 hook 测试(`tests/hooks_test.lua`)对每个 pull request 离线运行。它们对着一份发布索引的样本,模拟 x64 和 arm64 上的 Linux、macOS 和 Windows。 + +容器测试套件用真实的 vfox 安装真实的 Flutter,每种组合各用一个一次性容器,见 [e2e.md](e2e.md): + +| 机器 | 容器 | Flutter | vfox | +| --- | --- | --- | --- | +| Linux x64 和 Linux ARM64 | Ubuntu 26.04,用 bash 驱动。另有一个测试用 zsh 和 Homebrew 装的 vfox | 3.47.4 | 最新发布版和 `main` | +| Windows x64 和 Windows ARM64 | Windows Server 2025 Server Core,用 PowerShell 7 驱动,vfox 主目录放在带空格的路径里 | 3.47.4 | 最新发布版和 `main` | + +在 x64 上,这些运行还会针对 OpenHarmony 构建版本 3.41.10-ohos-1.0.0 重复一遍,只对最新的 vfox,还会设置 CFUG 镜像再跑一遍。ARM64 只跑官方构建。每次运行都会安装这个版本、检查漂移警告,并启动 `flutter` 和 `dart`。 + +macOS 不在容器测试里,因为容器跑不了它。它的架构选择只由离线测试覆盖。 diff --git a/docs/zh-CN/troubleshooting.md b/docs/zh-CN/troubleshooting.md new file mode 100644 index 0000000..3601021 --- /dev/null +++ b/docs/zh-CN/troubleshooting.md @@ -0,0 +1,200 @@ +# 问题排查 + +[中文](troubleshooting.md) | [English](../troubleshooting.md) + +## 提示信息 + +vfox 自己打印的提示,是 vfox 1.0.12 和 1.0.13 的原文;其他版本的措辞可能不同。 + +### `no installable runtime provided` + +``` +$ vfox install flutter@dev +no installable runtime provided +failed to install flutter +``` + +插件对你输入的名字什么也没找到。常见的原因: + +- 这个名字不是一个发布版本。`flutter@3.47` 和 `flutter@3` 不会补全成最新的 3.47.x 或 3.x,名字也区分大小写(`flutter@Stable`)。到 `vfox search flutter` 里查一下,见 [versions.md](versions.md)。 +- `dev`:Flutter 已经废弃这个渠道,见 [channels.md](channels.md#dev)。 +- 架构后缀在这台机器上不存在:Linux x64 和 Windows x64 上的 `-arm64`,没有架构元数据的版本带任何后缀(`1.22.6-x64`),或者 Apple 芯片上只有 x64 构建的老版本不带后缀(`flutter@2.10.5` 要写成 `flutter@2.10.5-x64`)。见 [arm64.md](arm64.md)。 +- 未发布:比第一个能用原生 Dart SDK 运行的版本更老的版本,在 Linux ARM64(`flutter@1.20.0`)或 Windows ARM64(`flutter@3.43.0-0.3.pre`)上。0.2.4 会用 x64 的 Dart SDK 装上它们,见 [arm64.md](arm64.md#能原生运行的发布版本)。写成 `-x64` 的名字装的是压缩包。 +- 带 `+ohos-` 的 OpenHarmony 名字(`3.41.9+ohos-1.0.2`),在 0.2.4 上,它只认 `-ohos-` 的名字。未发布:两种都认,见 [ohos.md](ohos.md#名称与发布版本)。 + +### `flutter@stable is not installed` + +`vfox use` 和 `vfox uninstall` 只认已安装的名字,而渠道在安装时就换成了具体的版本。`vfox list flutter` 能看到这些名字,见 [channels.md](channels.md#用版本号不要用渠道名)。`vfox exec flutter@stable` 因为同样的原因,会以 `failed to get runtime package for flutter@stable: open .../cache/flutter/v-stable: no such file or directory` 结束。 + +### `flutter@3.47.4 is already installed` + +这个名字已经安装了。vfox 不会再下载,也不会重新写 `.vfox-manifest`,见 [flutter-upgrade.md](flutter-upgrade.md#关闭检查)。 + +### `get version failed: ... (status ...)` + +``` +plugin [PreInstall] method error: ~/.vfox/plugin/flutter/hooks/available.lua:35: get version failed: Get "<索引地址>": <错误> (status nil) +stack traceback: + ... +failed to install flutter +``` + +插件没能读到 Flutter 的发布索引,它试了三次,每次间隔五秒,所以安装在开始十秒之后失败。`get version failed:` 后面是请求的错误,再后面括号里是 HTTP 状态: + +- `(status nil)` 表示没有收到 HTTP 响应:DNS、连接、TLS 或代理的问题。连接被拒绝时是 `dial tcp 127.0.0.1:1: connect: connection refused`,代理没有响应时是 `proxyconnect tcp: dial tcp 127.0.0.1:9: connect: connection refused`。 +- `get version failed: nil (status 404)`,或者别的数字,表示服务器应答了并拒绝。用镜像时出现 404,多半是地址写错了:插件请求的是 `<镜像>/flutter_infra_release/releases/releases_linux.json`(`releases_macos.json`、`releases_windows.json`)。 + +见[网络](#网络)和 [mirror.md](mirror.md)。渠道(`flutter@stable`)会在此之前先读一次索引,而且不重试,这时提示里写的是 `hooks/pre_install.lua`。 + +### `checksum error` + +``` +Verifying checksum c53c8aeff17d13c4c0b47bff2c54a293e48286e03dd089f37d561737e41a8c2c... +Checksum error, file: ~/.vfox/cache/flutter/flutter_linux_3.13.3-stable.tar.xz +checksum error +failed to install flutter +``` + +vfox 下载了压缩包,它的 SHA-256 和 Flutter 发布索引里的不一样。用镜像的话,要检查镜像提供的文件是否与它提供的索引一致。不用镜像时,这曾经是索引数据本身的问题:它把 `3.13.3`、`3.9.0-0.1.pre` 和 `3.17.0-0.0.pre` 各列了两三次,同一个文件,每条的校验和都不同。Flutter 重新上传了文件,却把旧的条目留着了。最新那条的校验和才是对的(在 Linux 上下载了这三个压缩包验证过)。 + +插件现在只列这种文件最新的那一条,所以这三个版本都能装。未发布:0.2.4 把所有条目都列出来,装排在最前面的那条,条目相同时顺序是任意的,还取决于列表的其余部分,比如其中有多少个 OpenHarmony 版本。2026-10-21 时,它在 Linux x64 上三个全都取了旧条目,在 macOS 和 Windows 上也有一部分,安装就会像上面这样结束。请装它旁边的版本(用 `3.13.2` 或 `3.13.4` 代替 `3.13.3`),或者更新插件。没有跳过校验的开关。 + +### `Warning: flutter SDK at ... has drifted from the version vfox installed.` + +`flutter upgrade`、`flutter channel` 或某条 `git` 命令移动了 SDK。`vfox use` 仍然会选中它。见 [flutter-upgrade.md](flutter-upgrade.md)。 + +### `flutter does not support the 386 architecture` + +插件只能在 x64 和 ARM64 上运行。vfox 会把它自己二进制的 CPU(`386`、`arm`、`riscv64`、`loong64` 等)告诉插件,提示里会写出它,比如 `flutter does not support the arm architecture`。`vfox search` 和官方版本的安装都会被它拦住。见 [platforms.md](platforms.md)。未发布:0.2.4 打印的内容一样,只是没有空格,是 `flutter does not support386architecture`。 + +### 源码安装:Linux ARM64 和 Windows ARM64 + +这些提示来自插件,会指出是哪一步失败了,见 [arm64.md](arm64.md#源码安装如何进行)。 + +| 提示 | 含义 | +| --- | --- | +| `cannot resolve the vfox home directory` | `VFOX_HOME`、`HOME` 和 `USERPROFILE` 都没有设置,插件不知道该在哪里建 `.vfox/tmp/<名称>` | +| `failed to initialize git in (is git installed?)` | `git init` 失败了:没有 `git`,或者目录不可写 | +| `failed to fetch tag 3.47.4 from https://github.com/flutter/flutter.git` | 三次拉取全都失败了。`git` 自己的错误打印在这条提示的上面。检查 GitHub 能否访问,或者设置 `VFOX_FLUTTER_GITHUB_MIRROR`,见 [mirror.md](mirror.md#源码安装用的-github-镜像) | +| `failed to check out 3.47.4 in ` | `git checkout FETCH_HEAD` 失败了 | +| `the checkout at 3.47.4 has no engine version pin` | 检出里没有 `bin/internal/engine.version`,所以它不是 Flutter SDK | +| `Using GitHub mirror https://gh-proxy.org/https://github.com/flutter/flutter.git` | 不是错误:正在使用 `VFOX_FLUTTER_GITHUB_MIRROR` | + +安装失败会在 `$VFOX_HOME` 下(或者你的主目录下)留下 `.vfox/tmp/<名称>`。把它删掉就行,下次安装同名版本时它本来也会被删掉。 + +拉取时如果一分钟内速度低于 1 KB/s,会被放弃并记为一次失败的尝试,卡住的连接最后就是这样变成 `failed to fetch tag` 的。 + +### OpenHarmony 构建版本 + +| 提示 | 含义 | +| --- | --- | +| `OpenHarmony releases are unavailable from https://gitcode.com/api/v5/...: HTTP 429` | 安装 OpenHarmony 构建版本时:五次读取发布版本列表都失败了。冒号后面是最后一次的错误,这里是 gitcode.com 的限流,见[下文](#gitcodecom-被限流)。列出版本时永远不会打印这条,它会打印下面那条警告,然后在没有 OpenHarmony 版本的情况下继续 | +| `Warning: the OpenHarmony releases could not be read from gitcode.com (), so only the official releases are listed.` | 两次尝试都没拿到列表时,由 `vfox search` 和 `vfox install` 的 `latest` 打印到 stderr。官方版本照常列出,OpenHarmony 构建版本仍然可以按名字安装。未发布:0.2.4 什么都不打印,而且先要等最多 6.5 分钟 | +| `flutter 3.47.7-ohos-1.0.0 is not an OpenHarmony release` | 名字里有 `-ohos-`,但 gitcode.com 上没有叫这个名字的发布版本 | +| `flutter 3.41.10-ohos-1.0.0 has no architecture variants` | 名字后面带了 `-x64` 或 `-arm64` | +| `failed to fetch from https://gitcode.com/CPF-Flutter/flutter_flutter.git` | 三次拉取全都失败了 | + +见 [ohos.md](ohos.md)。 + +### `bin/internal/shared.sh: line 5: $'\r': command not found` + +在 Linux 上做源码安装(Linux ARM64,或者 OpenHarmony 构建版本)后,第一次运行 `flutter` 就这样失败,说明检出的是 CRLF 换行,原因是你的 git 配置里有 `core.autocrlf=true`。插件现在会在检出里设置 `core.autocrlf=false`。未发布:0.2.4 用的是你的设置,请执行 `git config --global core.autocrlf input`,卸载这个版本再重新安装,见 [arm64.md](arm64.md#限制)。 + +### `Building flutter tool...` 和 `It appears that the downloaded file is corrupt` + +源码安装后的第一次 `flutter` 运行会构建 Flutter 工具并下载 Dart SDK,所以很慢,而且需要网络。从压缩包安装的则不会在第一次运行时构建工具:为此插件会在解压之后改写 `packages/flutter_tools/pubspec.lock`,见 [development.md](development.md#容易踩坑的约定)。 + +`It appears that the downloaded file is corrupt; please try again.` 是 Flutter 工具在下载来的 Dart SDK 不是有效的 zip 文件时说的,下载的文件不存在(HTTP 404)就是这种情况。OpenHarmony 构建版本在 ARM64 上会这样。 + +## 网络 + +插件会访问 `storage.googleapis.com`(或你的镜像)、`gitcode.com`,源码安装还会访问 `github.com`,见 [platforms.md](platforms.md#插件会连接的地址)。失败的请求会重试: + +| 请求 | 次数 | 每次之间的等待 | +| --- | --- | --- | +| 发布索引,列出或安装一个版本时 | 3 | 5 秒 | +| 发布索引,渠道在此之前的那一次 | 1 | 不等待 | +| gitcode.com 上的 OpenHarmony 发布版本,用于安装 OpenHarmony 构建版本 | 5 | 10 秒,但遇到 HTTP 429 之后是 30、60、120 和 180 秒 | +| gitcode.com 上的 OpenHarmony 发布版本,用于列出版本 | 2 | 2 秒。遇到 HTTP 429 就停止尝试 | +| 源码安装或 OpenHarmony 构建版本的 `git fetch` | 3 | 不等待。一分钟内速度低于 1 KB/s 就算失败 | + +vfox 自己的代理设置对插件的请求和压缩包的下载都有效: + +```bash +vfox config proxy.enable true +vfox config proxy.url http://127.0.0.1:7890 +``` + +`proxy.enable` 为 `false` 时,通常的 `HTTP_PROXY`、`HTTPS_PROXY` 和 `NO_PROXY` 环境变量依然有效。源码安装和 OpenHarmony 构建版本用的 `git` 不读 vfox 的设置,它读的是自己的:`HTTPS_PROXY` 或 `git config --global http.proxy`。 + +### gitcode.com 被限流 + +插件在两种情况下会向 gitcode.com 要 OpenHarmony 的发布版本。安装 OpenHarmony 构建版本需要它们,而且会等。`vfox search flutter`(vfox 会把结果缓存 12 小时,所以只有第一次会问)和 `vfox install` 的 `latest` 会列出它们,而且不会等。安装官方版本完全不会访问 gitcode.com。 + +gitcode.com 限制匿名客户端的请求次数。超出限制时它回答 HTTP 429: + +``` +{"error_code":429,"error_code_name":"UN_KNOW","error_message":"429 The number of times the user invokes this API is too large. Threshold: 10W times per user per hour. Limited account:p_repoRobot.", ...} +``` + +这是 gitcode.com 对未登录请求的限制,不是你自己的限额:提示里点出了这次请求算在哪个账户头上,是 `p_repoRobot`。插件对它的处理是: + +- 列出版本时尝试两次,间隔两秒,遇到 HTTP 429 就立刻停止。拿不到列表时,它在 stderr 上打印一行,然后用官方版本继续: + + ``` + Warning: the OpenHarmony releases could not be read from gitcode.com (HTTP 429), so only the official releases are listed. + ``` + + gitcode.com 无法访问时同样只花这两次尝试,所以连接立刻失败的话大约 2 秒(经过一个拒绝连接的代理测得 2.1 秒)。之后 `vfox search flutter` 会在没有 OpenHarmony 名字的情况下缓存 12 小时,`vfox config cache.availableHookDuration 0` 可以关掉缓存。 +- 安装 OpenHarmony 构建版本最多尝试五次,遇到 429 后依次等 30、60、120 和 180 秒,所以它可能等 6.5 分钟而且什么都不打印。 + +未发布:0.2.4 对每个列出版本的命令,以及每次安装(包括官方版本),都要这样等。2026-10-21 gitcode.com 回答 429 的时候测得: + +| 命令 | 耗时 | 结果 | +| --- | --- | --- | +| `vfox search flutter` | 392 秒 | 434 个官方版本,没有 OpenHarmony 版本,退出码 0 | +| `vfox install flutter@3.47.4` | 212 秒 | 装上了。第四次尝试通过了 | + +几分钟后同一个 API 一秒就能回答。没有 429 时,一次失败的请求每次重试花 10 秒,一共 40 秒。 + +## 看起来不对但其实正常的现象 + +- **`vfox install flutter@latest` 装出来的是 `3.47.7-x64`,不是 `3.47.7`。** `latest` 是 `vfox search flutter` 的第一行,也就是最新的稳定版,而且它带着架构后缀,所以你再装一次 `stable` 的话,同一个 Flutter 会装两份,见 [versions.md](versions.md#搜索)。未发布:0.2.4 装的是最新的 beta。 +- **一个版本在 `vfox search flutter` 里出现了不止一次。** Flutter 的索引里它在不止一个渠道里,见 [versions.md](versions.md#搜索)。 +- **未发布:`vfox search flutter` 或 `vfox install` 等了好几分钟,什么也没打印。** 0.2.4 在 gitcode.com 限制它的 API 时会等,见 [gitcode.com 被限流](#gitcodecom-被限流)。命令自己会结束。 +- **你装过的版本在 `(installed)` 标记里没有。** 标记跟的是列表里打印的名字,比如 `3.47.4-x64`,而不是你装的不带后缀的名字。以 `vfox list flutter` 为准。 +- **预期的版本在 `vfox search` 里没有。** vfox 会把结果缓存 12 小时。`vfox config cache.availableHookDuration 0` 可以关掉缓存。也可能是 Flutter 的索引里还没有它,或者你的镜像那份里还没有。 +- **`3.47.4` 和 `3.47.4-x64` 是两份安装。** 在 x64 机器上它们是同一个 Flutter,见 [versions.md](versions.md#已安装的名称)。 +- **卸载一个版本后,全局版本被换掉了。** 卸载正在使用的版本时,vfox 会选 `vfox list flutter` 里剩下的第一项,那可能是个 beta。 +- **`flutter` 运行的不是 `vfox use` 选的那个 Flutter。** `PATH` 里更靠前的另一个 Flutter 会胜出,比如手动装在 `~/develop/flutter` 的,或者包管理器装的。`command -v flutter` 能看到实际运行的是哪一个,它的路径应当以 `~/.vfox/sdks/flutter/bin` 开头,在项目里则是 `.vfox/sdks/flutter/bin`。 +- **Flutter 说“A new version of Flutter is available!”并建议执行 `flutter upgrade`。** 别理它:`flutter upgrade` 会在 vfox 的背后改动 SDK,见 [flutter-upgrade.md](flutter-upgrade.md)。`vfox install flutter@stable` 才是升级。`flutter --no-version-check` 可以不显示这条提示。 +- **`FLUTTER_ROOT` 没有设置。** 插件只设置 `PATH`。`flutter` 脚本会自己找到它所在的目录。 +- **没有装 Dart 插件,`dart` 却在。** Flutter SDK 包含 Dart SDK,`dart` 就在它的 `bin` 里。不需要再另外添加 [vfox-dart](https://github.com/version-fox/vfox-dart) 插件。 +- **`.vfox-manifest` 在 SDK 的 git 里是未跟踪的。** 这是插件写的,见 [flutter-upgrade.md](flutter-upgrade.md)。 +- **在中国,`flutter pub get` 很慢或者失败。** 包是从 `PUB_HOSTED_URL` 拉取的,插件不会去动它,见 [mirror.md](mirror.md#哪种安装用哪个镜像)。 +- **第一次执行 `flutter` 命令会下载很多东西。** Flutter 工具在你第一次为某个平台构建时才拉取所需的内容,而且它为此读取 `FLUTTER_STORAGE_BASE_URL`,所以要一直保持镜像变量处于设置状态,见 [mirror.md](mirror.md#这个变量的作用)。 + +## Flutter 在哪里 + +每份安装都是 vfox 主目录里自己的一个目录,默认是 `~/.vfox`,设置了 `VFOX_HOME` 则是它(`storage.sdkPath` 可以把 SDK 挪到别处,见 vfox 的[配置](https://vfox.dev/zh-hans/guides/configuration.html))。`vfox info flutter@3.47.4` 打印的是 SDK 上一层的目录,SDK 是它里面的 `flutter-3.47.4` 目录: + +``` +$ vfox info flutter@3.47.4 +/home/user/.vfox/cache/flutter/v-3.47.4 +$ ls /home/user/.vfox/cache/flutter/v-3.47.4 +flutter-3.47.4 +``` + +`vfox use` 还会为你选中的 SDK 建一个链接,不管它指向哪个版本,链接的路径都是同一个: + +| 作用域 | 链接 | +| --- | --- | +| `--global` | `~/.vfox/sdks/flutter`(Windows 上是 `%USERPROFILE%\.vfox\sdks\flutter`) | +| `--project` | 项目目录里的 `.vfox/sdks/flutter` | +| `--session` | `~/.vfox/tmp` 下某个目录里的 `flutter` | + +`PATH` 里放的是这个链接的 `bin` 目录,所以 `flutter` 和 `dart` 都来自它。想跟随你的全局选择的 IDE 或构建工具,可以把这个链接当作 Flutter SDK 路径(Android Studio 的 *Flutter SDK path*,VS Code 的 `dart.flutterSdkPath` 设置),这样切换版本时它们不用再改。要固定某个版本,就用 `vfox info` 打印的目录和它里面的 `flutter-<版本>` 目录。没有 Shell 钩子的地方,可以用 `vfox exec flutter@3.47.4 -- <命令>` 用那个 Flutter 运行命令,见 [versions.md](versions.md#使用)。 + +## 报告问题 + +请到 [version-fox/vfox-flutter](https://github.com/version-fox/vfox-flutter/issues) 提 issue,附上 `vfox --version` 和 `vfox info flutter` 的输出、操作系统和 CPU、你执行的命令,以及它打印的全部内容。`vfox --debug <命令>` 会打印更多信息。Flutter 本身的问题应当提给 [flutter/flutter](https://github.com/flutter/flutter/issues),OpenHarmony 分支的问题提给 [CPF-Flutter/flutter_flutter](https://gitcode.com/CPF-Flutter/flutter_flutter)。 diff --git a/docs/zh-CN/versions.md b/docs/zh-CN/versions.md new file mode 100644 index 0000000..f05542c --- /dev/null +++ b/docs/zh-CN/versions.md @@ -0,0 +1,153 @@ +# 版本与命名 + +[中文](versions.md) | [English](../versions.md) + +`vfox install`、`vfox use` 和 `vfox uninstall` 里 `flutter@` 后面的部分就是版本:可以是 `vfox search flutter` 打印出的某个发布版本的名字(带不带架构后缀都行),也可以是一个渠道。 + +```bash +vfox install flutter@3.47.4 # 稳定版,装本机架构的构建 +vfox install flutter@3.47.4-x64 # 同一个版本,x64 构建 +vfox install flutter@3.50.0-0.1.pre # beta 版 +vfox install flutter@stable # stable 渠道当前指向的版本 +vfox install flutter@3.41.10-ohos-1.0.0 # OpenHarmony 构建版本 +``` + +版本必须整串匹配。`flutter@3.47` 和 `flutter@3` 不会选中最新的 3.47.x 或 3.x,而是报 `no installable runtime provided`。名字区分大小写(`flutter@Stable` 会失败),vfox 自己会去掉开头的 `v`,所以 `flutter@v3.47.4` 就是 `flutter@3.47.4`。 + +## 发布版本的名称 + +发布版本的名字就是它包含的 Flutter 版本号,写法和 Flutter 的发布索引一致。`vfox search` 会在名字旁边标出它发布到的渠道,见[搜索](#搜索)。 + +| 名称 | 含义 | +| --- | --- | +| `3.47.7` | 稳定版 | +| `3.50.0-0.1.pre` | beta 版。Flutter 的预发布版本都以 `.pre` 结尾 | +| `3.47.4-x64`、`3.47.4-arm64` | 同一个版本的 x64 或 arm64 构建,见 [arm64.md](arm64.md) | +| `1.22.6` | 没有架构元数据的老版本,在哪里装都一样,见 [arm64.md](arm64.md#没有架构元数据的发布版本) | +| `3.41.10-ohos-1.0.0` | OpenHarmony 构建版本:这个分支 3.41.10 线上的 1.0.0 版,见 [ohos.md](ohos.md) | +| `stable`、`beta` | 渠道,安装时会解析成一个具体的版本,见 [channels.md](channels.md) | + +插件列出的是 Flutter 1.17(2020 年 4 月)以后的版本。更早的版本 Flutter 带 `v` 前缀发布(`v1.12.13+hotfix.9` 是插件停下来的第一个),不提供安装。 + +## 安装 + +`vfox install` 先问插件哪个包匹配,再由 vfox 下载并解压。 + +- 插件读取当前操作系统的 Flutter 发布索引:`https://storage.googleapis.com/flutter_infra_release/releases/` 下的 `releases_linux.json`、`releases_macos.json` 或 `releases_windows.json`;设置了 `FLUTTER_STORAGE_BASE_URL` 时则读取那个镜像下的同名文件(见 [mirror.md](mirror.md))。最多请求三次,每次间隔五秒。 +- 版本必须与索引里的某个名字完全相同,见[发布版本的名称](#发布版本的名称)。没有发布过的版本会报 `no installable runtime provided`。 +- 不带架构后缀时,插件选本机架构的构建。没有架构元数据的版本只有一个压缩包,就用它。 +- vfox 用索引里的 SHA-256 校验和验证压缩包,并打印 `Verifying checksum ...`。解压之后会删除压缩包。 +- 在 Linux ARM64 和 Windows ARM64 上,插件安装的是 git 标签而不是压缩包,见 [arm64.md](arm64.md)。OpenHarmony 构建版本同样是 git 检出,见 [ohos.md](ohos.md)。两者都不做校验和验证。 +- 安装一个已经存在的名字,会打印 `flutter@3.47.4 is already installed`,不会下载任何东西。 +- 安装官方版本不会访问 gitcode.com。列出版本才会:`vfox search` 和 `vfox install` 的 `latest` 会去问它有哪些 OpenHarmony 版本,最多两次,问不到时会提示,见 [troubleshooting.md](troubleshooting.md#gitcodecom-被限流)。未发布:0.2.4 每次安装也要问它,gitcode.com 限流时最多会等 6.5 分钟,而且什么都不说。 + +压缩包很大:2026-10-21 的 3.47.7,Linux x64 是 1.6 GB,Windows x64 是 1.9 GB,macOS 是 2.3 GB。第一次运行 `flutter` 还会再下载东西,见 [troubleshooting.md](troubleshooting.md#看起来不对但其实正常的现象)。 + +## 已安装的名称 + +插件按你请求的版本给安装命名,只有你写了架构后缀才会带后缀。下面是 Linux x64 在 2026-10-21 的结果;渠道和 `latest` 会换成它们代表的那个版本: + +| 你执行 | 已安装的名称 | +| --- | --- | +| `vfox install flutter@3.47.4` | `3.47.4` | +| `vfox install flutter@3.47.4-x64` | `3.47.4-x64` | +| `vfox install flutter@stable` | `3.47.7` | +| `vfox install flutter@stable-x64` | `3.47.7-x64` | +| `vfox install flutter@beta` | `3.50.0-0.1.pre` | +| `vfox install flutter@latest` | `3.47.7-x64`,即 `vfox search flutter` 的第一行,见[搜索](#搜索) | +| `vfox install flutter@3.41.10-ohos-1.0.0` | `3.41.10-ohos-1.0.0` | + +`vfox list flutter` 显示的就是这些名字,`vfox use` 和 `vfox uninstall` 要写的也是它们: + +``` +$ vfox list flutter +-> 3.50.0-0.1.pre-x64 +-> 3.47.4-x64 +-> 3.47.4 <— current +``` + +在 x64 机器上,`3.47.4` 和 `3.47.4-x64` 是同一个 Flutter,vfox 把它们当作两份安装,各是一个完整的 SDK。要么总是写后缀,要么总是不写。卸载其中一个不会动另一个: + +```bash +vfox uninstall flutter@3.47.4-x64 +``` + +`vfox uninstall flutter@stable` 会回答 `flutter@stable is not installed`;`vfox uninstall` 和 `vfox use` 一样,要写已安装的名字。卸载正在使用的版本时,vfox 会切换到 `vfox list flutter` 里剩下的第一项,那可能是个 beta。 + +每份安装都在 vfox 主目录(默认是 `~/.vfox`,设置了 `VFOX_HOME` 则是它)下的 `cache/flutter/v-<名称>/flutter-<名称>/` 里,见 [troubleshooting.md](troubleshooting.md#flutter-在哪里)。 + +## 使用 + +`vfox use` 只认已安装的名字,原样匹配,不会补全,也不会搜索: + +| 你执行 | 结果 | +| --- | --- | +| `vfox use flutter@3.47.4` | 选中 `3.47.4` | +| `vfox use flutter@3.47.4-x64` | 选中 `3.47.4-x64` | +| `vfox use flutter@3.47` | `flutter@3.47 is not installed` | +| `vfox use flutter@stable` | `flutter@stable is not installed`,见 [channels.md](channels.md) | +| `vfox use flutter@latest` | `vfox list flutter` 的第一项,那个列表由 vfox 自己排序,所以装了 beta 的话它就是 beta | + +用 `vfox use` 的参数选择作用域:`--global`、`--project` 或 `--session`。不带参数时,Linux 和 macOS 上是会话作用域,Windows 上是全局作用域。选择会写在这些地方: + +| 作用域 | 写入位置 | `flutter` 来自 | +| --- | --- | --- | +| `--global` | `~/.vfox/.vfox.toml` | `~/.vfox/sdks/flutter/bin` | +| `--project` | 当前目录里的 `.vfox.toml` | 那个目录里的 `.vfox/sdks/flutter/bin` | +| `--session` | 只在当前 Shell 里 | `~/.vfox/tmp` 下的某个目录 | + +`.vfox.toml` 里写的是已安装的名字(`flutter = "3.47.4"`)。把它提交进仓库,大家构建项目时用的就是同一个 Flutter;`.vfox/` 则不要提交:vfox 不会替你把它加进 `.gitignore`。vfox 在 `legacyVersionFile.enable` 打开时还会读取 asdf 的 `.tool-versions`(`flutter 3.47.4`),vfox 1.0.13 默认是打开的。插件自己没有旧版本文件的支持,所以不会读取 `.fvmrc` 和 FVM 的 `.fvm` 目录。 + +插件会把 `/bin` 加进 `PATH`,`flutter` 和 `dart` 就都在里面了。它不会设置 `FLUTTER_ROOT`,也不会把 `dart pub global activate` 放可执行文件的 `~/.pub-cache/bin` 加进去,这个要你自己加。 + +想在不切换版本的情况下用某个 Flutter 跑一条命令,或者是在没有启用 Shell 钩子的脚本、容器、IDE 任务里,可以用 `vfox exec`,同样要写已安装的名字: + +```bash +vfox exec flutter@3.47.4 -- flutter --version +``` + +`vfox exec flutter@stable` 不行,见 [channels.md](channels.md#用版本号不要用渠道名)。 + +## 搜索 + +``` +$ vfox search flutter +Available versions: + - 3.47.7-x64 (stable) [dart 3.13.5] + - 3.47.6-x64 (stable) [dart 3.13.5] + ... + - 1.17.0 (stable) [dart ] + - 3.50.0-0.1.pre-x64 (beta) [dart 3.14.0 (build 3.14.0-315.1.beta)] + - 3.49.0-0.2.pre-x64 (beta) [dart 3.14.0 (build 3.14.0-211.1.beta)] + ... + - 1.17.0-3.2.pre (beta) [dart ] + - 3.41.10-ohos-1.0.0 (OpenHarmony) + ... +``` + +每一行都是可以写在 `flutter@` 后面的名字,圆括号里是这个版本发布到的渠道,方括号里是随它一起的 Dart SDK。已安装的名字后面会有 `(installed)` 标记。Flutter 的索引里,`2.9.0-0.1.pre` 及更早的版本没有 Dart 版本,方括号就是空的。OpenHarmony 构建版本带的是 `(OpenHarmony)`,没有 Dart 版本。 + +标记只会出现在列表打印出的名字上。在 x64 的 Linux 和 Windows 上,几乎所有官方版本都以 `3.47.4-x64` 的形式列出,所以用 `vfox install flutter@3.47.4` 装出来的(已安装的名字是 `3.47.4`)不会有标记,用 `vfox install flutter@3.47.4-x64` 装的才有。以 `vfox list flutter` 为准。 + +在交互式终端里,`vfox search flutter` 会让你选一个版本并安装;其他情况只打印列表。vfox 会把结果缓存 12 小时(`vfox config cache.availableHookDuration`),所以刚发布一分钟的版本可能还看不到;`vfox config cache.availableHookDuration 0` 可以关掉缓存。 + +列表按下面的顺序排: + +1. 官方版本排在 OpenHarmony 构建版本之前。 +2. 本机架构排在其他架构之前。没有架构元数据的版本算作本机架构。 +3. stable 渠道的版本排在 beta 和 dev 渠道的预发布版本之前。 +4. 按版本号里的数字,新的在前。`3.47.0` 排在 `3.47.0-0.4.pre` 之前。 +5. 同一个版本的两个构建,本机架构在前;同一个版本在两个渠道里时,beta 在 dev 之前。 + +所以第一行是最新的稳定版,`vfox install flutter@latest` 装的就是它,带着架构后缀: + +``` +$ vfox install flutter@latest +Using latest version: 3.47.7-x64 +``` + +`latest` 会保持在本机架构上,原因见 [arm64.md](arm64.md),而且永远不会选中 OpenHarmony 构建版本。未发布:0.2.4 只按版本号里的数字给所有版本排序,所以更新的小版本的 beta(`3.50.0-0.1.pre`)排在所有 `3.47.x` 之前,它的 `latest` 装的就是那个 beta,即 `3.50.0-0.1.pre-x64`。 + +有些版本会出现不止一次。它们在 Flutter 的索引里就不止一条:有的是从一个渠道晋升到另一个渠道,每个渠道各有一个压缩包(`2.13.0-0.1.pre` 在 beta 和 dev 里都有),这几行装出来的名字相同。有的是版本重新发布过,同一个压缩包对应两个校验和,插件只列其中最新的那条(Flutter 重新发布过的有 `3.13.3`、`3.9.0-0.1.pre` 和 `3.17.0-0.0.pre` 这三个)。未发布:0.2.4 会把所有条目都列出来,装排在最前面的那条;如果装到了旧条目,安装会以 `checksum error` 结束,见 [troubleshooting.md](troubleshooting.md#checksum-error)。 + +列表有多长取决于机器。2026-10-21 的数量:Linux x64 是 455 个名字(430 个官方版本和 25 个 OpenHarmony 构建版本),macOS 是 717 个(692 个和 25 个,两种架构都在),Windows x64 是 453 个,Linux ARM64 和 Windows ARM64 分别是 743 个和 490 个,它们多出来的 288 个和 37 个是源码安装。列表比这短得多,说明插件有一部分数据来源没读到,见 [troubleshooting.md](troubleshooting.md#网络)。 diff --git a/hooks/available.lua b/hooks/available.lua index 2cb9c8c..ff120ca 100644 --- a/hooks/available.lua +++ b/hooks/available.lua @@ -21,10 +21,10 @@ local MAX_ATTEMPTS = 3 local RETRY_DELAY = 5 require("util") -local function releases(type) +local function releases(platform) local resp, err for attempt = 1, MAX_ATTEMPTS do - resp, err = http.get({ url = getReleasesUrl(type.osType) }) + resp, err = http.get({ url = getReleasesUrl(platform.osType) }) if resp ~= nil and resp.status_code == 200 then return json.decode(resp.body) end @@ -35,16 +35,48 @@ local function releases(type) error("get version failed: " .. tostring(err) .. " (status " .. tostring(resp and resp.status_code) .. ")") end -function PLUGIN:Available(ctx) - local type = getOsTypeAndArch() - local body = releases(type) +-- The index lists some releases more than once. A release that was promoted from one channel to +-- another has an archive in each. One that was published again has the same archive under two +-- checksums, and only the newest entry has the checksum of the file that is there now: the older +-- one fails the verification of the download. Keep the newest entry of each archive of a +-- version, in the order the index has them. Different versions never merge. +local function newestPublications(entries) + local newest, order = {}, {} + for _, info in ipairs(entries) do + local key = info.archive and (info.version .. "|" .. info.archive) or info + local seen = newest[key] + if seen == nil then + newest[key] = info + table.insert(order, key) + elseif (info.release_date or "") > (seen.release_date or "") then + newest[key] = info + end + end local result = {} + for _, key in ipairs(order) do + table.insert(result, newest[key]) + end + return result +end + +-- The list `vfox search` shows, and `vfox install flutter@latest` takes the first line of. +-- PreInstall asks for the official releases only (ctx.officialOnly): installing one has no use +-- for the OpenHarmony list, and must not wait for gitcode.com to answer it. +function PLUGIN:Available(ctx) + local platform = getOsTypeAndArch() + local body = releases(platform) + local kept = {} for _, info in ipairs(body.releases) do - local version = info.version - local oldVersion = string.sub(version, 1, 1) == "v" + local oldVersion = string.sub(info.version, 1, 1) == "v" if oldVersion then break end + table.insert(kept, info) + end + kept = newestPublications(kept) + local result = {} + for _, info in ipairs(kept) do + local version = info.version local dartArch = info.dart_sdk_arch version = dartArch and (version .. "-" .. dartArch) or version table.insert(result, { @@ -61,11 +93,22 @@ function PLUGIN:Available(ctx) }, }) end - for _, info in ipairs(ohos.list()) do - table.insert(result, info) + if not (type(ctx) == "table" and ctx.officialOnly) then + local listed, err = ohos.list() + if listed == nil then + io.stderr:write( + "Warning: the OpenHarmony releases could not be read from gitcode.com (" + .. tostring(err) + .. "), so only the official releases are listed.\n" + ) + else + for _, info in ipairs(listed) do + table.insert(result, info) + end + end end - if source.supports(type.osType, type.archType) then - for _, info in ipairs(source.list(body.releases)) do + if source.supports(platform.osType, platform.archType) then + for _, info in ipairs(source.list(kept, platform.osType, platform.archType)) do table.insert(result, info) end end @@ -79,21 +122,34 @@ function PLUGIN:Available(ctx) -- no architecture metadata, so @latest cannot select a foreign build. local _, aArch = splitVersionAndArch(a.version) local _, bArch = splitVersionAndArch(b.version) - local aDefault = aArch == nil or aArch == type.archType - local bDefault = bArch == nil or bArch == type.archType + local aDefault = aArch == nil or aArch == platform.archType + local bDefault = bArch == nil or bArch == platform.archType if aDefault ~= bDefault then return aDefault end + -- Stable releases before pre-releases, so @latest is the newest stable release and + -- not a beta that happens to have a higher number. + local aStable = a.note == "stable" + local bStable = b.note == "stable" + if aStable ~= bStable then + return aStable + end local order = compare_versions(a.version, b.version) if order ~= 0 then return order > 0 end - if aArch == type.archType then - return bArch ~= type.archType - elseif bArch == type.archType then - return false + local aNative = aArch == platform.archType + local bNative = bArch == platform.archType + if aNative ~= bNative then + return aNative + end + if a.version ~= b.version then + return a.version < b.version end - return a.version < b.version + -- The same name in two channels (a release promoted from beta to stable, or from dev to + -- beta): a fixed order, so that equal entries do not come out in a different one + -- whenever the rest of the list changes. + return (a.note or "") < (b.note or "") end) return result end diff --git a/hooks/pre_install.lua b/hooks/pre_install.lua index dc27121..4d09f0e 100644 --- a/hooks/pre_install.lua +++ b/hooks/pre_install.lua @@ -57,7 +57,8 @@ function PLUGIN:PreInstall(ctx) end local legacy - for _, info in ipairs(self:Available({})) do + -- Official releases only: gitcode.com has nothing to say about them, and rate-limits its API. + for _, info in ipairs(self:Available({ officialOnly = true })) do local version, arch = splitVersionAndArch(info.version) local matches = channelKey and info.key == channelKey or (channelKey == nil and version == arg) if matches then diff --git a/lib/git.lua b/lib/git.lua index f2eb2b5..7ff5a63 100644 --- a/lib/git.lua +++ b/lib/git.lua @@ -137,7 +137,13 @@ function M.init(root) if not exec("git init -q " .. quote(root)) then return false end - return git(root, "config core.longpaths true") + -- core.autocrlf: the checkout takes the user's git configuration, and a global + -- `core.autocrlf=true` (what the Git for Windows installer proposes) writes Flutter's shell + -- scripts with CRLF line endings, because its .gitattributes forces LF only for bin/flutter, + -- bin/flutter-dev and bin/dart. Then `flutter` dies on Linux with + -- `$'\r': command not found`. With `false` core.eol decides, which is LF on Linux. On + -- Windows it is native, so CRLF, and `false` writes the same files `true` does there. + return git(root, "config core.longpaths true") and git(root, "config core.autocrlf false") end function M.fetch(root, remote, ref) diff --git a/lib/ohos.lua b/lib/ohos.lua index 7fa588f..d6197fd 100644 --- a/lib/ohos.lua +++ b/lib/ohos.lua @@ -18,31 +18,43 @@ local git = require("git") require("util") -local MARKER = "-ohos-" +-- The fork tags its releases `3.41.10-ohos-1.0.0`, and, since 2026-10-15, `3.41.9+ohos-1.0.2`. +local MARKERS = { "-ohos-", "+ohos-" } local REPO = "CPF-Flutter/flutter_flutter" local RELEASES_URL = "https://gitcode.com/api/v5/repos/%s/releases?per_page=100" local CLONE_URL = "https://gitcode.com/%s.git" local NOTE = "OpenHarmony" -local RELEASES_ATTEMPTS = 5 -local RETRY_DELAY = 10 --- HTTP 429 (rate limited) waits progressively longer, so installs can ride --- out short quota windows instead of failing immediately. Ordinary errors +-- Installing an OpenHarmony build cannot go on without the releases, so it waits for them: +-- five attempts, ten seconds apart. HTTP 429 (rate limited) waits progressively longer, so +-- installs can ride out short quota windows instead of failing immediately. Ordinary errors -- keep the short delay: there is no window to wait out. -local RATE_LIMIT_DELAYS = { 30, 60, 120, 180 } +local INSTALL = { attempts = 5, delay = 10, rateLimited = { 30, 60, 120, 180 } } +-- Listing them only adds names to `vfox search`, and every command that lists versions would +-- wait along with it: two attempts, and HTTP 429 ends them, since a quota window lasts much +-- longer than a list is worth waiting for. The caller says the list is incomplete. +local LIST = { attempts = 2, delay = 2 } local M = {} function M.isOhosVersion(version) - return type(version) == "string" and version:find(MARKER, 1, true) ~= nil + if type(version) ~= "string" then + return false + end + for _, marker in ipairs(MARKERS) do + if version:find(marker, 1, true) ~= nil then + return true + end + end + return false end local function isRateLimited(resp, err) return err == nil and resp ~= nil and resp.status_code == 429 end -local function releases() +local function releases(policy) local lastErr = "unknown error" - for attempt = 1, RELEASES_ATTEMPTS do + for attempt = 1, policy.attempts do local resp, err = http.get({ url = RELEASES_URL:format(REPO) }) if err == nil and resp ~= nil and resp.status_code == 200 then local body = json.decode(resp.body) @@ -53,20 +65,30 @@ local function releases() else lastErr = err or ("HTTP " .. tostring(resp and resp.status_code)) end - if attempt < RELEASES_ATTEMPTS then + if attempt < policy.attempts then if isRateLimited(resp, err) then - sleep(RATE_LIMIT_DELAYS[attempt] or RATE_LIMIT_DELAYS[#RATE_LIMIT_DELAYS]) + local waits = policy.rateLimited + if waits == nil then + break + end + sleep(waits[attempt] or waits[#waits]) else - sleep(RETRY_DELAY) + sleep(policy.delay) end end end return nil, lastErr end +-- The OpenHarmony releases as `Available` entries, or nil and the reason when gitcode.com +-- does not answer. function M.list() + local body, err = releases(LIST) + if body == nil then + return nil, err + end local result = {} - for _, info in ipairs(releases() or {}) do + for _, info in ipairs(body) do local version = info.tag_name if M.isOhosVersion(version) then table.insert(result, { @@ -85,7 +107,7 @@ function M.checkout(version, requestedArch) error("flutter " .. version .. " has no architecture variants") end local commit - local body, err = releases() + local body, err = releases(INSTALL) if body == nil then error("OpenHarmony releases are unavailable from " .. RELEASES_URL:format(REPO) .. ": " .. tostring(err)) end diff --git a/lib/source.lua b/lib/source.lua index 722c997..6c83bc1 100644 --- a/lib/source.lua +++ b/lib/source.lua @@ -14,6 +14,8 @@ local git = require("git") +require("util") + local REPO = "flutter/flutter" local CLONE_URL = "https://github.com/%s.git" @@ -22,6 +24,26 @@ local SOURCE_INSTALL_PLATFORMS = { ["windows/arm64"] = true, } +-- The oldest release whose Flutter tool runs with a native Dart SDK on the machine, so that an +-- `-arm64` source install is what its name says. The tool downloads the Dart SDK on its first +-- run, and it takes the arm64 one only when two things hold: the release's own bootstrap script +-- (bin/internal/update_dart_sdk.sh, update_dart_sdk.ps1 on Windows) asks for it, and Flutter's +-- storage has it (flutter_infra_release/flutter//dart-sdk--arm64.zip). +-- Checked on 2026-10-21 for the 300 releases that have an x64 build, 1.20.0 to 3.50.0-0.1.pre: +-- Linux: the script asks for the x64 SDK up to 2.0.6, which an ARM64 machine does not run, and +-- for the arm64 one from 2.2.0 on. Storage has the arm64 SDK from 2.0.0 on. +-- Windows: the script asks for the arm64 SDK from 3.20.0-1.1.pre on, and falls back to the x64 +-- one, which Windows runs through its emulation, when storage has none. Storage has it from +-- 3.44.0-0.1.pre on, and for seven older betas (3.11.0-0.0.pre, 3.17.0-0.0.pre, +-- 3.18.0-0.1.pre, 3.19.0-0.1.pre, 3.20.0-1.1.pre, 3.21.0-0.0.pre and 3.30.0-0.1.pre), of which +-- the first four are older than the script that would use it. The three that run natively are +-- left out with the rest. +-- Older releases keep their x64 archives; this decides which releases get an `-arm64` name. +local OLDEST_NATIVE = { + ["linux/arm64"] = "2.2.0", + ["windows/arm64"] = "3.44.0-0.1.pre", +} + local M = {} function M.repoUrl() @@ -32,10 +54,16 @@ function M.supports(osType, archType) return SOURCE_INSTALL_PLATFORMS[osType .. "/" .. archType] == true end -function M.list(releases) +function M.list(releases, osType, archType) + local oldest = OLDEST_NATIVE[tostring(osType) .. "/" .. tostring(archType)] local result = {} for _, info in ipairs(releases or {}) do - if type(info.version) == "string" and string.sub(info.version, 1, 1) ~= "v" and info.dart_sdk_arch == "x64" then + if + type(info.version) == "string" + and string.sub(info.version, 1, 1) ~= "v" + and info.dart_sdk_arch == "x64" + and (oldest == nil or compare_versions(info.version, oldest) >= 0) + then table.insert(result, { version = info.version .. "-arm64", url = M.repoUrl(), diff --git a/lib/util.lua b/lib/util.lua index d439b3d..23d0f08 100644 --- a/lib/util.lua +++ b/lib/util.lua @@ -45,7 +45,7 @@ function getOsTypeAndArch() elseif archType == "arm64" then archType = "arm64" else - error("flutter does not support" .. archType .. "architecture") + error("flutter does not support the " .. archType .. " architecture") end return { osType = osType, diff --git a/tests/hooks_test.lua b/tests/hooks_test.lua index 3b3dd05..79b322d 100644 --- a/tests/hooks_test.lua +++ b/tests/hooks_test.lua @@ -14,7 +14,10 @@ package.path = "./lib/?.lua;" .. package.path -local fixture, requests, mirror +local fixture, requests, mirror, sleeps +-- What gitcode.com answers, when a test wants it to answer anything but the fixture: a function +-- of the request that returns what http.get returns, and the releases that its 200 carries. +local gitcode, gitcodeReleases local originalGetenv = os.getenv os.getenv = function(name) if name == "FLUTTER_STORAGE_BASE_URL" then @@ -26,13 +29,19 @@ package.preload.http = function() return { get = function(request) table.insert(requests, request.url) + if gitcode ~= nil and request.url:find("gitcode.com", 1, true) then + return gitcode(request) + end return { status_code = 200, body = "fixture" }, nil end, } end package.preload.json = function() return { - decode = function() + decode = function(body) + if body == "gitcode releases" then + return gitcodeReleases + end return fixture end, } @@ -42,6 +51,44 @@ local function equal(actual, expected) assert(actual == expected, "expected " .. tostring(expected) .. ", got " .. tostring(actual)) end +-- Everything the plugin writes to stderr while fn runs. +local function captureStderr(fn) + local written = {} + local original = io.stderr + rawset(io, "stderr", { + write = function(_, text) + table.insert(written, text) + return true + end, + }) + local ok, err = pcall(fn) + rawset(io, "stderr", original) + assert(ok, err) + return table.concat(written) +end + +-- Runs fn with every shell command succeeding, and the engine version pin of a checkout present: +-- the checkout code of the plugin runs, git does not. Returns what fn returned and the commands. +local function withoutGit(fn) + local execute, open = os.execute, io.open + local commands = {} + os.execute = function(command) + table.insert(commands, command) + return 0 + end + rawset(io, "open", function(path, mode) + if type(path) == "string" and path:find("engine.version", 1, true) then + return { close = function() end } + end + return open(path, mode) + end) + local ok, result = pcall(fn) + os.execute = execute + rawset(io, "open", open) + assert(ok, result) + return result, commands +end + local function release(version, arch, channel, hash) return { version = version, @@ -55,7 +102,8 @@ local function release(version, arch, channel, hash) end local function setup(osType, archType, storage) - mirror, requests = storage, {} + mirror, requests, sleeps = storage, {}, {} + gitcode, gitcodeReleases = nil, nil RUNTIME = { osType = osType, archType = archType } -- Both architectures share the same Flutter commit. Put x64 first to catch -- channel selection that relies on the upstream array's order. @@ -76,6 +124,10 @@ local function setup(osType, archType, storage) dofile("metadata.lua") dofile("hooks/available.lua") dofile("hooks/pre_install.lua") + -- util.lua defines sleep: replace it once it is loaded, and record instead of waiting. + sleep = function(seconds) + table.insert(sleeps, seconds) + end end local function installed(request, version, arch) @@ -222,6 +274,316 @@ tests[#tests + 1] = { assert(commands[1]:find(guard, 1, true), commands[1]) end, } +tests[#tests + 1] = { + "installing an official release never asks gitcode.com", + function() + setup("linux", "amd64") + installed("3.44.0", "3.44.0", "x64") + installed("stable", "3.44.0", "x64") + installed("3.44.0-0.1.pre-x64", "3.44.0-0.1.pre-x64", "x64") + for _, url in ipairs(requests) do + assert(not url:find("gitcode.com", 1, true), "asked " .. url) + end + -- `vfox search`, and the `latest` of `vfox install`, list the OpenHarmony releases too + requests = {} + PLUGIN:Available({}) + local asked = 0 + for _, url in ipairs(requests) do + if url:find("gitcode.com", 1, true) then + asked = asked + 1 + end + end + equal(asked, 1) + end, +} +tests[#tests + 1] = { + "a rate-limited gitcode.com costs a listing no wait, and the listing says what is missing", + function() + setup("linux", "amd64") + gitcode = function() + return { status_code = 429, body = "limited" }, nil + end + local versions + local warning = captureStderr(function() + versions = PLUGIN:Available({}) + end) + equal(#versions, 7) + equal(#sleeps, 0) + assert(warning:find("OpenHarmony releases could not be read from gitcode.com (HTTP 429)", 1, true), warning) + assert(warning:find("only the official releases are listed", 1, true), warning) + end, +} +tests[#tests + 1] = { + "a listing tries a failed gitcode.com request once more", + function() + setup("linux", "amd64") + local calls = 0 + gitcode = function() + calls = calls + 1 + return nil, "connection refused" + end + local warning = captureStderr(function() + PLUGIN:Available({}) + end) + equal(calls, 2) + equal(tostring(table.concat(sleeps, ",")), "2") + assert(warning:find("connection refused", 1, true), warning) + end, +} +tests[#tests + 1] = { + "installing an OpenHarmony build waits out a rate-limited gitcode.com", + function() + setup("linux", "amd64") + gitcodeReleases = { { tag_name = "3.41.10-ohos-1.0.0", target_commitish = "abc123" } } + local calls = 0 + gitcode = function() + calls = calls + 1 + if calls <= 4 then + return { status_code = 429, body = "limited" }, nil + end + return { status_code = 200, body = "gitcode releases" }, nil + end + local result, commands = withoutGit(function() + return PLUGIN:PreInstall({ version = "3.41.10-ohos-1.0.0" }) + end) + equal(result.version, "3.41.10-ohos-1.0.0") + equal(calls, 5) + equal(table.concat(sleeps, ","), "30,60,120,180") + local fetch = "fetch -q --depth 1 https://gitcode.com/CPF-Flutter/flutter_flutter.git abc123" + local fetched = false + for _, command in ipairs(commands) do + fetched = fetched or command:find(fetch, 1, true) ~= nil + end + assert(fetched, "the commit was not fetched") + end, +} +tests[#tests + 1] = { + "OpenHarmony releases tagged with +ohos- are listed and installable", + function() + setup("linux", "amd64") + gitcodeReleases = { + { tag_name = "3.41.10-ohos-1.0.0", target_commitish = "aaa111" }, + { tag_name = "3.41.9+ohos-1.0.2", target_commitish = "bbb222" }, + { tag_name = "v0.0.1", target_commitish = "ccc333" }, + } + gitcode = function() + return { status_code = 200, body = "gitcode releases" }, nil + end + local names = {} + for _, info in ipairs(PLUGIN:Available({})) do + table.insert(names, info.version) + end + -- after the seven official releases, newest first, and without the tag that is neither + equal(#names, 9) + equal(names[8], "3.41.10-ohos-1.0.0") + equal(names[9], "3.41.9+ohos-1.0.2") + local result, commands = withoutGit(function() + return PLUGIN:PreInstall({ version = "3.41.9+ohos-1.0.2" }) + end) + equal(result.version, "3.41.9+ohos-1.0.2") + local fetched = false + for _, command in ipairs(commands) do + fetched = fetched or command:find(" bbb222", 1, true) ~= nil + end + assert(fetched, "the commit was not fetched") + local ok, err = pcall(PLUGIN.PreInstall, PLUGIN, { version = "3.41.9+ohos-1.0.2-x64" }) + assert(not ok and tostring(err):find("has no architecture variants", 1, true), tostring(err)) + end, +} +tests[#tests + 1] = { + "stable releases come before pre-releases, so latest is the newest stable release", + function() + for _, arch in ipairs({ "arm64", "amd64" }) do + local native = arch == "amd64" and "x64" or arch + setup("darwin", arch) + table.insert(fixture.releases, 1, release("3.45.0-0.1.pre", "x64", "beta", "newer")) + table.insert(fixture.releases, 2, release("3.45.0-0.1.pre", "arm64", "beta", "newer")) + local versions = PLUGIN:Available({}) + equal(versions[1].version, "3.44.0-" .. native) + -- of the native architecture, every stable release is before every pre-release + local pre = false + for _, info in ipairs(versions) do + local _, infoArch = splitVersionAndArch(info.version) + if infoArch == nil or infoArch == native then + if info.note == "stable" then + assert(not pre, info.version .. " is listed after a pre-release") + else + pre = true + end + end + end + assert(pre, "no pre-release was listed") + -- and a pre-release still installs by its name + installed("3.45.0-0.1.pre", "3.45.0-0.1.pre", native) + end + end, +} +tests[#tests + 1] = { + "an archive published again keeps the entry with the newest checksum", + function() + local old = release("3.43.1", "x64", "stable", "oldcommit") + old.release_date, old.sha256 = "2026-01-01T10:00:00Z", "old:x64" + local new = release("3.43.1", "x64", "stable", "newcommit") + new.release_date, new.sha256 = "2026-01-01T12:00:00Z", "new:x64" + -- whichever comes first in the index + for _, entries in ipairs({ { old, new }, { new, old } }) do + setup("linux", "amd64") + for _, entry in ipairs(entries) do + table.insert(fixture.releases, entry) + end + local listed = 0 + for _, info in ipairs(PLUGIN:Available({})) do + if info.version == "3.43.1-x64" then + listed = listed + 1 + equal(info.sha256, "new:x64") + end + end + equal(listed, 1) + equal(PLUGIN:PreInstall({ version = "3.43.1" }).sha256, "new:x64") + end + -- a release promoted to another channel has an archive in each, and keeps both entries + setup("linux", "amd64") + local beta = release("3.42.0-0.1.pre", "x64", "beta", "promoted") + local dev = release("3.42.0-0.1.pre", "x64", "dev", "promoted") + dev.archive, dev.sha256 = "sdk/dev/3.42.0-0.1.pre-x64.zip", "dev:x64" + table.insert(fixture.releases, dev) + table.insert(fixture.releases, beta) + local notes = {} + for _, info in ipairs(PLUGIN:Available({})) do + if info.version == "3.42.0-0.1.pre-x64" then + table.insert(notes, info.note) + end + end + equal(table.concat(notes, ","), "beta,dev") + -- different versions never merge, even when a mirror gives them one archive + setup("linux", "amd64") + local first = release("3.42.5", "x64", "stable", "first") + local second = release("3.42.6", "x64", "stable", "second") + second.archive = first.archive + table.insert(fixture.releases, first) + table.insert(fixture.releases, second) + local found = {} + for _, info in ipairs(PLUGIN:Available({})) do + found[info.version] = true + end + assert(found["3.42.5-x64"] and found["3.42.6-x64"], "versions that share an archive were merged") + end, +} +tests[#tests + 1] = { + "ARM64 source installs start at the first release that runs with a native Dart SDK", + function() + local function names() + local found = {} + for _, info in ipairs(PLUGIN:Available({})) do + found[info.version] = true + end + return found + end + setup("linux", "arm64") + fixture.releases = { + release("3.44.0", "x64", "stable", "c3"), + release("2.2.0", "x64", "stable", "c2"), + release("2.0.6", "x64", "stable", "c1"), + } + local linux = names() + assert(linux["3.44.0-arm64"] and linux["2.2.0-arm64"], "native releases are not offered") + assert(not linux["2.0.6-arm64"], "the tool of 2.0.6 takes the x64 Dart SDK on Linux") + assert(linux["2.0.6-x64"], "the x64 archive is still listed") + equal(PLUGIN:PreInstall({ version = "2.0.6" }), nil) + equal(PLUGIN:PreInstall({ version = "2.0.6-arm64" }), nil) + installed("2.0.6-x64", "2.0.6-x64", "x64") + setup("windows", "arm64") + fixture.releases = { + release("3.44.0", "x64", "stable", "c4"), + release("3.44.0-0.1.pre", "x64", "beta", "c3"), + release("3.43.0-0.3.pre", "x64", "beta", "c2"), + release("3.11.0-0.0.pre", "x64", "beta", "c1"), + } + local windows = names() + assert(windows["3.44.0-arm64"] and windows["3.44.0-0.1.pre-arm64"], "native releases are not offered") + assert(not windows["3.43.0-0.3.pre-arm64"], "3.43.0-0.3.pre falls back to the x64 Dart SDK on Windows") + assert(not windows["3.11.0-0.0.pre-arm64"], "older betas are left out") + assert(windows["3.43.0-0.3.pre-x64"], "the x64 archive is still listed") + end, +} +tests[#tests + 1] = { + "git checkouts ignore core.autocrlf in your git configuration", + function() + local function succeeded(status) + return status == 0 or status == true + end + local execute = os.execute + if originalGetenv("OS") == "Windows_NT" or not succeeded(execute("git --version >/dev/null 2>&1")) then + print("SKIP git checkouts ignore core.autocrlf: needs git and a POSIX shell") + return + end + RUNTIME = { osType = "linux", archType = "amd64" } + local git = require("git") + local base = os.tmpname() + os.remove(base) + local origin, plain, checkout, config = + base .. "/origin", base .. "/plain", base .. "/checkout", base .. "/config" + -- as a user whose git configuration says core.autocrlf=true + local function asUser(command) + return execute("GIT_CONFIG_GLOBAL='" .. config .. "' GIT_CONFIG_NOSYSTEM=1 " .. command) + end + local function write(path, text) + local file = assert(io.open(path, "wb")) + file:write(text) + file:close() + end + local function read(path) + local file = assert(io.open(path, "rb")) + local text = file:read("*a") + file:close() + return text + end + assert(succeeded(execute("mkdir -p '" .. origin .. "'"))) + write(config, "[core]\n\tautocrlf = true\n") + write(origin .. "/.gitattributes", "*.sh text\n") + write(origin .. "/run.sh", "echo hi\n") + assert(succeeded(asUser("git init -q '" .. origin .. "'"))) + local commit = "git -C '" .. origin .. "' -c core.autocrlf=false -c user.name=t -c user.email=t@t " + assert(succeeded(asUser(commit .. "add ."))) + assert(succeeded(asUser(commit .. "commit -q -m c"))) + assert(succeeded(asUser(commit .. "tag t1"))) + -- the control: with that configuration a plain checkout has CRLF line endings + local fetch = " fetch -q --depth 1 file://" .. origin .. " refs/tags/t1" + assert(succeeded(asUser("git init -q '" .. plain .. "'"))) + assert(succeeded(asUser("git -C '" .. plain .. "'" .. fetch))) + assert(succeeded(asUser("git -C '" .. plain .. "' checkout -q FETCH_HEAD"))) + assert(read(plain .. "/run.sh"):find("\r", 1, true), "the control checkout has no CRLF") + -- the plugin's checkout does not + rawset(os, "execute", function(command) + return asUser(command) + end) + local ok, err = pcall(function() + assert(git.init(checkout), "git init failed") + assert(git.fetch(checkout, "file://" .. origin, "refs/tags/t1"), "git fetch failed") + assert(git.checkoutHead(checkout), "git checkout failed") + end) + rawset(os, "execute", execute) + local text = ok and read(checkout .. "/run.sh") or "" + execute("rm -rf '" .. base .. "'") + assert(ok, err) + assert(not text:find("\r", 1, true), "the checkout has CRLF line endings") + equal(text, "echo hi\n") + end, +} +tests[#tests + 1] = { + "an unsupported CPU is named in the message", + function() + setup("linux", "amd64") + for _, cpu in ipairs({ "386", "arm", "riscv64" }) do + RUNTIME = { osType = "linux", archType = cpu } + local ok, err = pcall(getOsTypeAndArch) + assert( + not ok and tostring(err):find("flutter does not support the " .. cpu .. " architecture", 1, true), + tostring(err) + ) + end + end, +} local failures = 0 for _, test in ipairs(tests) do