Skip to content
Closed
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ sudo apt install --assume-yes curl git unzip xz-utils zip libglu1-mesa
vfox use --global flutter@3.47.4
```

`sudo apt install --assume-yes curl git unzip xz-utils zip libglu1-mesa` comes from https://docs.flutter.dev/install/manual#download-prerequisites ,
these are prerequisite packages not included with the Flutter SDK.
You can verify this using the following command:

```bash
Expand Down
2 changes: 0 additions & 2 deletions docs/ohos.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ vfox search flutter
- **`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.
- **A Windows home containing a space** (`C:\Users\John Doe\.vfox`) is not supported;
point vfox at a path without spaces with `$env:VFOX_HOME = 'D:\vfox'`.
- **The first `flutter` command is slow.** It builds the tool and downloads the Dart SDK
from Huawei's OBS, so it needs a network connection.
- **No checksum is verified.** Git history is the integrity check, and this is a
Expand Down
77 changes: 50 additions & 27 deletions lib/git.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,16 @@ local function quote(value)
return "'" .. value:gsub("'", "'\\''") .. "'"
end

-- PowerShell single-quoted strings are literal: 'text' escapes ' as ''.
local function psQuote(value)
return "'" .. value:gsub("'", "''") .. "'"
end

local function exec(command)
local result = os.execute(command)
return result == 0 or result == true
end

local function git(root, args)
return exec("git -C " .. quote(root) .. " " .. args)
end

local function sep()
if RUNTIME.osType == "windows" then
return "\\"
end
return "/"
end

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)
Expand Down Expand Up @@ -70,6 +56,35 @@ local function utf16le(data)
return data:gsub("(.)", "%1" .. string.char(0))
end

-- Run a PowerShell script via -EncodedCommand (base64 UTF-16LE). The encoded
-- string has no spaces or quotes, so Go's argv re-quoting cannot mangle it.
local function execPS(script)
return exec("powershell -NoProfile -NoLogo -ExecutionPolicy Bypass -EncodedCommand "
.. base64(utf16le(script)))
end

local function git(root, args)
if RUNTIME.osType == "windows" then
return execPS("& git -C " .. psQuote(root) .. " " .. args)
end
return exec("git -C " .. quote(root) .. " " .. args)
end

local function sep()
if RUNTIME.osType == "windows" then
return "\\"
end
return "/"
end

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

-- 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.
Expand Down Expand Up @@ -147,8 +162,7 @@ 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)))
return execPS(script)
end

local function probeWithTimeout(remote, ref, timeout)
Expand All @@ -162,10 +176,9 @@ end

function M.removeDir(path)
if RUNTIME.osType == "windows" then
exec('if exist "' .. path .. '" rmdir /s /q "' .. path .. '"')
else
exec("rm -rf " .. quote(path))
return execPS("Remove-Item -Recurse -Force " .. psQuote(path) .. " -ErrorAction SilentlyContinue")
end
return exec("rm -rf " .. quote(path))
end

local function vfoxHome()
Expand Down Expand Up @@ -197,13 +210,14 @@ local function parentDir(dir)
end

local function makeParentDir(dir)
if RUNTIME.osType == "windows" then
return
end
local parent = parentDir(dir)
if parent == nil then
return
end
if RUNTIME.osType == "windows" then
execPS("New-Item -ItemType Directory -Force " .. psQuote(parent) .. " | Out-Null")
return
end
exec("mkdir -p " .. quote(parent))
end

Expand All @@ -213,6 +227,15 @@ function M.resetDir(dir)
end

function M.init(root)
if RUNTIME.osType == "windows" then
local script = string.format([[
& git init -q %s
if ($LASTEXITCODE -ne 0) { exit 1 }
& git -C %s config core.longpaths true
exit $LASTEXITCODE
]], psQuote(root), psQuote(root))
return execPS(script)
end
if not exec("git init -q " .. quote(root)) then
return false
end
Expand Down
60 changes: 50 additions & 10 deletions lib/manifest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,40 @@ local function joinPath(a, b)
return a .. sep() .. b
end

-- vfox feeds command strings to cmd.exe /c on Windows. Go has to re-quote the
-- whole command line, which turns a double quote into \" so cmd.exe reads a
-- quoted path back as the volume-relative path \path\ and rejects it with
-- "The filename, directory name, or volume label syntax is incorrect." Match
-- lib/git.lua and pass paths unquoted there; a path with a space then simply
-- skips the drift check instead of breaking vfox use.
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
end
return table.concat(out)
end

local function utf16le(data)
return data:gsub("(.)", "%1" .. string.char(0))
end

-- PowerShell single-quoted strings are literal: 'text' escapes ' as ''.
local function psQuote(value)
return "'" .. value:gsub("'", "''") .. "'"
end

-- Shell single-quote quoting. On Windows, capture wraps commands in PowerShell
-- with psQuote instead; this function is for Unix only.
local function quote(value)
if RUNTIME.osType == "windows" then
return value
Expand All @@ -28,7 +56,12 @@ end

local function capture(command)
local function run()
local pipe = io.popen(command)
local shellCmd = command
if RUNTIME.osType == "windows" then
shellCmd = "powershell -NoProfile -NoLogo -ExecutionPolicy Bypass -EncodedCommand "
.. base64(utf16le(command))
end
local pipe = io.popen(shellCmd)
if pipe == nil then
return nil
end
Expand Down Expand Up @@ -77,7 +110,7 @@ function M.gitRoot(sdkRoot)
end
local listCmd
if RUNTIME.osType == "windows" then
listCmd = "dir /b /ad " .. quote(sdkRoot)
listCmd = "Get-ChildItem -Directory " .. psQuote(sdkRoot) .. " | Select-Object -ExpandProperty Name"
else
listCmd = "find " .. quote(sdkRoot) .. " -maxdepth 1 -mindepth 1 -type d -printf '%f\\n'"
end
Expand All @@ -97,12 +130,19 @@ function M.gitRoot(sdkRoot)
return nil
end

local function gitRevParse(root)
if RUNTIME.osType == "windows" then
return capture("& git -C " .. psQuote(root) .. " rev-parse HEAD")
end
return capture("git -C " .. quote(root) .. " rev-parse HEAD")
end

function M.currentHead(sdkRoot)
local root = M.gitRoot(sdkRoot)
if root == nil then
return nil
end
return capture("git -C " .. quote(root) .. " rev-parse HEAD")
return gitRevParse(root)
end

function M.manifestPath(sdkRoot)
Expand All @@ -118,7 +158,7 @@ function M.write(sdkRoot, info)
if root == nil then
return false
end
local head = capture("git -C " .. quote(root) .. " rev-parse HEAD")
local head = gitRevParse(root)
local data = {
plugin = "vfox-flutter",
plugin_version = (PLUGIN and PLUGIN.version) or "unknown",
Expand Down
14 changes: 12 additions & 2 deletions tests/e2e/windows/e2e.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,26 @@ foreach ($vfox in $foxes) {
foreach ($flavor in $flavours) {
foreach ($mirror in $mirrorList) {
if ((-not $mirrorExplicit) -and ($mirror -ne 'default') -and ($vfox -ne 'latest')) { continue }
$combos.Add([pscustomobject]@{ Vfox = $vfox; Flavor = $flavor; Mirror = $mirror })
$combos.Add([pscustomobject]@{ Vfox = $vfox; Flavor = $flavor; Mirror = $mirror; Spaces = 'normal' })
}
}
}
# One combo with a VFOX_HOME path containing a space, to exercise the
# PowerShell -EncodedCommand quoting fix. Only on x64: arm64 runs official
# only, which downloads a prebuilt archive and never touches git paths.
if ($arch -eq 'amd64') {
$combos.Add([pscustomobject]@{ Vfox = 'latest'; Flavor = 'ohos'; Mirror = 'default'; Spaces = 'yes' })
}

$maxJobs = 2
if ($env:E2E_MAX_JOBS) { $maxJobs = [int]$env:E2E_MAX_JOBS }
if ($maxJobs -lt 1) { $maxJobs = 1 }

function Get-ComboSlug {
param([object] $Combo)
return ($Combo.Vfox + '-' + $Combo.Flavor + '-' + ($Combo.Mirror -replace '[^A-Za-z0-9]', '_'))
$slug = ($Combo.Vfox + '-' + $Combo.Flavor + '-' + ($Combo.Mirror -replace '[^A-Za-z0-9]', '_'))
if ($Combo.Spaces -eq 'yes') { $slug += '-spaces' }
return $slug
}

function Get-ContainerExitCode {
Expand All @@ -65,13 +73,15 @@ for ($start = 0; $start -lt $combos.Count; $start += $maxJobs) {
$flavor = $combo.Flavor
$mirror = $combo.Mirror
$prefix = "vfox $vfox, $flavor, mirror $mirror, $platform"
if ($combo.Spaces -eq 'yes') { $prefix += ', spaces' }
Write-Output "[$prefix] === start ==="

$slug = Get-ComboSlug $combo
$runArgs = @(
'run', '-d', '--platform', $platform,
'-e', "VFOX_VERSION=$vfox", '-e', "FLAVOR=$flavor",
'-e', "VFOX_E2E_SLOT=$slug")
if ($combo.Spaces -eq 'yes') { $runArgs += @('-e', "VFOX_E2E_SPACES=yes") }
if ($mirror -ne 'default') { $runArgs += @('-e', "FLUTTER_STORAGE_BASE_URL=$mirror") }
$runArgs += $Image

Expand Down
6 changes: 5 additions & 1 deletion tests/e2e/windows/setup.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ $slot = if ($env:VFOX_E2E_SLOT) { $env:VFOX_E2E_SLOT } else { 'default' }
$slotRoot = Join-Path $env:USERPROFILE "vfox-e2e-runs\$slot"
New-Item -ItemType Directory -Force -Path (Join-Path $slotRoot 'tmp') | Out-Null
$env:USERPROFILE = $slotRoot
$env:VFOX_HOME = Join-Path $slotRoot '.vfox'
if ($env:VFOX_E2E_SPACES -eq 'yes') {
$env:VFOX_HOME = "$slotRoot\.vfox with space"
} else {
$env:VFOX_HOME = Join-Path $slotRoot '.vfox'
}
$env:TEMP = Join-Path $slotRoot 'tmp'
$env:TMP = $env:TEMP

Expand Down
Loading