Merge pull request #5342 from jandubois/ci-plain-windows-jobs

CI: factor Windows setup into composite actions; add plain-Windows jobs
This commit is contained in:
Akihiro Suda authored and GitHub committed 2026-10-03 05:20:36 +09:00
commit b1fab39fd9
13 files changed
+568 -66

No files matched your search

@@ -0,0 +1,27 @@
name: 'windows msys2 prep'
description: >
Adds C:\msys64\usr\bin to PATH for the rest of the job, unit tests and
make included, and installs the MSYS2 packages the Windows integration
jobs need.
runs:
using: composite
steps:
- name: Prepend MSYS2 to PATH and install test dependencies
shell: pwsh
# Writes only the static literal C:\msys64\usr\bin; no PR-controlled data
# reaches the env file.
run: | # zizmor: ignore[github-env]
$ErrorActionPreference = 'Stop'
Add-Content -Path $env:GITHUB_PATH -Value 'C:\msys64\usr\bin'
# rsync: without it the copy test skips its rsync backend, so the Windows
# path conversion that backend performs goes unexercised.
# openssh: keeps a Cygwin ssh ahead of %SystemRoot%\System32\OpenSSH, so
# these jobs cover the Cygwin toolchain and the plain jobs the native one.
& 'C:\msys64\usr\bin\pacman.exe' -Sy --noconfirm diffutils jq openssh rsync socat w3m
if ($LASTEXITCODE -ne 0) { throw "pacman failed: $LASTEXITCODE" }
# Lima's ssh selection skips an ssh.exe without scp.exe and ssh-keygen.exe
# beside it, and reverse-sshfs takes the sftp-server from the same install.
foreach ($exe in 'bin\ssh.exe', 'bin\scp.exe', 'bin\ssh-keygen.exe', 'lib\ssh\sftp-server.exe') {
$exePath = Join-Path 'C:\msys64\usr' $exe
if (-not (Test-Path $exePath)) { throw "missing: $exePath" }
}
@@ -0,0 +1,24 @@
name: 'windows plain build'
description: >
Builds limactl.exe and the Linux/amd64 guest agent with `go build`, without
make. limactl needs the guest agent to start an instance, and
`go build ./cmd/limactl` does not produce it.
Without make's -X ldflag the version stays "<unknown>". Lima treats an
unparsable version as newer than any other, so default.yaml's
minimumLimaVersion accepts these builds.
runs:
using: composite
steps:
- name: Build limactl and Linux/amd64 guest agent
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
go build -o _output\bin\limactl.exe .\cmd\limactl
if ($LASTEXITCODE -ne 0) { throw "limactl build failed: $LASTEXITCODE" }
New-Item -ItemType Directory -Force -Path _output\share\lima | Out-Null
$env:CGO_ENABLED = '0'
$env:GOOS = 'linux'
$env:GOARCH = 'amd64'
go build -o _output\share\lima\lima-guestagent.Linux-x86_64 .\cmd\lima-guestagent
if ($LASTEXITCODE -ne 0) { throw "lima-guestagent build failed: $LASTEXITCODE" }
@@ -0,0 +1,172 @@
name: 'windows plain host'
description: >
Uninstalls MSYS2 and Git for Windows, then verifies they are gone and the
native OpenSSH client is present, so a runner-image change in either
direction fails the job.
Destructive and irreversible: it also rewrites the Machine and User PATH in
the registry, so run it only on an ephemeral runner.
Run AFTER steps that need git CLI (checkout, windows_plain_templates) and
BEFORE the build / smoke test steps.
runs:
using: composite
steps:
- name: Uninstall Git for Windows
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$uninstaller = 'C:\Program Files\Git\unins000.exe'
if (Test-Path $uninstaller) {
Write-Host "Running Git for Windows Inno Setup uninstaller..."
& $uninstaller /VERYSILENT /NORESTART /SUPPRESSMSGBOXES
# A copy of unins000 in %TEMP% (_unins.tmp, or _iu*.tmp in older Inno
# Setup) does the uninstall. unins000 waits for it, but the copy
# outlives unins000 to delete unins000.exe and the Git directory.
$deadline = (Get-Date).AddSeconds(120)
while ((Get-Date) -lt $deadline -and
(Get-Process -Name 'unins000', '_unins*', '_iu*' -ErrorAction SilentlyContinue)) {
Start-Sleep -Seconds 2
}
} else {
Write-Host "No Git for Windows uninstaller at $uninstaller; skipping."
}
foreach ($d in 'C:\Program Files\Git', 'C:\Program Files (x86)\Git') {
if (Test-Path $d) {
Write-Host "Removing leftover $d"
Remove-Item $d -Recurse -Force -ErrorAction SilentlyContinue
}
}
# pwsh does not wait for the GUI uninstaller or record its exit status,
# so the verify step below is the gate.
exit 0
- name: Uninstall MSYS2 and standalone mingw
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
# MSYS2 and the mingw dirs on this image are extracted trees with no
# uninstaller.
$procs = Get-Process | Where-Object {
try { $_.Modules.ModuleName -contains 'msys-2.0.dll' } catch { $false }
}
if ($procs) {
Write-Host "Stopping $($procs.Count) process(es) still mapping msys-2.0.dll"
$procs | Stop-Process -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 1
}
foreach ($d in 'C:\msys64', 'C:\tools\msys64', 'C:\msys32',
'C:\mingw64', 'C:\mingw32') {
if (Test-Path $d) {
Write-Host "Removing $d"
Remove-Item $d -Recurse -Force -ErrorAction SilentlyContinue
}
}
- name: Scrub forbidden entries from Machine + User PATH
shell: pwsh
# Publishes the runner's own Machine + User PATH minus a static deny-list;
# no PR-controlled data reaches the env file.
run: | # zizmor: ignore[github-env]
$ErrorActionPreference = 'Stop'
# The uninstall leaves stale entries in the Machine and User PATH.
# Registry changes do not reach the running runner, so also publish the
# result via $GITHUB_ENV. $GITHUB_PATH entries from earlier steps are
# still prepended on top of it, out of the scrub's reach.
$forbidden = '(?i)(msys|mingw|cygwin|\\Git\\(cmd|bin|usr))'
foreach ($scope in @('Machine','User')) {
$orig = [Environment]::GetEnvironmentVariable('Path', $scope)
if (-not $orig) { continue }
$cleaned = ($orig -split ';' | Where-Object { $_ -and ($_ -notmatch $forbidden) }) -join ';'
if ($cleaned -ne $orig) {
Write-Host "Scrubbing $scope PATH"
[Environment]::SetEnvironmentVariable('Path', $cleaned, $scope)
}
}
$machine = [Environment]::GetEnvironmentVariable('Path','Machine')
$user = [Environment]::GetEnvironmentVariable('Path','User')
$combined = (@($machine, $user) | Where-Object { $_ }) -join ';'
Add-Content -Path $env:GITHUB_ENV -Value "PATH=$combined"
- name: Verify host is vanilla Windows (no MSYS2 / Git for Windows / Cygwin)
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
# Check the inherited PATH, which is what later steps see. Rebuilding it
# from the registry would only repeat the scrub step's own read.
$fail = @()
# --- Layer 1: filesystem ---
$fsForbidden = @(
'C:\msys64', 'C:\tools\msys64', 'C:\msys32',
'C:\Program Files\Git', 'C:\Program Files (x86)\Git',
'C:\mingw64', 'C:\mingw32',
'C:\cygwin', 'C:\cygwin64', 'C:\tools\cygwin'
)
foreach ($d in $fsForbidden) {
if (Test-Path $d) { $fail += "filesystem: $d still exists" }
}
# --- Layer 2: PATH (process + machine + user) ---
$pathRegex = '(?i)(msys|mingw|cygwin|\\Git\\(cmd|bin|usr))'
foreach ($scope in @('Process','Machine','User')) {
$p = [Environment]::GetEnvironmentVariable('Path', $scope)
if (-not $p) { continue }
foreach ($entry in $p -split ';') {
if ($entry -and ($entry -match $pathRegex)) {
$fail += ("PATH ({0}): {1}" -f $scope, $entry)
}
}
}
# --- Layer 3: smoking-gun binaries ---
# WSL installs %SystemRoot%\System32\bash.exe; allow that bash only.
$forbiddenCmds = @('cygpath','pacman','mintty','git','git-bash','git-cmd')
foreach ($cmd in $forbiddenCmds) {
$g = Get-Command $cmd -ErrorAction SilentlyContinue
if ($g) { $fail += ("binary: {0} -> {1}" -f $cmd, $g.Source) }
}
$allowedBash = Join-Path $env:SystemRoot 'System32\bash.exe'
$bash = Get-Command bash -ErrorAction SilentlyContinue
if ($bash -and ($bash.Source -ne $allowedBash)) {
$fail += "binary: bash -> $($bash.Source) (only $allowedBash is allowed)"
}
$sh = Get-Command sh -ErrorAction SilentlyContinue
if ($sh) { $fail += "binary: sh -> $($sh.Source)" }
# --- Layer 4: registry uninstall keys ---
$uninstallRoots = @(
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
)
foreach ($root in $uninstallRoots) {
if (-not (Test-Path $root)) { continue }
$keys = Get-ChildItem $root -ErrorAction SilentlyContinue
foreach ($k in $keys) {
$props = Get-ItemProperty $k.PSPath -ErrorAction SilentlyContinue
if ($props -and $props.DisplayName -and
($props.DisplayName -match '(?i)(^git$|msys|git for windows|cygwin|mingw)')) {
$fail += ("registry: {0} -> DisplayName='{1}'" -f $k.PSPath, $props.DisplayName)
}
}
}
# --- Layer 5: the toolchain these jobs do need ---
# Check up front. A missing ssh.exe fails later with an opaque exec
# error, a missing scp.exe or ssh-keygen.exe makes Lima fall back to a
# bare ssh lookup, and a missing sftp-server.exe drops reverse-sshfs to
# the builtin driver.
$opensshDir = Join-Path $env:SystemRoot 'System32\OpenSSH'
foreach ($exe in 'ssh.exe', 'scp.exe', 'ssh-keygen.exe', 'sftp-server.exe') {
$exePath = Join-Path $opensshDir $exe
if (-not (Test-Path $exePath)) { $fail += "missing: $exePath" }
}
if ($fail.Count -gt 0) {
Write-Host "Plain-Windows verification FAILED:" -ForegroundColor Red
$fail | ForEach-Object { Write-Host " $_" }
Write-Error "Runner is not in the expected plain-Windows state; see the list above."
exit 1
}
Write-Host "Plain-Windows verification PASSED."
@@ -0,0 +1,78 @@
name: 'windows plain smoke test'
description: >
Runs Lima's create / start / shell / copy / stop / delete cycle against a
given template on a plain Windows host, and dumps the instance logs on
failure.
inputs:
template:
description: Path to the Lima template (e.g. .\templates\experimental\wsl2.yaml).
required: true
instance-name:
description: Name to use for the test instance.
required: false
default: plain
vm-type:
description: >
Lima vmType to force via `--vm-type` (e.g. "qemu" or "wsl2"). When empty,
the template or Lima's default decides.
required: false
default: ''
runs:
using: composite
steps:
- name: Smoke test (create / start / shell / copy / stop / delete)
shell: pwsh
env:
LIMA_HOME: ${{ runner.temp }}\lima
INSTANCE: ${{ inputs.instance-name }}
TEMPLATE: ${{ inputs.template }}
VM_TYPE: ${{ inputs.vm-type }}
run: |
$ErrorActionPreference = 'Stop'
# $ErrorActionPreference does not stop on a native command's exit code.
function Invoke-Limactl {
& .\_output\bin\limactl.exe --debug @args
if ($LASTEXITCODE -ne 0) { throw "limactl $($args -join ' ') exited $LASTEXITCODE" }
}
if (Test-Path $env:LIMA_HOME) { Remove-Item $env:LIMA_HOME -Recurse -Force }
New-Item -ItemType Directory -Path $env:LIMA_HOME | Out-Null
$createArgs = @('--tty=false', "--name=$env:INSTANCE")
if ($env:VM_TYPE) { $createArgs += "--vm-type=$env:VM_TYPE" }
$createArgs += $env:TEMPLATE
Invoke-Limactl create @createArgs
Invoke-Limactl start $env:INSTANCE
Invoke-Limactl shell --tty=false $env:INSTANCE -- uname -srm
'roundtrip' | Out-File -Encoding ascii "$env:LIMA_HOME\rt.txt"
Invoke-Limactl copy "$env:LIMA_HOME\rt.txt" "${env:INSTANCE}:/tmp/rt.txt"
Invoke-Limactl shell --tty=false $env:INSTANCE -- cat /tmp/rt.txt
# Cover the guest -> host direction too.
Invoke-Limactl copy "${env:INSTANCE}:/tmp/rt.txt" "$env:LIMA_HOME\rt-back.txt"
# -Raw yields $null for an empty file, and $null.Trim() would throw
# before the mismatch message a zero-byte copy is meant to produce.
$back = "$(Get-Content "$env:LIMA_HOME\rt-back.txt" -Raw)".Trim()
if ($back -ne 'roundtrip') {
throw "round-trip mismatch: expected 'roundtrip', got '$back'"
}
Invoke-Limactl stop $env:INSTANCE
Invoke-Limactl delete --force $env:INSTANCE
- name: Dump Lima logs on failure
if: failure()
shell: pwsh
env:
LIMA_HOME: ${{ runner.temp }}\lima
INSTANCE: ${{ inputs.instance-name }}
run: |
$instDir = Join-Path $env:LIMA_HOME $env:INSTANCE
if (-not (Test-Path $instDir)) {
Write-Host "No instance directory at $instDir, nothing to dump."
exit 0
}
Get-ChildItem $instDir | Format-Table Name, Length, LastWriteTime
foreach ($name in 'ha.stdout.log','ha.stderr.log','serial.log','lima.yaml','ssh.config') {
$p = Join-Path $instDir $name
if (Test-Path $p) {
Write-Host ("===== {0} =====" -f $p)
Get-Content $p
}
}
@@ -0,0 +1,74 @@
name: 'windows plain templates'
description: >
Resolves Lima's template symlinks and copies templates/ to
_output/share/lima/templates/, replicating `make`'s TEMPLATES target without
make.
A checkout writes the git symlinks under templates/ as NTFS symlinks or as
plaintext stubs, depending on core.symlinks, so the targets are read from
git's object store instead.
This action MUST run before windows_plain_host (which uninstalls Git for
Windows), because it shells out to `git ls-tree` and `git cat-file`.
runs:
using: composite
steps:
- name: Install templates (replicating make's TEMPLATES target)
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
throw "git not found; this action must run before windows_plain_host"
}
$srcRoot = 'templates'
$dstRoot = '_output\share\lima\templates'
if (Test-Path $dstRoot) { Remove-Item $dstRoot -Recurse -Force }
New-Item -ItemType Directory -Force -Path $dstRoot | Out-Null
Copy-Item -Path "$srcRoot\*" -Destination $dstRoot -Recurse -Force
$linkTargets = @{}
# Check ls-tree's exit code before the loop, whose `git cat-file` calls
# overwrite $LASTEXITCODE.
$lsTreeOutput = git ls-tree -r HEAD $srcRoot
if ($LASTEXITCODE -ne 0) { throw "git ls-tree failed: $LASTEXITCODE" }
foreach ($line in $lsTreeOutput) {
if ($line -notmatch '^120000\s') { continue }
$parts = $line -split "`t", 2
$sha = ($parts[0] -split '\s+')[2]
$posixPath = $parts[1]
# A failed cat-file writes only to stderr. Keep the value a string,
# since $null.Trim() would throw ahead of the check below.
$target = "$(git cat-file blob $sha)".Trim()
if ($LASTEXITCODE -ne 0) { throw "git cat-file $sha failed: $LASTEXITCODE" }
$linkTargets[$posixPath] = $target
}
foreach ($posixPath in $linkTargets.Keys) {
$resolved = $posixPath
$depth = 0
while ($linkTargets.ContainsKey($resolved)) {
if ($depth++ -gt 16) { throw "Symlink chain too deep starting from $posixPath" }
$target = $linkTargets[$resolved]
$slash = $resolved.LastIndexOf('/')
$resolved = if ($slash -ge 0) { "$($resolved.Substring(0, $slash))/$target" } else { $target }
$segs = New-Object System.Collections.ArrayList
foreach ($s in ($resolved -split '/')) {
if ($s -eq '' -or $s -eq '.') { continue }
if ($s -eq '..') {
if ($segs.Count -gt 0) { $segs.RemoveAt($segs.Count - 1) | Out-Null }
continue
}
$segs.Add($s) | Out-Null
}
$resolved = $segs -join '/'
}
# Defensive: the '..' handling above silently underflows. Guard against
# a future symlink edit whose chain resolves outside templates/.
if (-not $resolved.StartsWith("$srcRoot/")) {
throw "Symlink $posixPath resolved to $resolved, which escapes $srcRoot/"
}
$srcFile = $resolved -replace '/', '\'
$dstFile = Join-Path '_output\share\lima' ($posixPath -replace '/', '\')
Write-Host "Resolving symlink: $posixPath -> $resolved"
Copy-Item -Force -Path $srcFile -Destination $dstFile
}
@@ -0,0 +1,11 @@
name: 'windows qemu install'
description: Installs QEMU on the Windows runner via winget.
runs:
using: composite
steps:
- name: Install QEMU
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
winget install --silent --accept-source-agreements --accept-package-agreements --disable-interactivity SoftwareFreedomConservancy.QEMU
if ($LASTEXITCODE -ne 0) { throw "winget install QEMU failed: $LASTEXITCODE" }
@@ -0,0 +1,55 @@
name: 'windows wsl2 setup'
description: >
Enables WSL2 and imports a dummy distro, because `wsl --list --verbose`
(called from Lima) fails when no distro is installed (lima-vm/lima#1826).
WSL2 2.5.7.0 and later reject a distro tarball without /bin/sh and /etc.
runs:
using: composite
steps:
- name: Enable WSL2
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
# $ErrorActionPreference does not stop on a native command's exit code.
wsl --set-default-version 2
if ($LASTEXITCODE -ne 0) { throw "wsl --set-default-version failed: $LASTEXITCODE" }
wsl --shutdown
if ($LASTEXITCODE -ne 0) { throw "wsl --shutdown failed: $LASTEXITCODE" }
# --update and --list --online reach a Microsoft update service that
# returns transient 403s, so these four calls are informational.
wsl --update
wsl --status
wsl --version
wsl --list --online
# pwsh would otherwise exit with --list --online's status.
exit 0
- name: Install dummy WSL2 distro
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
# Keep the scratch tarball and the distro disk under $RUNNER_TEMP so no
# untracked files land in the checked-out workspace.
$scratch = Join-Path $env:RUNNER_TEMP 'wsl2-dummy'
New-Item -ItemType Directory -Force -Path "$scratch\bin","$scratch\etc" | Out-Null
Set-Content -Path "$scratch\bin\sh" -Value ''
$tarball = Join-Path $env:RUNNER_TEMP 'wsl2-dummy.tar'
tar -cf $tarball --format ustar -C $scratch .
if ($LASTEXITCODE -ne 0) { throw "tar -cf $tarball failed: $LASTEXITCODE" }
# Unchecked, because a fresh runner has nothing to unregister. A second
# run on a developer machine needs the unregister before the import.
wsl --unregister dummy
$distroDir = Join-Path $env:RUNNER_TEMP 'wsl2-dummy-distro'
New-Item -ItemType Directory -Force -Path $distroDir | Out-Null
# A composite action step cannot set timeout-minutes, so bound the
# import here. -ArgumentList joins its elements unquoted, so quote the
# two paths.
$p = Start-Process -FilePath wsl.exe -PassThru -NoNewWindow `
-ArgumentList '--import', 'dummy', "`"$distroDir`"", "`"$tarball`""
if (-not $p.WaitForExit(60000)) {
$p.Kill()
throw "wsl --import dummy timed out after 60s"
}
if ($p.ExitCode -ne 0) { throw "wsl --import dummy failed: $($p.ExitCode)" }
wsl --list --verbose
# Informational, like the listings in the step above.
exit 0
+74 -43
View File
@@ -147,36 +147,11 @@ jobs:
- name: Uninstall
run: sudo make uninstall
windows:
windows-wsl2:
name: "Windows tests (WSL2)"
runs-on: windows-2025
timeout-minutes: 45
steps:
- name: Enable WSL2
run: |
wsl --set-default-version 2
wsl --shutdown
wsl --update
wsl --status
wsl --version
wsl --list --online
- name: Install WSL2 distro
timeout-minutes: 1
run: |
# FIXME: At least one distro has to be installed here,
# otherwise `wsl --list --verbose` (called from Lima) fails:
# https://github.com/lima-vm/lima/pull/1826#issuecomment-1729993334
# The distro image itself is not consumed by Lima.
# Starting with WSL2 version 2.5.7.0 the distro will be rejected
# if it doesn't contain /bin/sh and /etc.
# ------------------------------------------------------------------
mkdir dummy
mkdir dummy\bin
mkdir dummy\etc
echo "" >dummy\bin\sh
tar -cf dummy.tar --format ustar -C dummy .
wsl --import dummy $env:TEMP dummy.tar
wsl --list --verbose
- name: Set gitconfig
run: |
git config --global core.autocrlf false
@@ -190,27 +165,26 @@ jobs:
with:
go-version: stable
cache: false
- uses: $/.github/actions/windows_wsl2_setup
- uses: $/.github/actions/windows_msys2_prep
- name: Unit tests
run: go test -v ./...
- name: Make
run: make
- name: Integration tests (WSL2, Windows host)
run: |
$env:PATH = "$pwd\_output\bin;" + 'C:\msys64\usr\bin;' + $env:PATH
pacman -Sy --noconfirm diffutils jq socat w3m
$env:MSYS2_ENV_CONV_EXCL = 'HOME_HOST;HOME_GUEST;_LIMA_WINDOWS_EXTRA_PATH'
$env:PATH = "$pwd\_output\bin;" + $env:PATH
$env:MSYS2_ENV_CONV_EXCL = 'HOME_HOST;HOME_GUEST'
$env:HOME_HOST = $(cygpath.exe "$env:USERPROFILE")
$env:HOME_GUEST = "/mnt$env:HOME_HOST"
$env:LIMACTL_CREATE_ARGS = '--vm-type=wsl2 --mount-type=wsl2 --containerd=system'
$env:_LIMA_WINDOWS_EXTRA_PATH = 'C:\Program Files\Git\usr\bin'
bash.exe -c "./hack/test-templates.sh templates/experimental/wsl2.yaml"
- name: Run BATS port-forwarding test (WSL2, Windows host)
run: |
$env:PATH = "$pwd\_output\bin;" + 'C:\msys64\usr\bin;' + $env:PATH
$env:MSYS2_ENV_CONV_EXCL = '_LIMA_WINDOWS_EXTRA_PATH;LIMA_BATS_PORT_FORWARDING_BASE_TEMPLATE'
$env:PATH = "$pwd\_output\bin;" + $env:PATH
$env:MSYS2_ENV_CONV_EXCL = 'LIMA_BATS_PORT_FORWARDING_BASE_TEMPLATE'
$env:LIMACTL_CREATE_ARGS = '--vm-type=wsl2 --mount-type=wsl2 --containerd=system'
$env:LIMA_BATS_PORT_FORWARDING_BASE_TEMPLATE = 'template:experimental/wsl2'
$env:_LIMA_WINDOWS_EXTRA_PATH = 'C:\Program Files\Git\usr\bin'
bash.exe -c "./hack/bats/lib/bats-core/bin/bats --timing ./hack/bats/tests/port-forwarding.bats"
windows-qemu:
@@ -231,29 +205,86 @@ jobs:
with:
go-version: stable
cache: false
- uses: $/.github/actions/windows_qemu_install
- uses: $/.github/actions/windows_msys2_prep
- name: Make
run: make
- name: Install QEMU
run: |
winget install --silent --accept-source-agreements --accept-package-agreements --disable-interactivity SoftwareFreedomConservancy.QEMU
- name: Integration tests (QEMU, Windows host)
run: |
$env:PATH = "$pwd\_output\bin;" + 'C:\msys64\usr\bin;' + 'C:\Program Files\QEMU;' + $env:PATH
pacman -Sy --noconfirm diffutils jq socat w3m
$env:MSYS2_ENV_CONV_EXCL = 'HOME_HOST;HOME_GUEST;_LIMA_WINDOWS_EXTRA_PATH'
$env:PATH = "$pwd\_output\bin;" + 'C:\Program Files\QEMU;' + $env:PATH
$env:MSYS2_ENV_CONV_EXCL = 'HOME_HOST;HOME_GUEST'
$env:HOME_HOST = $(cygpath.exe "$env:USERPROFILE")
$env:HOME_GUEST = "$env:HOME_HOST"
$env:LIMACTL_CREATE_ARGS = '--vm-type=qemu'
$env:_LIMA_WINDOWS_EXTRA_PATH = 'C:\Program Files\Git\usr\bin'
bash.exe -c "./hack/test-templates.sh templates/default.yaml"
- name: Run BATS port-forwarding test (QEMU, Windows host)
run: |
$env:PATH = "$pwd\_output\bin;" + 'C:\msys64\usr\bin;' + 'C:\Program Files\QEMU;' + $env:PATH
$env:MSYS2_ENV_CONV_EXCL = '_LIMA_WINDOWS_EXTRA_PATH'
$env:PATH = "$pwd\_output\bin;" + 'C:\Program Files\QEMU;' + $env:PATH
$env:LIMACTL_CREATE_ARGS = '--vm-type=qemu'
$env:_LIMA_WINDOWS_EXTRA_PATH = 'C:\Program Files\Git\usr\bin'
bash.exe -c "./hack/bats/lib/bats-core/bin/bats --timing ./hack/bats/tests/port-forwarding.bats"
windows-plain-wsl2:
# Smoke test of Lima's WSL2 driver on a host with only what a default
# Windows 10/11 install ships (native OpenSSH, wsl.exe, tar). If a step
# ever needs MSYS2 or Git for Windows back to pass, Lima has gained a new
# external-binary dependency, and that needs review first.
name: "Windows tests (WSL2, plain Windows host)"
runs-on: windows-2025
timeout-minutes: 30
steps:
- name: Set gitconfig
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: stable
cache: false
- uses: $/.github/actions/windows_wsl2_setup
# No windows_plain_templates: wsl2.yaml has no `base:`.
- uses: $/.github/actions/windows_plain_host
- uses: $/.github/actions/windows_plain_build
- uses: $/.github/actions/windows_plain_smoke_test
with:
template: .\templates\experimental\wsl2.yaml
windows-plain-qemu:
# Like windows-plain-wsl2, for the QEMU driver. The reverse-sshfs mount
# starts through the native sftp-server, but sshocker does not fail when
# the mount check fails, so the mount itself is not covered.
name: "Windows tests (QEMU, plain Windows host)"
runs-on: windows-2025
timeout-minutes: 30
steps:
- name: Set gitconfig
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: stable
cache: false
- uses: $/.github/actions/windows_qemu_install
- uses: $/.github/actions/windows_plain_templates
- uses: $/.github/actions/windows_plain_host
# No QEMU on PATH here: limactl appends the default QEMU install dir to
# its own PATH, and this job tests that.
- name: Check that QEMU is not on PATH
run: |
$qemu = Get-Command qemu-system-x86_64 -ErrorAction SilentlyContinue
if ($qemu) { throw "QEMU is on PATH: $($qemu.Source)" }
- uses: $/.github/actions/windows_plain_build
- uses: $/.github/actions/windows_plain_smoke_test
with:
template: .\templates\default.yaml
vm-type: qemu
qemu:
name: "Integration tests (QEMU, macOS host)"
runs-on: macos-15-large # Intel
-8
View File
@@ -40,14 +40,6 @@ func main() {
yq.MaybeRunYQ()
if runtime.GOOS == "windows" {
extras, hasExtra := os.LookupEnv("_LIMA_WINDOWS_EXTRA_PATH")
if hasExtra && strings.TrimSpace(extras) != "" {
p := os.Getenv("PATH")
err := os.Setenv("PATH", strings.TrimSpace(extras)+string(filepath.ListSeparator)+p)
if err != nil {
logrus.Warning("Can't add extras to PATH, relying entirely on system PATH")
}
}
p := os.Getenv("PATH")
newPath := envutil.AppendDirsToPath(p, envutil.PlatformCommonToolDirs)
if newPath != p {
+14 -1
View File
@@ -128,7 +128,20 @@ func TestRsyncCommandPaths(t *testing.T) {
// The paths are everything after the "--" separator.
sep := slices.Index(cmd.Args, "--")
assert.Assert(t, sep != -1, "rsync args must contain the %#q separator: %v", "--", cmd.Args)
assert.DeepEqual(t, cmd.Args[sep+1:], tt.expected)
// Command converts each local path for the rsync it runs. Convert
// without the trailing slash so a conversion that drops it still
// fails here.
expected := make([]string, len(tt.expected))
for i, p := range tt.expected {
trimmed := strings.TrimSuffix(p, "/")
expected[i], err = tool.pathForRsync(t.Context(), trimmed)
assert.NilError(t, err)
expected[i] += strings.TrimPrefix(p, trimmed)
}
assert.DeepEqual(t, cmd.Args[sep+1:], expected)
for _, arg := range cmd.Args[sep+1:] {
assert.Assert(t, !looksRemoteToRsync(arg), "rsync reads %q as host:path", arg)
}
})
}
}
+34
View File
@@ -83,6 +83,40 @@ func TestPickCompleteSSHOnWindows(t *testing.T) {
t.Setenv("PATH", mingit)
assert.Equal(t, pickCompleteSSHOnWindows(), nativeSSH)
})
t.Run("incomplete native install disqualifies it too", func(t *testing.T) {
fakeRoot := resolvedTempDir(t)
nativeDir := filepath.Join(fakeRoot, "System32", "OpenSSH")
assert.NilError(t, os.MkdirAll(nativeDir, 0o755))
assert.NilError(t, os.WriteFile(filepath.Join(nativeDir, "ssh.exe"), nil, 0o644))
t.Setenv("SystemRoot", fakeRoot)
t.Setenv("PATH", "")
assert.Equal(t, pickCompleteSSHOnWindows(), "",
"an ssh.exe without its companions never qualifies, wherever it lives")
})
}
// TestNewSSHExeFallsBackToPATH: when no directory holds a complete install,
// NewSSHExe takes whatever `ssh` PATH resolves.
func TestNewSSHExeFallsBackToPATH(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("Windows-only PATH walk")
}
partial := resolvedTempDir(t)
sshExe := filepath.Join(partial, "ssh.exe")
assert.NilError(t, os.WriteFile(sshExe, nil, 0o644))
t.Setenv("SystemRoot", resolvedTempDir(t))
t.Setenv("PATH", partial)
t.Setenv(EnvShellSSH, "")
assert.Equal(t, pickCompleteSSHOnWindows(), "", "the partial install must not qualify")
got, err := NewSSHExe()
assert.NilError(t, err)
assert.Equal(t, got.Exe, sshExe)
}
// TestCygpathForSSH: an ssh.exe next to cygpath.exe is Cygwin-based and
@@ -147,20 +147,6 @@ This page documents the environment variables used in Lima.
export LIMA_USERNET_RESOLVE_IP_ADDRESS_TIMEOUT=5
```
### `_LIMA_WINDOWS_EXTRA_PATH`
- **Description**: Additional directories which will be added to PATH by `limactl.exe` process to search for tools.
It is useful, when there is a need to prevent collisions between binaries available in active shell and ones
used by `limactl.exe` - injecting them only for the running process w/o altering PATH observed by user shell.
Is is Windows specific and does nothing for other platforms.
- **Default**: unset
- **Usage**:
```bat
set _LIMA_WINDOWS_EXTRA_PATH=C:\Program Files\Git\usr\bin
```
- **Note**: It is an experimental setting and has no guarantees being ever promoted to stable. It may be removed
or changed at any stage of project development.
### `QEMU_SYSTEM_AARCH64`
- **Description**: Path to the `qemu-system-aarch64` binary.
@@ -3,6 +3,11 @@ title: Breaking changes
weight: 20
---
## v2.3.0
- The experimental `_LIMA_WINDOWS_EXTRA_PATH` environment variable was removed. It prepended directories to
`limactl.exe`'s own PATH on Windows hosts, leaving the calling shell's PATH untouched. Put them on `PATH`
instead.
## v2.2.0
- The default [`socket_vmnet` group](../config/network/vmnet.md) in `networks.yaml` was changed from `everyone` to
`admin`. A non-admin user must set `group` to a group they belong to (e.g. `staff`).