Merge nucleic/keen-grove-ferret-3rgz into dev

This commit is contained in:
2026-08-05 19:53:03 -07:00
parent 4282750ec6
commit 6d394d8f58
4 changed files with 70 additions and 29 deletions
@@ -97,7 +97,13 @@ extension MacVMEngine {
// on exec ("damaged and can't be opened"). Phase 7 then blamed the Xcode license for that
// SIGKILL and failed the build. §7½ now also proves a required Xcode RUNS before accepting it.
// A v14 base was either never published or holds a damaged Xcode, so force them all.
public static let macOSProvisioningRecipe = 15
// v16: Xcode 27 allows `xcodebuild -version` before its license is accepted, so v15's alleged
// functional license probe returned a false positive, skipped `-license accept`, and then
// failed every base at `-runFirstLaunch`. Phase 7 now compares the bundle's declared
// LicenseInfo.plist ID/type against the matching system-wide IDELast<Type>LicenseAgreedTo
// value. Force any v15 base through the exact license gate (failed bases retry regardless,
// while this also corrects any base that happened to get stamped around the bad heuristic).
public static let macOSProvisioningRecipe = 16
/// The current Linux base **provisioning-recipe version**. Bump when the bundled Linux provisioning
/// assets change in a way that must re-provision an already-built base a new step in
+15 -9
View File
@@ -1699,7 +1699,7 @@ import UniformTypeIdentifiers
/// prints "Killed: 9"; Finder says "damaged and can't be opened"). Phase 7 met exactly that and
/// reported it as an unaccepted Xcode LICENSE sending the operator to run `-license accept`
/// against a bundle that cannot run at all. A signal death must be diagnosed as damage, at the
/// copy, and a plain nonzero exit must still be treated as the license being outstanding.
/// copy, while license state must be checked independently against the bundle's declared ID.
@Test func macOSGuestProvisionerDiagnosesADamagedXcodeRatherThanBlamingTheLicense() throws {
let repoRoot = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
@@ -1711,21 +1711,22 @@ import UniformTypeIdentifiers
let installed = try #require(script.range(of: "installed $name is DAMAGED"))
#expect(script.contains(#"|| xcodebuild_rc=$?"#))
#expect(script.contains(#"if [ "$xcodebuild_rc" -ge 128 ]"#))
// and the later license pass separates a signal from a nonzero exit before it accuses the
// license, which it must still go on to accept for the ordinary (exit 1) case.
let licenseCheck = try #require(script.range(of: "xcode_run_status()"))
// and the later license pass keeps the signal-only integrity probe separate from the exact
// preference-based license check, which still goes on to accept an outstanding license.
let licenseCheck = try #require(script.range(of: "xcode_binary_status()"))
let damaged = try #require(script.range(of: "is DAMAGED: xcodebuild is killed by signal"))
let licenseAccepted = try #require(script.range(of: "-license accept", options: .backwards))
#expect(installed.upperBound < licenseCheck.lowerBound)
#expect(damaged.upperBound < licenseAccepted.lowerBound)
#expect(script.contains("This is NOT a license problem"))
#expect(script.contains(#"xcode_license_ok() { [ "$(xcode_run_status)" -eq 0 ]; }"#))
#expect(script.contains("XCODE_LICENSE_INFO"))
#expect(script.contains("IDELast${XCODE_LICENSE_TYPE}LicenseAgreedTo"))
}
/// A copied-in Xcode is unusable until its license is accepted by root, and an agent exec has no
/// TTY to answer the prompt in so the provisioner must accept it during the base build, verify
/// the acceptance the way a build does, and fall back to driving the interactive prompt with its
/// confirmations pre-fed rather than trusting `-license accept`'s exit code.
/// the exact license ID recorded system-wide, and fall back to driving the interactive prompt
/// with its confirmations pre-fed rather than trusting `-license accept`'s exit code.
@Test func macOSGuestProvisionerAcceptsTheXcodeLicenseBeforeBuildingAnything() throws {
let repoRoot = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
@@ -1740,8 +1741,13 @@ import UniformTypeIdentifiers
script.range(of: "printf 'q\\nagree\\nagree\\ny\\n'", options: .backwards))
#expect(accept.upperBound < interactive.lowerBound)
#expect(script.contains("sudo \"$XCODE_DEVDIR/usr/bin/xcodebuild\" -license >/dev/null"))
// Verified functionally (what a build hits), not by exit code
// Verified against this bundle's exact declared license, not via `-version` (Xcode 27 allows
// that informational command while its license is still outstanding) or the accept exit code.
#expect(script.contains("xcode_license_ok()"))
#expect(script.contains("Contents/Resources/LicenseInfo.plist"))
#expect(script.contains(#"defaults read /Library/Preferences/com.apple.dt.Xcode"#))
#expect(script.contains(#"[ "$agreed" = "$XCODE_LICENSE_ID" ]"#))
#expect(!script.contains(#"xcode_license_ok() { [ "$(xcode_binary_status)" -eq 0 ]; }"#))
// and everything happens before the Metal download, which needs a licensed xcodebuild.
let metal = try #require(
script.range(of: "-downloadComponent MetalToolchain", options: .backwards))
@@ -2962,7 +2968,7 @@ import UniformTypeIdentifiers
provisioned: true, agent: false, grants: true), os: .macOS))
#expect(MacVMEngine.baseIsUsable(status(provisioned: true, agent: false), os: .linux))
#expect(!MacVMEngine.baseIsUsable(status(provisioned: false, agent: false), os: .linux))
#expect(MacVMEngine.macOSProvisioningRecipe == 15)
#expect(MacVMEngine.macOSProvisioningRecipe == 16)
}
/// Clone-time admission duplicates the current-recipe publication invariant: neither a stale
+9 -7
View File
@@ -545,13 +545,15 @@ This is what gives a guest a `metal` compiler, and there is no alternative:
(falling back to `-downloadPlatform macOS`), and finally a **verification** — resolve `metal` through
`xcrun` and compile a probe kernel, because `-downloadComponent` can exit 0 with an unusable toolchain.
The license step matters as much as the download: a *copied* Xcode refuses every `xcodebuild`/`xcrun`
call with *"You have not agreed to the Xcode license agreements"* until root records the agreement, and
an exec-only agent has no TTY to answer that prompt in. §7⅞ tries `sudo xcodebuild -license accept`,
then — if the license is still outstanding — drives the interactive `sudo xcodebuild -license` with its
confirmations pre-fed on stdin (`q`, `agree`), bounded by `run_timeout`. Acceptance is checked
*functionally* after each attempt, by running `xcodebuild -version` as the agent (exactly what a build
hits), because `-license accept` can exit 0 having recorded nothing. It also runs
The license step matters as much as the download: a *copied* Xcode refuses build/first-launch commands
with *"You have not agreed to the Xcode license agreements"* until root records the agreement, and an
exec-only agent has no TTY to answer that prompt in. §7⅞ tries `sudo xcodebuild -license accept`, then —
if the license is still outstanding — drives the interactive `sudo xcodebuild -license` with its
confirmations pre-fed on stdin (`q`, `agree`), bounded by `run_timeout`. Acceptance is checked after
each attempt by matching this bundle's `Contents/Resources/LicenseInfo.plist` ID/type to the corresponding
system-wide `IDELast<Type>LicenseAgreedTo` preference. `xcodebuild -version` is deliberately not the
probe: Xcode 27 prints its version and exits successfully while its license is still outstanding, and
`-license accept` can exit 0 having recorded nothing. The provisioner also runs
`DevToolsSecurity -enable` and adds the agent to the `_developer` group, so the first debug/test run in
a clone doesn't pop the "Developer Tools Access needs to take control" authentication panel.
Xcode is a base-publication invariant, not a best-effort extra. Host-side staging errors propagate;
+39 -12
View File
@@ -1021,27 +1021,49 @@ else
# ── License agreement ───────────────────────────────────────────────────────────────────────────
# A just-copied Xcode.app is NOT usable: until the agreement is recorded (by root, system-wide in
# /Library/Preferences/com.apple.dt.Xcode), EVERY xcodebuild/xcrun call — and the GUI — stops at
# /Library/Preferences/com.apple.dt.Xcode), build/first-launch commands — and the GUI — stop at
# "You have not agreed to the Xcode license agreements", and an agent exec has no TTY to answer
# that prompt in. So accept it HERE, once per base, and PROVE it took.
#
# The proof is functional, not an exit code: `xcodebuild -version` run as the agent (NOT root) is
# exactly what a build hits, and it fails while the license is outstanding. Probe with it after
# every attempt — `-license accept` can exit 0 having recorded nothing.
# Do NOT use `xcodebuild -version` as the proof. Xcode 27 prints its version and exits 0 even when
# that Xcode's license is still outstanding; that false positive skips acceptance, then
# `-runFirstLaunch` stops at the license gate. Instead, read the license ID/type declared by THIS
# bundle and require the corresponding system-wide agreement value to match it exactly. (For
# example, Xcode 27 beta declares Beta/EA2002 and records IDELastBetaLicenseAgreedTo=EA2002.) Probe
# that exact state after every attempt — `-license accept` can exit 0 having recorded nothing.
#
# But separate the TWO ways that probe can fail. An outstanding license is a nonzero EXIT; a bundle
# whose bytes didn't survive being copied here is a SIGNAL — the kernel's code-signing check kills
# xcodebuild on exec ("Killed: 9"), and no amount of `-license accept` will ever change that. Reading
# the second as the first is what made a damaged Xcode report itself as an unaccepted license, and
# sent the operator to run `xcodebuild -license accept` against a bundle that cannot run at all.
xcode_run_status() {
# Keep executable integrity as a separate probe. A bundle whose bytes didn't survive being copied
# here is killed by a SIGNAL when xcodebuild starts — the kernel's code-signing check prints
# "Killed: 9" and no amount of `-license accept` will ever change that. Reading the signal as a
# license failure is what once sent operators to accept a license against a bundle that could not
# run at all. A zero/nonzero ordinary exit is irrelevant to the exact preference check below.
xcode_binary_status() {
local rc=0
"$XCODE_DEVDIR/usr/bin/xcodebuild" -version >/dev/null 2>&1 || rc=$?
printf '%s' "$rc"
}
xcode_license_ok() { [ "$(xcode_run_status)" -eq 0 ]; }
XCODE_RUN_RC="$(xcode_run_status)"
XCODE_LICENSE_INFO="$XCODE_APP/Contents/Resources/LicenseInfo.plist"
XCODE_LICENSE_ID="$(/usr/libexec/PlistBuddy -c 'Print :licenseID' \
"$XCODE_LICENSE_INFO" 2>/dev/null || true)"
XCODE_LICENSE_TYPE="$(/usr/libexec/PlistBuddy -c 'Print :licenseType' \
"$XCODE_LICENSE_INFO" 2>/dev/null || true)"
XCODE_LICENSE_PREF=""
# Apple currently ships GM, Beta, and PTR licenses. Derive the preference name instead of fixing
# those three in a case statement, but accept only a plain alphabetic type before interpolating it.
case "$XCODE_LICENSE_TYPE" in
"" | *[!A-Za-z]*) ;;
*) XCODE_LICENSE_PREF="IDELast${XCODE_LICENSE_TYPE}LicenseAgreedTo" ;;
esac
xcode_license_ok() {
local agreed
[ -n "$XCODE_LICENSE_ID" ] && [ -n "$XCODE_LICENSE_PREF" ] || return 1
agreed="$(/usr/bin/defaults read /Library/Preferences/com.apple.dt.Xcode \
"$XCODE_LICENSE_PREF" 2>/dev/null || true)"
[ "$agreed" = "$XCODE_LICENSE_ID" ]
}
XCODE_RUN_RC="$(xcode_binary_status)"
if [ "$XCODE_RUN_RC" -ge 128 ]; then
echo "$XCODE_APP is DAMAGED: xcodebuild is killed by signal $((XCODE_RUN_RC - 128)) on" >&2
echo " launch, so its code signature no longer validates — the bundle did not survive being" >&2
@@ -1053,6 +1075,11 @@ else
if xcode_license_ok; then
echo " ✓ Xcode license already accepted."
else
if [ -z "$XCODE_LICENSE_ID" ] || [ -z "$XCODE_LICENSE_PREF" ]; then
echo " ✗ could not read a supported license ID/type from $XCODE_LICENSE_INFO." >&2
echo " Refusing to guess whether this Xcode's license is accepted." >&2
exit 1
fi
# 1. The documented non-interactive verb.
run_timeout 300 sudo "$XCODE_DEVDIR/usr/bin/xcodebuild" -license accept >/dev/null 2>&1 || true
if xcode_license_ok; then