Skip to content

Commit 83ab39c

Browse files
authored
Ci/check macos virtualization (#159)
* ci: add manual workflow to probe macOS Virtualization.framework Adds a workflow_dispatch-only job that runs on an Apple Silicon (arm64) GitHub-hosted macOS runner and reports whether the Virtualization framework is available. Probes layer from cheapest to most authoritative: runner identity dump, arm64 assertion, kern.hv_support sysctl, framework bundle presence, and finally VZVirtualMachine.isSupported via an inline Swift program. Writes a result table to the job step summary. * ci(vz-check): make VZVirtualMachine.isSupported the only gate On Apple Silicon kern.hv_support does not exist, so the previous hard-fail was wrong. Demote sysctl inspection to diagnostic-only (now also captures kern.hv_vmm_present and CPU brand), let the Swift VZVirtualMachine.isSupported probe be the sole pass/fail signal, and record each step's findings to GITHUB_OUTPUT so the always()-summary renders an accurate table even when later steps fail.
1 parent 73a8de7 commit 83ab39c

1 file changed

Lines changed: 198 additions & 0 deletions

File tree

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
2+
3+
# Manual probe: confirm the Virtualization.framework is available on a
4+
# GitHub-hosted Apple Silicon (ARM64) macOS runner.
5+
#
6+
# Apple's Virtualization.framework is the macOS analogue of KVM/WHP that
7+
# hyperlight needs for micro-VMs. This job inspects the runner so we can
8+
# verify before betting on macOS hosting (see /memories/azure-hosting-kvm.md
9+
# for why "just use platform X" warrants a real check first).
10+
#
11+
# Authoritative pass/fail: VZVirtualMachine.isSupported (Apple's own runtime
12+
# verdict). The sysctl dump is diagnostic only — on Apple Silicon some keys
13+
# (notably kern.hv_support) simply do not exist; treating their absence as a
14+
# failure is wrong. We also capture kern.hv_vmm_present which tells us whether
15+
# this kernel itself is running inside a hypervisor (true for GitHub-hosted
16+
# macOS runners) — relevant because nested-virt support is silicon/OS
17+
# version dependent.
18+
19+
name: Check macOS Virtualization Framework
20+
21+
on:
22+
workflow_dispatch:
23+
24+
permissions:
25+
contents: read
26+
27+
jobs:
28+
check-virtualization:
29+
name: Inspect Virtualization.framework on ARM macOS
30+
runs-on: macos-latest
31+
32+
steps:
33+
- name: Report runner identity
34+
id: identity
35+
run: |
36+
os_name="$(sw_vers -productName)"
37+
os_version="$(sw_vers -productVersion)"
38+
os_build="$(sw_vers -buildVersion)"
39+
arch="$(uname -m)"
40+
kernel="$(uname -srv)"
41+
cpu_brand="$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo unknown)"
42+
43+
echo "::group::Runner identity"
44+
echo "OS: ${os_name} ${os_version} (build ${os_build})"
45+
echo "Architecture: ${arch}"
46+
echo "Kernel: ${kernel}"
47+
echo "CPU brand: ${cpu_brand}"
48+
echo "::endgroup::"
49+
50+
{
51+
echo "os_name=${os_name}"
52+
echo "os_version=${os_version}"
53+
echo "os_build=${os_build}"
54+
echo "arch=${arch}"
55+
echo "cpu_brand=${cpu_brand}"
56+
} >> "$GITHUB_OUTPUT"
57+
58+
- name: Assert Apple Silicon (arm64)
59+
run: |
60+
arch="$(uname -m)"
61+
if [ "$arch" != "arm64" ]; then
62+
echo "::error::Expected arm64 runner, got '$arch'"
63+
exit 1
64+
fi
65+
echo "Confirmed ARM64 runner."
66+
67+
- name: Capture hypervisor sysctls (diagnostic)
68+
id: sysctls
69+
# Diagnostic only — does NOT gate the job. On Apple Silicon
70+
# kern.hv_support does not exist; kern.hv_vmm_present indicates the
71+
# *kernel* itself is running inside a hypervisor (expected on
72+
# GitHub-hosted runners since they are virtualised). The real
73+
# pass/fail is VZVirtualMachine.isSupported in a later step.
74+
run: |
75+
hv_support="$(sysctl -n kern.hv_support 2>/dev/null || echo missing)"
76+
hv_vmm_present="$(sysctl -n kern.hv_vmm_present 2>/dev/null || echo missing)"
77+
78+
echo "::group::Hypervisor sysctls"
79+
echo "kern.hv_support = ${hv_support} (Intel-era key; 'missing' is expected on arm64)"
80+
echo "kern.hv_vmm_present = ${hv_vmm_present} (1 = this kernel is itself running inside a hypervisor)"
81+
echo ""
82+
echo "Full kern.hv_* / hw.optional.arm / cpu brand dump:"
83+
sysctl -a 2>/dev/null \
84+
| grep -E '^(kern\.hv_|hw\.optional\.arm|machdep\.cpu\.brand_string)' \
85+
|| true
86+
echo "::endgroup::"
87+
88+
{
89+
echo "hv_support=${hv_support}"
90+
echo "hv_vmm_present=${hv_vmm_present}"
91+
} >> "$GITHUB_OUTPUT"
92+
93+
- name: Locate Virtualization.framework
94+
id: framework
95+
run: |
96+
fw="/System/Library/Frameworks/Virtualization.framework"
97+
if [ ! -d "$fw" ]; then
98+
echo "::error::Virtualization.framework not present at $fw"
99+
echo "framework_present=false" >> "$GITHUB_OUTPUT"
100+
exit 1
101+
fi
102+
echo "Found framework bundle: $fw"
103+
ls -la "$fw"
104+
105+
fw_version="unknown"
106+
# Best-effort version read; not fatal if Info.plist layout changes.
107+
if [ -f "$fw/Resources/Info.plist" ]; then
108+
fw_version="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' \
109+
"$fw/Resources/Info.plist" 2>/dev/null || echo unknown)"
110+
echo "Framework version: ${fw_version}"
111+
fi
112+
113+
{
114+
echo "framework_present=true"
115+
echo "framework_version=${fw_version}"
116+
} >> "$GITHUB_OUTPUT"
117+
118+
- name: Probe VZVirtualMachine.isSupported via Swift
119+
id: vz_probe
120+
# The framework being on disk doesn't guarantee the runtime says
121+
# "yes you can boot a VM here". The authoritative check is
122+
# VZVirtualMachine.isSupported, exposed by Apple's Swift API.
123+
# This step is the SINGLE source of truth for pass/fail.
124+
run: |
125+
probe="$(mktemp -t vz-probe.XXXXXX).swift"
126+
cat > "$probe" <<'SWIFT'
127+
import Foundation
128+
#if canImport(Virtualization)
129+
import Virtualization
130+
let supported = VZVirtualMachine.isSupported
131+
FileHandle.standardOutput.write(Data("VZVirtualMachine.isSupported = \(supported)\n".utf8))
132+
exit(supported ? 0 : 2)
133+
#else
134+
FileHandle.standardError.write(Data("Virtualization module not importable on this runner\n".utf8))
135+
exit(3)
136+
#endif
137+
SWIFT
138+
139+
echo "Running probe: $probe"
140+
set +e
141+
swift "$probe"
142+
rc=$?
143+
set -e
144+
145+
# Record the verdict in step output BEFORE potentially failing, so
146+
# the always()-summary at the end can render an accurate value.
147+
case "$rc" in
148+
0)
149+
{
150+
echo "vz_supported=true"
151+
echo "vz_status=enabled"
152+
} >> "$GITHUB_OUTPUT"
153+
echo "Virtualization framework is ENABLED on this runner."
154+
;;
155+
2)
156+
{
157+
echo "vz_supported=false"
158+
echo "vz_status=isSupported==false"
159+
} >> "$GITHUB_OUTPUT"
160+
echo "::error::Virtualization.framework loaded but VZVirtualMachine.isSupported == false"
161+
exit 1
162+
;;
163+
3)
164+
{
165+
echo "vz_supported=false"
166+
echo "vz_status=module-not-importable"
167+
} >> "$GITHUB_OUTPUT"
168+
echo "::error::Swift could not import the Virtualization module"
169+
exit 1
170+
;;
171+
*)
172+
{
173+
echo "vz_supported=false"
174+
echo "vz_status=probe-error-${rc}"
175+
} >> "$GITHUB_OUTPUT"
176+
echo "::error::Swift probe failed with exit code $rc"
177+
exit 1
178+
;;
179+
esac
180+
181+
- name: Summary
182+
if: always()
183+
run: |
184+
{
185+
echo "### macOS Virtualization Framework check"
186+
echo ""
187+
echo "| Property | Value |"
188+
echo "| --- | --- |"
189+
echo "| Runner label | macos-latest |"
190+
echo "| OS | ${{ steps.identity.outputs.os_name }} ${{ steps.identity.outputs.os_version }} (build ${{ steps.identity.outputs.os_build }}) |"
191+
echo "| Architecture | ${{ steps.identity.outputs.arch }} |"
192+
echo "| CPU | ${{ steps.identity.outputs.cpu_brand }} |"
193+
echo "| kern.hv_support | ${{ steps.sysctls.outputs.hv_support }} _(Intel-era; 'missing' is normal on arm64)_ |"
194+
echo "| kern.hv_vmm_present | ${{ steps.sysctls.outputs.hv_vmm_present }} _(1 = runner is itself a VM)_ |"
195+
echo "| Virtualization.framework present | ${{ steps.framework.outputs.framework_present || 'unknown' }} |"
196+
echo "| Virtualization.framework version | ${{ steps.framework.outputs.framework_version || 'unknown' }} |"
197+
echo "| **VZVirtualMachine.isSupported** | **${{ steps.vz_probe.outputs.vz_supported || 'not-run' }}** (${{ steps.vz_probe.outputs.vz_status || 'n/a' }}) |"
198+
} >> "$GITHUB_STEP_SUMMARY"

0 commit comments

Comments
 (0)