From c2a8f0ae154018be934cf7d08c880f5453da72ca Mon Sep 17 00:00:00 2001 From: Qiheng He Date: Wed, 30 Sep 2026 23:32:22 +0800 Subject: [PATCH] feat: replace auto-fallback with `VFOX_FLUTTER_GITHUB_MIRROR` env var for source installs --- docs/arm64.md | 6 +- docs/mirror.md | 47 +++++++- docs/ohos.md | 6 +- lib/git.lua | 154 +++----------------------- lib/source.lua | 7 +- tests/e2e/linux/run.sh | 17 +++ tests/e2e/windows/preflight.Tests.ps1 | 21 ++++ 7 files changed, 107 insertions(+), 151 deletions(-) diff --git a/docs/arm64.md b/docs/arm64.md index 78cd841..da0d461 100644 --- a/docs/arm64.md +++ b/docs/arm64.md @@ -55,7 +55,5 @@ run on Windows ARM64. 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. If GitHub sends no data within 10s, the plugin - automatically falls back to the `https://gh-proxy.org/` mirror and shows - a countdown while probing. Once the transfer starts it is never - interrupted. + from GitHub. Set `VFOX_FLUTTER_GITHUB_MIRROR` (see `mirror.md`) to mirror the + GitHub source install. diff --git a/docs/mirror.md b/docs/mirror.md index ab91bd8..4cdb080 100644 --- a/docs/mirror.md +++ b/docs/mirror.md @@ -53,6 +53,47 @@ $env:FLUTTER_STORAGE_BASE_URL = "https://storage.flutter-io.cn" On Linux ARM64 and Windows ARM64, the plugin installs Flutter from git source instead of a prebuilt archive. -If `https://github.com` sends no data within 10s, -the plugin automatically falls back to `https://gh-proxy.org/`. -Once the transfer starts it is never interrupted. +If you have difficulty accessing `https://github.com`, +you can set the `VFOX_FLUTTER_GITHUB_MIRROR` environment variable to use a mirror +instead. The value is a URL prefix that is prepended to the GitHub URL. + +Common mirror values: + +| Mirror | Value | +|----------------------|--------------------------| +| gh-proxy | `https://gh-proxy.org/` | + +For Bash, + +1. You can make the setting take effect temporarily in the current shell using the following command. + +```bash +export VFOX_FLUTTER_GITHUB_MIRROR=https://gh-proxy.org/ +``` + +2. To ensure that environment variables always take effect, you can perform the following steps: + +```bash +sudo tee /etc/profile.d/myenvvars.sh < #data then - out[#out + 1] = "==" - elseif i + 2 > #data then - out[#out + 1] = B64CHARS:sub(math.floor(n / 64) % 64 + 1, math.floor(n / 64) % 64 + 1) .. "=" - else - out[#out + 1] = B64CHARS:sub(math.floor(n / 64) % 64 + 1, math.floor(n / 64) % 64 + 1) - .. B64CHARS:sub(n % 64 + 1, n % 64 + 1) - end +-- Rewrite a GitHub URL to use the configured mirror, if any. The mirror +-- value is a URL prefix (e.g. https://gh-proxy.org/) that is prepended +-- to the original URL. Non-GitHub URLs are returned unchanged. +function M.mirrorUrl(url) + if type(url) ~= "string" or url:sub(1, #GH_BASE) ~= GH_BASE then + return url end - return table.concat(out) -end - --- PowerShell -EncodedCommand needs UTF-16LE. The script is ASCII-only, so --- interleaving NUL bytes is sufficient. -local function utf16le(data) - return data:gsub("(.)", "%1" .. string.char(0)) -end - --- Probe whether the remote answers within the timeout. Success means the --- first byte arrived: once the transfer starts, the real fetch below runs --- without a timeout and is never interrupted. -local function probeWithTimeoutUnix(remote, ref, timeout) - local gitCmd = "git ls-remote --exit-code " .. quote(remote) .. " " .. quote(ref) - -- Note: %%s becomes %s for the shell, %d is the Lua format arg for timeout - local script = string.format( - [[tmp=$(mktemp /tmp/vfox_probe_XXXXXX) -start=$(date +%%s) -end=$((start + %d)) -%s >"$tmp" 2>/dev/null & -pid=$! -while kill -0 $pid 2>/dev/null; do - if [ -s "$tmp" ]; then - kill $pid 2>/dev/null - wait $pid 2>/dev/null - rm -f "$tmp" - printf "\r%%*s\r" 40 "" - printf "\n" - exit 0 - fi - now=$(date +%%s) - remaining=$((end - now)) - if [ $remaining -le 0 ]; then - kill $pid 2>/dev/null - wait $pid 2>/dev/null - rm -f "$tmp" - printf "\r%%*s\r" 40 "" - printf "\n" - exit 124 - fi - printf "\rTimeout in %%3ds... " $remaining - sleep 1 -done -wait $pid -rc=$? -rm -f "$tmp" -printf "\r%%*s\r" 40 "" -printf "\n" -exit $rc -]], timeout, gitCmd) - return exec(script) -end - -local function probeWithTimeoutWindows(remote, ref, timeout) - -- The plugin runtime does not process shell quotes on Windows (a quoted - -- -File path arrives literally and fails with "Illegal characters in - -- path"), so the script is passed via -EncodedCommand instead: base64 - -- has no spaces or quotes and survives any argv splitting. - local function psArg(value) - return '"' .. value:gsub('"', '""') .. '"' + local prefix = os.getenv(GITHUB_MIRROR_ENV) + if prefix == nil or prefix == "" then + return url end - local script = string.format([[ -$tmp = Join-Path ([IO.Path]::GetTempPath()) ("vfox_probe_" + $PID + ".out") -$proc = Start-Process -FilePath "git" -ArgumentList @("ls-remote", "--exit-code", %s, %s) -RedirectStandardOutput $tmp -RedirectStandardError "NUL" -PassThru -NoNewWindow -$deadline = (Get-Date).AddSeconds(%d) -while (-not $proc.HasExited) { - if ((Test-Path $tmp) -and ((Get-Item $tmp).Length -gt 0)) { - try { $proc.Kill() } catch {} - Remove-Item -Force $tmp - Write-Host "" - exit 0 - } - $remaining = [int]($deadline - (Get-Date)).TotalSeconds - if ($remaining -le 0) { - try { $proc.Kill() } catch {} - Remove-Item -Force $tmp -ErrorAction SilentlyContinue - Write-Host "" - exit 124 - } - Write-Host -NoNewline ("`rTimeout in " + $remaining + " s... ") - Start-Sleep -Seconds 1 -} -Remove-Item -Force $tmp -ErrorAction SilentlyContinue -Write-Host "" -exit $proc.ExitCode -]], psArg(remote), psArg(ref), timeout) - return exec("powershell -NoProfile -NoLogo -ExecutionPolicy Bypass -EncodedCommand " - .. base64(utf16le(script))) -end - -local function probeWithTimeout(remote, ref, timeout) - io.write(string.format("Connecting to %s (timeout: %ds)...\n", remote, timeout)) - io.flush() - if RUNTIME.osType == "windows" then - return probeWithTimeoutWindows(remote, ref, timeout) + if prefix:sub(-1) ~= "/" then + prefix = prefix .. "/" end - return probeWithTimeoutUnix(remote, ref, timeout) + return prefix .. url end function M.removeDir(path) @@ -219,7 +112,7 @@ function M.init(root) return git(root, "config core.longpaths true") end -local function fetchPlain(root, remote, ref) +function M.fetch(root, remote, ref) for _ = 1, FETCH_ATTEMPTS do if git(root, "fetch -q --depth 1 " .. remote .. " " .. ref) then return true @@ -228,25 +121,6 @@ local function fetchPlain(root, remote, ref) return false end -function M.fetch(root, remote, ref) - if not isGithubUrl(remote) then - return fetchPlain(root, remote, ref) - end - local remotes = { remote, GH_PROXY_PREFIX .. remote } - for i, url in ipairs(remotes) do - if probeWithTimeout(url, ref, FETCH_TIMEOUT) then - if fetchPlain(root, url, ref) then - return true - end - end - if i < #remotes then - io.write(string.format("Cannot reach %s, trying %s...\n", url, remotes[i + 1])) - io.flush() - end - end - return false -end - function M.checkoutHead(root) return git(root, "checkout -q FETCH_HEAD") end diff --git a/lib/source.lua b/lib/source.lua index 3d0d897..ed1ac37 100644 --- a/lib/source.lua +++ b/lib/source.lua @@ -52,7 +52,12 @@ function M.checkout(baseVersion, versionName) if dir == nil then error("cannot resolve the vfox home directory") end - local cloneUrl = M.repoUrl() + local repoUrl = M.repoUrl() + local cloneUrl = git.mirrorUrl(repoUrl) + if cloneUrl ~= repoUrl then + io.write(string.format("Using GitHub mirror %s\n", cloneUrl)) + io.flush() + end git.resetDir(dir) if not git.init(dir) then error("failed to initialize git in " .. dir .. " (is git installed?)") diff --git a/tests/e2e/linux/run.sh b/tests/e2e/linux/run.sh index c7cd517..21d1659 100755 --- a/tests/e2e/linux/run.sh +++ b/tests/e2e/linux/run.sh @@ -44,6 +44,22 @@ check_bogus_mirror_rejected() { assert_contains "$bogus_output" "invalid.example.invalid" 'bogus mirror error' } +# On ARM64 the official flavour installs from git source; a bogus +# $VFOX_FLUTTER_GITHUB_MIRROR must fail naming the host, proving the plugin +# honours it instead of falling back to https://github.com. +check_bogus_github_mirror_rejected() { + local flavor="$1" + local version="$2" + if [[ "$flavor" != "official" || "$(uname --machine)" != "aarch64" ]]; then + return 0 + fi + local bogus_mirror="https://invalid.example.invalid" + local bogus_output bogus_code=0 + bogus_output="$(VFOX_FLUTTER_GITHUB_MIRROR="$bogus_mirror" vfox install flutter@"$version" 2>&1)" || bogus_code=$? + ((bogus_code != 0)) || die "bogus GitHub mirror install unexpectedly succeeded" + assert_contains "$bogus_output" "invalid.example.invalid" 'bogus GitHub mirror error' +} + main() { local flavor mirror version flavor="$(require_env FLAVOR)" @@ -55,6 +71,7 @@ main() { check_mirror_reachable "$flavor" "$mirror" setup_vfox check_bogus_mirror_rejected "$flavor" "$version" + check_bogus_github_mirror_rejected "$flavor" "$version" bash "$here/install.sh" "$version" activate_vfox diff --git a/tests/e2e/windows/preflight.Tests.ps1 b/tests/e2e/windows/preflight.Tests.ps1 index cb50158..d421e37 100644 --- a/tests/e2e/windows/preflight.Tests.ps1 +++ b/tests/e2e/windows/preflight.Tests.ps1 @@ -40,4 +40,25 @@ Describe 'mirror preflight' { $bogusCode | Should -Not -Be 0 $bogusOutput | Should -Match ([regex]::Escape('invalid.example.invalid')) } + + It 'rejects a bogus GitHub mirror naming the host' { + if ($Flavor -ne 'official') { + Set-ItResult -Skipped -Because 'only the official flavour installs from git source' + } + if ($env:PROCESSOR_ARCHITECTURE -ne 'ARM64') { + Set-ItResult -Skipped -Because 'only ARM64 installs from git source' + } + $origMirror = $env:VFOX_FLUTTER_GITHUB_MIRROR + try { + $env:VFOX_FLUTTER_GITHUB_MIRROR = 'https://invalid.example.invalid' + $PSNativeCommandUseErrorActionPreference = $false + $bogusOutput = (& vfox install "flutter@$(Resolve-FlutterVersion $Flavor)" 2>&1 | Out-String) + $bogusCode = $LASTEXITCODE + $PSNativeCommandUseErrorActionPreference = $true + } finally { + if ($null -eq $origMirror) { Remove-Item Env:\VFOX_FLUTTER_GITHUB_MIRROR } else { $env:VFOX_FLUTTER_GITHUB_MIRROR = $origMirror } + } + $bogusCode | Should -Not -Be 0 + $bogusOutput | Should -Match ([regex]::Escape('invalid.example.invalid')) + } }