QEMU tools cannot run in the app process on iOS: a process may load QEMU
only once and qemu-img calls exit(). So disk snapshots of a stopped VM,
reclaiming space, compression, resizing and conversion on import were
macOS-only.
iOS 26 brings ExtensionKit, so qemu-img now runs in an ExtensionKit
extension (iOSHelper, and iOSHelper-SE for UTM SE since an extension's
bundle identifier must be prefixed by its host's). The helper speaks the
same QEMUHelperProtocol as the macOS XPC helper but loads the tool into
its own process, one tool per process. UTMProcess accepts an injected
connection so the remote path is shared with macOS, including file
access through bookmarks, whose implicit security scope grants the
resolving process access. UTMHelperProcess launches the helper and,
because the system hands a process that is still exiting to a launch
that comes too soon, tears it down with a handshake on the interruption
callback and runs helpers one at a time.
UTMQemuImage now reads the tool's output until it ends instead of
racing the exit message, resumes its continuations off the XPC reply
queue (a call made from that queue blocks in NSXPC), and stops the
process explicitly since the connection's exported object kept it from
ever being deallocated on macOS. The last stderr line becomes the error
text when the tool gives none.
Assisted-by: Claude:claude-fable-5-1
The Mesa host build now installs a build machine copy of the KosmicKrisp
driver into host/lib. It has the same architecture in every sysroot, so
lipo refuses to combine it.
Assisted-by: Claude:claude-opus-5-5
The new Mesa revision builds KosmicKrisp with the precompiled shader
compiler, and the driver now needs macOS 26 (and the aligned iOS,
tvOS, and visionOS 26 releases) at runtime. Bump the availability
gates on the settings picker and the VM start check to match.
Assisted-by: Claude:claude-fable-5-1
Repackaging one dependency after a change previously meant re-running the
whole build_dependencies.sh. Move the framework fixup into scripts/fixup.sh
so it can be run on its own, and have build_dependencies.sh call it.
Standalone use needed two fixes: re-running no longer nests symlinks
inside an existing Versions/Current (ln -h) and updates Info.plist keys
instead of skipping them, and imports are also matched by file name so
a sysroot built elsewhere (e.g. a CI artifact) is rewritten correctly.
Output for a full build is unchanged.
Assisted-by: Claude:claude-opus-5-5
SwiftTerm 2 no longer exposes the underlying Terminal from TerminalView, so
move the terminal views to the copied view APIs: terminalDimensions,
resize(cols:rows:), setCursorStyle, softReset and resetToInitialState. The
view's resize also soft resets the terminal now, so only call it when the
saved size differs, otherwise every resume would reset the guest's modes.
Track SwiftTerm from the osy/SwiftTerm fork's fix/visionos-build branch
for now: upstream main reads UIWindow.screen, which does not exist on
visionOS, and the fork reads the display scale from the trait collection
instead so the visionOS build works again.
SwiftTerm now ships a build tool plugin, which needs two changes to
build_utm.sh for CI. Xcode refuses to run a package plugin the machine has
not trusted, so pass -skipPackagePluginValidation. And -sdk also applies
to host tools, so the plugin's generator was built for the target platform
instead of the Mac and the build could not find it; pick the platform
with a generic destination and the architectures with ARCHS instead.
Also match the SDK name macosx when picking the default macOS scheme.
Take advantage of what the update brings:
- Render with Metal on macOS and iOS, falling back to Core Graphics if the
GPU pipeline cannot be created. visionOS stays on Core Graphics because
SwiftTerm's Metal path draws at the wrong scale there.
- Release the renderer with updateUiClosed() when the terminal window
closes or the iOS view is dismantled, as SwiftTerm now requires.
- Feed guest output straight from the serial port thread; feed() is
thread-safe now, so the hop to the main queue is no longer needed.
- Add the standard Edit > Find menu on macOS, which the terminal's new
find bar is driven from.
- Open only http(s) links from the terminal. SwiftTerm now also turns host
file paths printed by the guest into links, and its default handler on
macOS opens any URL or existing host path, so clicking one could launch
a host app. On iOS, let the bell use SwiftTerm's default haptic instead
of an empty override.
Assisted-by: Claude:claude-opus-5
Assisted-by: Claude:claude-opus-5-5
pipe2() and dup3() are new in the 27.0 SDKs and carry
__API_AVAILABLE(macos(27.0), ios(27.0), ...). Both meson's has_function()
and autoconf's AC_CHECK_FUNCS declare their own prototype rather than
including the system header, so they never see that annotation and report
the functions present no matter the deployment target. The real call sites
do include <unistd.h>, so clang emits a weak import that is NULL on
anything older than 27 and the first call branches to zero.
That broke every VM start on iOS 26 and below: -[CSMain init] ->
g_main_context_new() -> g_wakeup_new() -> g_unix_open_pipe(), which calls
pipe2() when glib is configured with HAVE_PIPE2. glib, gio, gettext and
libusb all picked it up in every sysroot built with Xcode 27.
Tell both configure systems the functions are missing so the existing
pipe() + fcntl(FD_CLOEXEC) fallbacks are compiled in instead. Nothing
detects dup3() today, but cover it so the next dependency bump cannot
reintroduce the same crash.
Assisted-by: Claude:claude-opus-5
Since Xcode 26.1, actool ignores an .appiconset that shares its name with
an Icon Composer .icon and renders the icons for older OS versions from the
.icon instead. The undocumented --enable-icon-stack-fallback-generation flag
that used to turn this off is now parsed and discarded, so building with
Xcode 27 replaced our flat icons with generated ones on macOS 12-15, where we
prefer our own low resolution art.
Replacing AppIcon.icns is not enough because macOS reads the icon from
Assets.car before Tahoe as well. Instead compile AppIcon.appiconset into its
own catalog and merge its renditions into the built Assets.car with CoreUI,
leaving the icon stack renditions that macOS 26 uses in place.
The rendition format is undocumented, so the merge fails the build if the
catalog does not look the way we expect, and checks that the icons still
decode after writing.
Assisted-by: Claude:claude-opus-5[1m]
Now that the deployment targets are iOS 15 and macOS 12, every guard,
fallback and workaround for an older release is dead code that only
obscures what the supported path is. Remove them:
- Drop the `@available` attributes for iOS 15, macOS 11 and macOS 12, and
unwrap the matching `#available`/`#unavailable` guards in Swift and the
`@available`/`API_AVAILABLE`/`NS_AVAILABLE_IOS` ones in Objective-C.
- Delete the legacy-only views and helpers left unreachable by that:
NumberTextFieldOld, VMConfigNetworkPortForwardLegacyView,
VMContextMenuModifier.bodyBigSur, the nonbrokenSheet, uniqued,
customBadge, borderedList and legacySettingsToolbar shims, the
.unsupported BootloaderSelection case, and the iOS 14 sacrificial
version Section in the QEMU settings list.
- jb_has_cs_execseg_allow_unsigned() has returned false for every host
since iOS 14.4, so drop it along with the is_device_A12_or_newer()
helper that only it used. The entitlement parser no longer needs to
copy the blob either, since that copy existed only so the psychic paper
needle could be blanked in place.
- VMDisplayTerminalViewController tracked the keyboard frame by hand for
iOS 14. `useAutoLayout` is a constant that cannot be overridden, so
nothing ever registered the notification observers and only the
keyboardLayoutGuide path is reachable.
- VMConfigQEMUView's additionalArguments is now referenced on iOS only,
which leaves its macOS branch, and the macOS 11 ForEach crash
workaround inside it, unreachable.
- Raise the Cydia package floor and the postinst/prerm helpers to match
the new minimum.
Two of the removed guards dropped an error path rather than just a
branch. The shared directory `featureNotSupported` throw covered
VZVirtioFileSystemDeviceConfiguration and friends being macOS 12+, which
no longer applies to any supported host. The macOS guest
`platformUnsupported` throw when decoding a boot config on Intel only
fired on macOS 11; the Apple Silicon requirement it really guards is
enforced unconditionally in
UTMAppleConfigurationSystem.fillVZConfiguration, which both the start and
the save path go through.
Assisted-by: Claude:claude-opus-5
Xcode 27 no longer supports deploying to iOS 14 or macOS 11, and the
macOS 27 SDK breaks several dependency builds, so raise the minimum
versions and fix the dependency build:
- Bump IOS_SDKMINVER/MAC_SDKMINVER and the project deployment targets.
- Pass our deployment targets to the Hypervisor build, whose project
still targets iOS 14.
- Build ANGLE without -Werror; new clang warnings break it.
- Update MoltenVK to pick up the upstream minimum target bump and set
a visionOS minimum so it no longer inherits the SDK version.
- Patch QEMU apple-gfx to use PGCreateDeviceWithDescriptor, since
PGNewDeviceWithDescriptor is no longer exported by the macOS 27 SDK.
- Skip copying the IOKit headers on macOS where they are already in the
SDK and a second copy conflicts with the IOKit module.
- Export MACOSX_DEPLOYMENT_TARGET since libtool drops -target when
linking C++ libraries, which built libasprintf for macOS 27.
- Default CI to the installed Xcode.
Assisted-by: Claude:claude-opus-5
The scripting-targets entitlement names the target app as a dictionary key built from $(PRODUCT_BUNDLE_PREFIX:default=com.utmapp). Xcode expands it only when it signs, and the unsigned package re-signs from the raw file, so utmctl in an unsigned build was allowed to script an app with that literal name and no other: every command failed with "failed to get scripting definition" and an unrecognized selector. Expand the key before signing.
Concurrent build_dependencies.sh runs on the same CI runner get rate
limited by GitHub, so clones and fetches fail intermittently. Retry each
network operation with exponential backoff, jittered by PID so parallel
builds do not retry in lockstep.
The tree:0 filtered clones lazily fetch objects during checkout and
submodule update, so those hit the network too and are retried as well.
Add -f to curl so that a rate limited response is treated as a failure
instead of being saved as the tarball.
Assisted-by: Claude:claude-opus-5
The macOS build embeds D3DMetal.framework when the sysroot ships it, but
the framework is distributed separately and cannot be committed. When
D3DM_DIST_DMG_URL is set, fetch the distribution DMG and stage the
framework into the universal sysroot before it is packed into the
artifact and saved to the cache, so downstream builds pick it up. Builds
without the variable set are unaffected.
We cannot ship a com.apple.* bundle, but D3DMetal loads its own
default.metallib through [NSBundle bundleWithIdentifier:@"com.apple.D3DMetal"]
using that identifier as a literal compiled into the framework binary, so
the rebrand rewrites both the Info.plist and the binary. Rewriting only one
makes the lookup return nil and every D3DMetal process aborts in
-[MTLDevice newLibraryWithFile:nil error:].
Staging lives in scripts/install_d3dmetal.sh so it can also be run by hand.
Assisted-by: Claude:claude-opus-5
- virglrenderer uses shmem for texture import/export so we need App Group for both iOS and macOS
- Add QEMURenderServer for new process model rendering (both Venus and Neptune)
- macOS: add DXMT and D3DMetal-Native backends
- We require 8G hostmem for Neptune but iOS currently fails > 256M