Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions docs/arm64.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
47 changes: 44 additions & 3 deletions docs/mirror.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <<EOF
export VFOX_FLUTTER_GITHUB_MIRROR="https://gh-proxy.org/"
EOF

source /etc/profile.d/myenvvars.sh
```

For PowerShell 7,

1. You can make the setting take effect temporarily in the current shell using the following command.

```powershell
$env:VFOX_FLUTTER_GITHUB_MIRROR = "https://gh-proxy.org/"
```

2. To ensure that environment variables always take effect, you can perform the following steps:

```powershell
[Environment]::SetEnvironmentVariable('VFOX_FLUTTER_GITHUB_MIRROR', 'https://gh-proxy.org/', 'Machine')
```

The mirror only rewrites URLs that start with `https://github.com/`,
so the `FLUTTER_STORAGE_BASE_URL` variable above is unaffected.
6 changes: 3 additions & 3 deletions docs/ohos.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ 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` (see `mirror.md`) only affects
official SDK downloads; OpenHarmony builds are cloned from gitcode, so the variable
is ignored.
- **Mirrors don't apply.** `FLUTTER_STORAGE_BASE_URL` and `VFOX_FLUTTER_GITHUB_MIRROR` (see `mirror.md`) only affect
official SDK downloads; OpenHarmony builds are cloned from gitcode, so both variables
are ignored.

### Building OpenHarmony applications

Expand Down
154 changes: 14 additions & 140 deletions lib/git.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ local FALLBACK_HOME_ENV = { "HOME", "USERPROFILE" }
local VFOX_DIR = ".vfox"
local TMP_DIR = "tmp"
local ENGINE_PIN = "bin/internal/engine.version"
local FETCH_TIMEOUT = 10
local FETCH_ATTEMPTS = 3
local GH_PROXY_PREFIX = "https://gh-proxy.org/"
local GITHUB_MIRROR_ENV = "VFOX_FLUTTER_GITHUB_MIRROR"
local GH_BASE = "https://github.com/"

local M = {}
Expand Down Expand Up @@ -37,127 +36,21 @@ local function localPath(path)
return path:gsub("/", sep())
end

local function isGithubUrl(url)
return type(url) == "string" and url:sub(1, #GH_BASE) == GH_BASE
end

local B64CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

local function base64(data)
local out = {}
for i = 1, #data, 3 do
local a = data:byte(i)
local b = data:byte(i + 1) or 0
local c = data:byte(i + 2) or 0
local n = a * 65536 + b * 256 + c
out[#out + 1] = B64CHARS:sub(math.floor(n / 262144) % 64 + 1, math.floor(n / 262144) % 64 + 1)
.. B64CHARS:sub(math.floor(n / 4096) % 64 + 1, math.floor(n / 4096) % 64 + 1)
if i + 1 > #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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
7 changes: 6 additions & 1 deletion lib/source.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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?)")
Expand Down
17 changes: 17 additions & 0 deletions tests/e2e/linux/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions tests/e2e/windows/preflight.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
}
}
Loading