File: README.md
UTM
It is possible to invent a single machine which can be used to compute any computable sequence.
-- Alan Turing, 1936
UTM is a full featured system emulator and virtual machine host for iOS and macOS. It is based off of QEMU. In short, it allows you to run Windows, Linux, and more on your Mac, iPhone, and iPad. More information at https://getutm.app/ and https://mac.getutm.app/
Features
- Full system emulation (MMU, devices, etc) using QEMU
- 30+ processors supported including x86_64, ARM64, and RISC-V
- VGA graphics mode using SPICE and QXL
- Text terminal mode
- USB devices
- JIT based acceleration using QEMU TCG
- Frontend designed from scratch for macOS 11 and iOS 11+ using the latest and greatest APIs
- Create, manage, run VMs directly from your device
Additional macOS Features
- Hardware accelerated virtualization using Hypervisor.framework and QEMU
- Boot macOS guests with Virtualization.framework on macOS 12+
UTM SE
UTM/QEMU requires dynamic code generation (JIT) for maximum performance. JIT on iOS devices require either a jailbroken device, or one of the various workarounds found for specific versions of iOS (see "Install" for more details).
UTM SE ("slow edition") uses a threaded interpreter which performs better than a traditional interpreter but still slower than JIT. This technique is similar to what iSH does for dynamic execution. As a result, UTM SE does not require jailbreaking or any JIT workarounds and can be sideloaded as a regular app.
To optimize for size and build times, only the following architectures are included in UTM SE: ARM, PPC, RISC-V, and x86 (all with both 32-bit and 64-bit variants).
Install
UTM (SE) for iOS: https://getutm.app/install/
UTM is also available for macOS: https://mac.getutm.app/
Development
[macOS Development](Documentation/MacDevelopment.md)
[iOS Development](Documentation/iOSDevelopment.md)
Related
- iSH: emulates a usermode Linux terminal interface for running x86 Linux applications on iOS
- a-shell: packages common Unix commands and utilities built natively for iOS and accessible through a terminal interface
License
UTM is distributed under the permissive Apache 2.0 license. However, it uses several (L)GPL components. Most are dynamically linked but the gstreamer plugins are statically linked and parts of the code are taken from qemu. Please be aware of this if you intend on redistributing this application.
Some icons made by Freepik from www.flaticon.com.
Additionally, UTM frontend depends on the following MIT/BSD License components:
Continuous integration hosting is provided by MacStadium
File: Documentation/Architecture.md
Architecture
UTM is built upon several pieces of technology, layered to provide compatibility across various host configurations. Below is a simplified diagram of key pieces of UTM with additional details provided below.
┌────────────────────┬──────────────────────┐
│ iOS VM Display │ macOS VM Display │
├────────────────────┴──────────────────────┤
│ SwiftUI │
├───────────────────────────────────────────┤
│ UTMVirtualMachine │
├────────────────┬──────────────────────────┤
│ CocoaSpice │ │
├────────────────┤ Virtualization.framework │
│ QEMU (TCG/HVF) │ │
└────────────────┴──────────────────────────┘QEMU
The backbone of UTM is QEMU, which provides the emulation and virtualization engine. We run a custom fork which includes several features such as:
- Building QEMU as a shared library
- APRR support for jailbroken iOS
- ARM64 TCTI from @ktemkin (JIT-less iOS support)
- SPICE ANGLE backend for hardware GL acceleration
These features, along with several others (which are undergoing code review and may not have made the latest stable release), allow our fork to be optimized for Darwin.
Hypervisor.framework
QEMU includes support for the hvf accelerator which provides same architecture virtualization (x86 -> x86 or ARM64 -> ARM64) on macOS. This framework is not available on iOS.
UTMQemu
QEMU is linked as a shared library. On iOS, there is no ability to use fork, XPC (directly), or any other way of launching a new process. As a result, we run the QEMU main loop in a pthread which for all intents and purposes work like normal (the key exception being that you cannot spawn multiple instances of QEMU and since QEMU does not properly clean up resources, you cannot re-launch QEMU). On macOS, XPC is used to launch QEMU in a new process. UTMQemu manages the pthread or XPC implementation.
XPC Helper
On macOS, spawning new processes is permitted but due to App Sandbox security requirements, we need some additional "bootstrapping" code to launch QEMU properly. The added benefit is that we only have to provide a single bundle identifier for the "launcher" executable rather than a different identifier for each QEMU executable (required for App Sandbox).
QEMUHelper is an XPC helper with its own App Sandbox separate from the UTM main application. This improves security by providing an additional layer of separation. However, due to this extra care has to be taken when passing file handles and other system resources from the main app. For example, the Unix socket file used to communicate with SPICE is stored in a shared App Group directory. For disk images and shared directories that cannot be stored in the App Group, we have to do a complicated sandbox dance to get the right access permissions.
- The main application opens a
NSOpenPanel, allowing the user to select a file/directory outside the sandbox. This returns aNSURLwith the right security scope attached. If we take a regular bookmark ofNSURLand pass it through XPC, the XPC process should also have access once it reads the bookmark back into aNSURL. - However, the access to that file is only valid while the app is open. As soon as you close it, the app loses those permissions and must either prompt the user to select the file again or store a "security scoped bookmark." The issue here is that a security scoped bookmark is only valid for the sandbox that created it. If we take a security scoped bookmark and pass it directly to the XPC process, it cannot get back a
NSURL. - So the solution is to take a standard bookmark, pass it to the XPC process, have the XPC process then create a security scoped bookmark and pass it back to the main process. The main process can now store this bookmark and have it work as intended when it passes it to the XPC process next launch.
The XPC helper spawns QEMULauncher with an inherited sandbox. This means it can access files that are accessible to the helper XPC. The launcher process is what runs QEMU. When a new file is opened (for example a new disk image is mounted), the main application will pass a bookmark to the helper XPC where it will call -startAccessingSecurityScopedResource which also applies to the child process (QEMULauncher). This way, QEMU does not have to have any knowledge of the App Sandbox.
UTMConfiguration
VM configuration is stored in a PLIST format. This PLIST maps to either a UTMQemuConfiguration or UTMAppleConfiguration structure which stores the underlying configuration data in a Codable interface for easy serialization.
UTMQemuSystem
UTMQemuSystem maps UTMQemuConfiguration to command line arguments used to launch QEMU.
UTMQemuManager
After a VM is launched, UTMQemuManager provides run-time services though the QMP protocol. These services include stopping/pausing/resuming the VM, taking snapshots, switching between mouse and tablet, mounting removable disk images, and more. The underlying transport for QMP is JSON (over a socket), so UTMJSONStream marshals the data to and from NSDictionary objects.
QAPI
QMP protocol is defined by the QAPI schema which is provided as a set of JSON files in QEMU. QEMU uses these JSON files to generate wrapper C functions for internal usage. UTM includes a modified function generator derived from QEMU's own script (qapi-gen.py) along with modified QAPI C visitors (for NSDictionary). This allows UTM to use the same commands, structures, and events that QEMU uses in a transparent way.
For example, when UTM makes a call to some QAPI command such as qmp_blockdev_change_medium, the generated C functions will automatically marshal the function arguments into a NSDictionary object which UTMJSONStream converts into JSON and sends it over the QMP socket. When a response is received, UTMJSONStream converts the JSON into a NSDictionary object, which goes through the generated C functions to unmarshal into some C structure which is returned from qmp_blockdev_change_medium. This is all transparent to the caller as long as they understand that the function call will block until the response is received so it must not be called from the main thread.
Virtualization.framework
On Apple Silicon Macs running macOS 12 or later, Virtualization.framework is provided by Apple to run macOS 12 guests.
UTMAppleConfiguration
As the backend is different, the configuration format UTM uses is also different and is represented in UTMAppleConfiguration. As this configuration is handed in Swift, the Codable protocol is used for serialization instead of a NSDictionary backing used in UTMQemuConfiguration. The Codable backing is more extendible as it allows more complex data to be represented without a lot of boilerplate.
CocoaSpice
UTM uses the SPICE front-end with QEMU because it has more versatility than VNC to handle things like USB forwarding, multiple displays, and the ability to use a SPICE agent running on the guest to share clipboard and change the resolution. CocoaSpice is provided as a Swift package and acts as Cocoa/Objective-C bindings for SPICE GTK. CocoaSpice also provides a bridge between the Pixman framebuffer that SPICE uses and Metal textures that is used by MetalKit to render to screen.
UTMSpiceIO
UTMSpiceIO connects CocoaSpice to UTM and is used by UTMQemuVirtualMachine to control the SPICE client and respond to client events.
UTMVirtualMachine
UTMVirtualMachine provides file I/O operations for creating and saving .utm VM bundles as well as controls for the platform-specific layer above to do high level tasks like starting and stopping the VM. It is highest level of the "backend", providing a platform independent view of UTM virtual machines.
UTMQemuVirtualMachine
This subclass manages QEMU+CocoaSpice backend VMs.
UTMAppleVirtualMachine
This subclass manages Apple Virtualization.framework backend VMs.
Frontend
SwiftUI
The frontend for UTM is designed mostly in SwiftUI 2.0. That means the minimum supported operating system is iOS 14 and macOS 11 and is the main reason there are no plans to back-port UTM to earlier versions. Most views are designed to work on both macOS and iOS.
UTMData
UTMData is a SwiftUI ObservableObject that contains the "state" and is the "single source of truth" for the home view. It stores the list of VMs and functions to create, modify, move, etc VMs on that list. UTMConfigurable provides the state for VM configuration views.
iOS VM Display
The VM display uses UIKit as SwiftUI is not mature enough to do everything UTM needs. This includes the custom keyboard accessory view implemented in a NIB for emulating keys that are not available on the standard iOS keyboard.
macOS VM Display
On macOS, the VM display uses AppKit for similar reasons above.
File: Documentation/Dependencies.md
Dependencies
UTM is built upon QEMU, SPICE, and various libraries that those projects depend on. To support building as an Xcode project, we designed a custom build system that creates Xcode compatible frameworks from autoconf and meson projects.
`build_dependencies.sh`
The build script sets up a build environment for the target platform and architecture.
Usage: [VARIABLE...] build_dependencies.sh [-p platform] [-a architecture] [-q qemu_path] [-d] [-r]
-p platform Target platform. Default ios. [ios|ios_simulator|ios-tci|ios_simulator-tci|macos]
-a architecture Target architecture. Default arm64. [armv7|armv7s|arm64|i386|x86_64]
-q qemu_path Do not download QEMU, use qemu_path instead.
-d, --download Force re-download of source even if already downloaded.
-r, --rebuild Avoid cleaning build directory.
VARIABLEs are:
SDKVERSION Target a specific SDK version.
CHOST Configure host, set if not deducable by ARCH.
CFLAGS CPPFLAGS CXXFLAGS LDFLAGSThe build steps are summarized below:
- It will attempt to detect if your build system has all the required tools before starting.
- All source archives in patches/sources are downloaded if not found. If the download is a Git repository, it is cloned and the right commit is checked out.
- If found, the
.patchfile in patches with the corresponding name is applied to each download. - Each dependency is configured, built, and installed to the sysroot directory.
- Once all libraries are built, they are converted to a .framework. The id and library dependencies are patched to be relative to @rpath (using
install_name_tool). - The QAPI sources are generated using scripts/qapi-gen.py.
Updating dependencies
QEMU
The steps for updating QEMU is the most involved. UTM maintains a fork of QEMU which the updated QEMU version must be merged into. This will be the most time consuming part as the fork needs to build and run correctly outside of UTM.
Next, the QAPI generator for UTM needs to be updated with the changes from QEMU. The UTM QAPI script is derived from QEMU's scripts/qapi/*. Many of the files are unchanged and copied directly from QEMU. However, the key files (commands.py, events.py, types.py) are heavily modified. The best way to approach this is to do a 3-way diff with the UTM files, the version of QEMU where those files are derived from, and the new version of the scripts. Then take the changes from the old version of QEMU to the new version of QEMU and merge it into UTM. For files where there are no changes between UTM and QEMU, the new version from QEMU can be copied to directly. For files where there are changes, some work is required to integrate the changes. From experience, it may be easiest to do incremental changes from QEMU's commits. Also, QAPI does not change often so it is not required to update the scripts after each QEMU update.
As a result of the above, the QAPI support files may also need to be updated. Check with QEMU qapi/*.c's commit history to see if there's any changes needed there. It will usually correspond to changes in the Python generator code.
UTMQemuConfiguration+ConstantsGenerated.m needs to be updated by running const-gen.py. You need to build the UTM fork of QEMU for macOS and pass the build directory as an argument to const-gen. It will then run each QEMU executable in order to parse the help text to find changes in device support. If QEMU adds or removes a supported architecture, this must be manually changed in the const-gen script.
Finally, make sure to rename the binary patches directory to the new QEMU version. The code patch for the previous QEMU version can be deleted if all the changes have been integrated into the UTM fork of QEMU.
Others
The other dependencies are more straightforward. Take the latest release tarball and re-integrate the UTM patches if needed. The UTM GitHub will usually keep forks of projects it depends on with required patches. The UTM changes should be rebased off of the commit corresponding to the latest release of the project. Then git format-patch can be used to generate the patch file which will be applied to the release tarball.
File: Documentation/Graphics.md
Graphics
The graphics architecture of UTM involves many separate translation layers.
GPU Acceleration
┌────────────────────────────────────────────────┐
│ Host │
│ ┌────────────────────────────────┬───────────┐ │
┌──┼─► virglrenderer │ +Venus† │ gfxstream†│ │
│ │ │ │ │ │ │
│ │ ├──────────┬──────────┬──────────┴───────────┤ │
┌─────────────────────┐ │ │ │ ANGLE │ ANGLE │ MoltenVK† │ │
│ Guest │ Q │ │ Metal │ OpenGL │ │ │
│ ┌─────────────────┐ │ E │ ├──────────┴──────────┴──────────────────────┤ │
│ │ Userland 3D API │ │ M │ │ CocoaSpice Metal Renderer │ │
│ │ (e.g. Mesa) │ │ U │ │ │ │
│ ├─────────────────┤ │ │ │ ├────────────────────────────────────────────┤ │
│ │ Kernel Driver │ │ │ │ │ Metal Device │ │
│ │ (virtio-gpu) ├─┼───┘ │ │ │ │
│ └─────────────────┘ │ │ └────────────────────────────────────────────┘ │
│ │ │ │
└─────────────────────┘ └────────────────────────────────────────────────┘†: Future work that is not currently in UTM.
No GPU Acceleration
┌───────────────────────────────┐
│ Host │
│ ┌─────────────┬─────────────┐ │
┌─────────────────────┐ ┌──┼─► pixman │ pixman │ │
│ Guest │ │ │ │ EGL Canvas │ Pixel Buffer│ │
│ │ Q │ ├─────────────┴─────────────┤ │
│ │ E │ │ CocoaSpice Metal Renderer │ │
│ │ M │ │ │ │
│ ┌─────────────────┐ │ U │ ├───────────────────────────┤ │
│ │ Framebuffer │ │ │ │ │ Metal Device │ │
│ │ ├─┼───┘ │ │ │ │
│ └─────────────────┘ │ │ └───────────────────────────┘ │
│ │ │ │
└─────────────────────┘ └───────────────────────────────┘Guest Side
When GPU acceleration is available, the guest userland will translate the graphics API (OpenGL, Vulkan, etc) to kernel driver commands. The commands goes through QEMU through the VirtIO interface to be decoded by the host.
If GPU acceleration is not available, it is typically due to one or more of the following:
- The VirtIO GPU device is not used or is not available on the guest architecture
- The guest drivers for VirtIO GPU are not available or are incomplete (as is the case with Windows)
- The guest userland component (e.g. Mesa) is incompatible with the host side libraries (due to a bug or a missing feature)
- The guest application attempts to use a graphics API feature that is not supported by the host side libraries
When GPU acceleration is missing, if a -gl display hardware is used, then QEMU will handle the blit operations directly to a EGL canvas. This will be slight faster and have lower latency than the alternative, which is for CocoaSpice to do the blit operation AND render to screen. This is why it is worth selecting a -gl device even if there is no guest driver support.
virglrenderer
virglrenderer is the host side library that decodes the draw commands from the guest and calls into OpenGL (on the host side). Venus is a newer addition to virglrenderer that allows the guest to pass Vulkan commands to the host.
gfxstream
gfxstream is an alternative library that allows the guest to serialize OpenGL and Vulkan commands, pass them through a communication channel ("pipe") to the host, and the host will deserialize and evaluate the calls. It differs from virglrenderer in that there is no intermediate translation (guest Mesa -> virgl commands -> host OpenGL). Currently this technology is used for Google's Android emulator and not by mainline QEMU so it will take some time for UTM to adopt the code.
ANGLE
ANGLE is an implementation of OpenGL ES on top of other graphics APIs. UTM uses three ANGLE backends:
- On macOS, the
cgl(Core OpenGL) backend is provided - On iOS, the
eaglbackend is provided - For both macOS and iOS, the
metalbackend is provided
The three backends have differing compatibility and there is no "best" backend. QEMU uses ANGLE to draw into an IOSurface instead of directly to screen.
MoltenVK
MoltenVK is used to translate Vulkan to Metal because Apple devices do not support Vulkan natively. MoltenVK is currently not used in UTM.
CocoaSpice
CocoaSpice renders the IOSurface as a texture directly to screen with Metal APIs. It also controls the frame time by synchronizing to the display's vblank signal to reduce tearing. As an optimization, CocoaSpice renderer will only draw the last update before a vblank which means that if the guest is drawing multiple times per monitor refresh, the host will consolidate the draws to a single Metal call. On macOS, the IOSurface is passed from QEMULauncher (rendered in a separate process) through a global IOSurfaceID. On iOS, because there is no process separation, the IOSurface reference is passed directly from QEMU to CocoaSpice.
UTM uses SPICE as a QEMU frontend, which means that all input/output goes through SPICE. SPICE was designed to work remotely over the network but when operating remotely, GPU acceleration is not supported. Instead, all pixel buffer updates must be sent from QEMU which is why it is slower than the EGL canvas rendering.
Debugging Tips
ANGLE has an option to enable tracing all GL calls. You can modify scripts/build_dependencies.sh to add the following arguments to the ANGLE build:
--args angle_enable_trace=true angle_enable_trace_events=trueBy default, the trace logs are set to the device syslog. If you want it to show up in stderr (especially to sync with log items from other components), modify src/common/debug.cpp and change the line #elif defined(ANGLE_PLATFORM_APPLE) to #elif defined(ANGLE_PLATFORM_APPLE_NOT_DEFINED) as well as the fprintf after it to fprintf(stderr, "%s: %s\n", LogSeverityName(severity), (in order to force stderr to always be used).
virglrenderer also can print debug output. In order to enable it, modify scripts/build_dependencies.sh to include --buildtype debug to the Meson call. Then you have to add the environment variable VREND_DEBUG=all to the process. The easiest way to do that is to modify UTMQemuSystem.m and add it to the setRendererBackend: method.
To enable MoltenVK debug output, modify scripts/build_dependencies.sh and change the call to make $platform in build_moltenvk() to make ${platform}-debug.
File: Documentation/iOSDevelopment.md
iOS Development
This document describes the steps to build and debug UTM on iOS and simulator devices.
Getting the Source
Make sure you perform a recursive clone to get all the submodules:
git clone --recursive https://github.com/utmapp/UTM.gitAlternatively, run git submodule update --init --recursive after cloning if you did not do a recursive clone.
Dependencies
The easy way is to get the prebuilt dependences from GitHub Actions. Pick the latest release and download the Sysroot-* artifact for the targets you wish to develop on. You need to be logged in to GitHub to download artifacts.
| Intel | Apple Silicon | |
|---|---|---|
| iOS | N/A | ios-arm64 |
| iOS SE | N/A | ios-tci-arm64 |
| iOS Simulator | ios_simulator-x86_64 |
ios_simulator-arm64 |
| iOS Simulator SE | ios_simulator-tci-x86_64 |
ios_simulator-tci-arm64 |
| visionOS | N/A | visionos-arm64 |
| visionOS SE | N/A | visionos-tci-arm64 |
| visionOS Simulator | N/A | visionos_simulator-arm64 |
| visionOS Simulator SE | N/A | visionos_simulator-tci-arm64 |
After downloading the prebuilt artifacts of your choice, extract them to the root directory where you cloned the repository.
To build UTM, make sure you have the latest version of Xcode installed.
Building Dependencies (Advanced)
If you want to build the dependencies yourself, it is highly recommended that you start with a fresh macOS VM. This is because some of the dependencies attempt to use /usr/local/lib even though the architecture does not match. Certain installed packages like libusb, gawk, and cmake will break the build.
- Install Xcode command line and Homebrew
- Install the following build prerequisites
brew install bison pkg-config gettext glib libgpg-error nasm meson
pip3 install six pyparsing
Make sure to addbisonto your$PATHenvironment variable!
export PATH=/usr/local/opt/bison/bin:/opt/homebrew/opt/bison/bin:$PATH - Run
./scripts/build_dependencies.sh -p PLATFORM -a ARCHITECTUREwhereARCHITECTUREis the last part of the table above (e.g.x86_64) andPLATFORMis the first part (e.g.ios_simulator-tci). - Repeat the above for any other platforms and architectures you wish to target.
Building UTM
Command Line
You can build UTM for iOS with the script (run ./scripts/build_utm.sh for all options):
./scripts/build_utm.sh -k iphoneos -s iOS -a arm64 -o /path/to/output/directoryThe built artifact is an unsigned .xcarchive which you can use with the package tool (see below). Replace iOS with iOS-SE to build UTM SE. Replace iphoneos with xros to build for visionOS.
Packaging
Artifacts built with build_utm.sh (includes GitHub Actions artifacts) must be re-signed before it can be used. For stock iOS devices, you can sign with either a free developer account or a paid developer account. Free accounts have a 7 day expire time and must be re-signed every 7 days. For jailbroken iOS devices, you can generate a DEB which is fake-signed.
Stock signed IPA
For a user friendly option, you can use iOS App Signer to re-sign the .xcarchive. Advanced users can use the package.sh script:
./scripts/package.sh signedipa /path/to/UTM.xcarchive /path/to/output TEAM_ID PROFILE_UUIDThis builds UTM.ipa in /path/to/output which can be installed by Xcode, iTunes, or AirDrop. Note that you need a "Development" signing certificate and NOT a "Distribution" certificate. This is because UTM requires a provisioning profile with the get-task-allow entitlement which Apple only grants for Development signing.
Unsigned IPA
./scripts/package.sh ipa /path/to/UTM.xcarchive /path/to/outputThis builds UTM.ipa in /path/to/output which can be installed by AltStore or a jailbroken device with AppSync Unified installed.
DEB Package
./scripts/package.sh deb /path/to/UTM.xcarchive /path/to/outputThis builds UTM.deb which is a wrapper for an unsigned UTM.ipa which can be installed by Cydia or Sileo along with AppSync Unified.
Xcode Development
Copy CodeSigning.xcconfig.sample to CodeSigning.xcconfig and modify the file replacing DEVELOPMENT_TEAM with your Team ID and PRODUCT_BUNDLE_PREFIX with a bundle identifier that is registered to you.
If you have a paid Apple Developer account, you can find your Team ID at https://developer.apple.com/account/#/membership
If you have a free Apple Developer account, you need to generate a new signing certificate. To do so, follow the steps in iOS App Signer to create a new Xcode project and generate a provisioning profile. After saving the project, open project.pbxproj inside your newly created .xcproj and look for DEVELOPMENT_TEAM. Copy this value to CodeSigning.xcconfig and your unique identifier to PRODUCT_BUNDLE_PREFIX.
Set DEVELOPER_ACCOUNT_PAID = YES if you used a paid Apple Developer account in order to automatically request the increased memory limit entitlement from Apple.
Tethered Launch
For JIT to work on the latest version of iOS, it must be launched through the debugger. You can do it from Xcode (and detach the debugger after launching) or you can follow these instructions for an easier way.
File: Documentation/MacDevelopment.md
macOS Development
Because UTM is a sand-boxed Mac app, there are a few extra steps needed for a proper development environment.
Getting the Source
Make sure you perform a recursive clone to get all the submodules:
git clone --recursive https://github.com/utmapp/UTM.gitAlternatively, run the following after cloning if you did not do a recursive clone.
git submodule update --init --recursiveDependencies
The easy way is to get the prebuilt dependencies from GitHub Actions. Pick the latest release and download all of the Sysroot-macos-* artifacts. You need to be logged in to GitHub to download artifacts. If you only intend to run locally, it is alright to just download the sysroot for your architecture. After downloading the prebuilt artifacts of your choice, extract them to the root directory where you cloned the repository.
To build UTM, make sure you have the latest version of Xcode installed.
Building Dependencies (Advanced)
If you want to build the dependencies yourself, it is highly recommended that you start with a fresh macOS VM. This is because some of the dependencies attempt to use /usr/local/lib even though the architecture does not match. Certain installed packages like libusb, gawk, and cmake will break the build.
Install Xcode command line and Homebrew
Install the following build prerequisites
shbrew install bison pkg-config gettext glib-utils libgpg-error nasm mesonshpip3 install six pyparsingMake sure to add
bisonto your$PATHenvironment variable!shexport PATH=/usr/local/opt/bison/bin:/opt/homebrew/opt/bison/bin:$PATHRun
sh./scripts/build_dependencies.sh -p macos -a ARCHwhere
ARCHis eitherarm64orx86_64.
If you want to build universal binaries, you need to run build_dependencies.sh for both arm64 and x86_64 and then run
./scripts/pack_dependencies.sh . macos arm64 x86_64If you are developing QEMU and wish to pass in a custom path to QEMU, you can use the -q PATH_TO_QEMU_SOURCE option to build_dependencies.sh. Note that you need to use a UTM compatible fork of QEMU.
Building UTM
Command Line
You can build UTM with the script:
./scripts/build_utm.sh -t TEAMID -k macosx -s macOS -a ARCH -o /path/to/output/directoryARCH can be x86_64 or arm64 or "arm64 x86_64" (quotes are required) for a universal binary. The built artifact is an unsigned .xcarchive which you can use with the package tool (see below).
TEAMID is optional and only used if you are going to sign it.
Packaging
Artifacts built with build_utm.sh (includes GitHub Actions artifacts) must be re-signed before it can be used. To properly use all features, you must be a paid Apple Developer with access to a provisioning profile with the Hypervisor entitlements. However, non-registered developers can build "unsigned" packages which lack certain features (such as USB and network bridging support).
Unsigned packages
./scripts/package_mac.sh unsigned /path/to/UTM.xcarchive /path/to/outputwhere /path/to/output is an existing directory.
This builds UTM.dmg in /path/to/output which can be installed to /Applications.
Signed packages
./scripts/package_mac.sh developer-id /path/to/UTM.xcarchive /path/to/output TEAM_ID PROFILE_UUID HELPER_PROFILE_UUID LAUNCHER_PROFILE_UUIDwhere /path/to/output is an existing directory.
To build a signed package, you need to be a registered Apple Developer. From the developer portal, create a certificate for "Developer ID Application" (and install it into your Keychain). Also create three provisioning profiles with that certificate with Hypervisor entitlements (you need to manually request these entitlements and be approved by Apple) for UTM, QEMUHelper, and QEMULauncher. TEAM_ID should be the same as in the certificate, PROFILE_UUID should be the UUID of the profile installed by Xcode (open the profile in Xcode), and HELPER_PROFILE_UUID is the UUID of a separate profile for the XPC helper. LAUNCHER_PROFILE_UUID is the UUID of a profile for the launcher.
Once properly signed, you can ask Apple to notarize the DMG.
Mac App Store
./scripts/package_mac.sh app-store /path/to/UTM.xcarchive /path/to/output TEAM_ID PROFILE_UUID HELPER_PROFILE_UUID LAUNCHER_PROFILE_UUIDSimilar to the above but builds a UTM.pkg for submission to the Mac App Store. You need a certificate for "Apple Distribution" and a certificate for "Mac App Distribution" as well as a provisioning profile with the right entitlements.
Xcode Development
By default, Xcode will build UTM unsigned (lacking USB and bridged networking features).
If you have a registered developer account with access to Hypervisor entitlements, you should create a CodeSigning.xcconfig file with the proper values (see CodeSigning.xcconfig.sample). Make sure to set DEVELOPER_ACCOUNT_VM_ACCESS = YES.
Note that due to a macOS bug, you may get a crash when launching a VM with the debugger attached. The workaround is to start UTM with the debugger detached and attach the debugger with Debug -> Attach to Process after launching a VM. Once you do that, you can start additional VMs without any issues with the debugger.
File: Documentation/Release.md
Release Guide
This document details the release procedure for UTM team members. The release procedure is mostly automated by GitHub Actions. In short, when you submit a new "release" on the GitHub repository, the following happens:
- If UTM dependencies are not cached from a previous run, build it for {iOS,macOS,iOS-TCI,iOS Simulator} X {arm64,x86_64}.
- Build UTM for all configurations and architectures.
- Take the {iOS-arm64,iOS-TCI-arm64} build and package it into a fakesigned IPA and post it as a release asset.
- Take the iOS-arm64 build and package it into a Cydia DEB and post it as a release asset.
- Update the Cydia repository.
- Update the AltStore repository.
- Combine dependencies for macOS-arm64 and macOS-x86_64 into universal binaries.
- Build UTM as a macOS universal binary.
- Take the universal binary and sign and package it into a DMG.
- Notarize the DMG with App Store Connect and post it as a release asset.
- Take the universal binary and sign and package it for the Mac App Store.
- Submit the signed package to App Store Connect.
For more details see the build.yml file.
Making a release
Once you are ready to make a new release:
- Modify Build.xcconfig and bump
MARKETING_VERSIONfollowing semantic versioning andCURRENT_PROJECT_VERSIONby 1. - Commit the change with the commit message
project: bumped version. Tag the release withgit tag vx.y.zcorresponding toMARKETING_VERSION. - Push the commit and tag.
- In GitHub, draft a new release with the template below. Use the title
vx.y.zorvx.y.z (Beta). Select the tag you just created. Check "This is a pre-release" if it is a beta. Check "Create a discussion for this release" and the "Release" category. - When the release is published, the pipeline will run and do everything else.
Release notes template
Make sure to copy all the changes for all beta releases after the last non-beta release.
## Installation
Visit [https://getutm.app/install/][1] for the most up to date installation instructions.
## Highlights
* Key features
* Keep these bullets short
## Notes
* List any important changes here
* Include anything that deviates significantly from previously defined behaviour
## Changes (vx.y.1)
* Third change
* (iOS) iOS only change
* (iOS) Another iOS only change
* (macOS) A macOS only change
## Changes (vx.y.0)
* One change
* Another change
## Issues
Please check the full list on [Github][2] and help report any bug you find that is not listed.
### Known Issues
* Optional section. List any known major issues here in bullet points.
[1]: https://getutm.app/install/
[2]: https://github.com/utmapp/UTM/issuesBeta release
Beta releases will not show up as the "latest version" in the GitHub home page. It also will not be posted to AltStore and Cydia and will not be distributed to the App Store (exception: TestFlight).
Re-release
In case of issues in post release that warrants a re-release, follow the same steps but do not change MARKETING_VERSION (CURRENT_PROJECT_VERSION must still be incremented by 1 or App Store Connect rejects the build). The tag should be named vx.y.z-t where t starts at 2 and increments by 1 for every re-release. Then copy-paste the release notes from the previous release and follow the same steps above. Finally, delete the old release if desired.
Actions Details
Secrets
Below is a summary of all the variables and secrets used by GitHub Actions in the release process.
| Secret | Description |
|---|---|
PERSONAL_ACCESS_TOKEN |
GitHub personal token with permission for repository_dispatch |
SIGNING_CERTIFICATE_P12_DATA |
Base64 encoded PKCS#12 format containing certificates and private keys for signing |
SIGNING_CERTIFICATE_PASSWORD |
Password of the PKCS#12 file |
CONNECT_KEY |
App Store Connect API key for notarizing and submission (base64 encoded .p8) |
| Variable | Description |
|---|---|
DISPATCH_ALTSTORE_REPO_NAME |
username/repo path to a altstore-github repository |
DISPATCH_CYDIA_REPO_NAME |
username/repo path to a silica-package-github repository |
SIGNING_TEAM_ID |
Team ID associated with signing certificates |
CONNECT_ISSUER_ID |
App Store Connect API issuer id |
CONNECT_KEY_ID |
App Store Connect API key id |
PROFILE_DATA |
Base64 encoded provisioning profile of main application |
PROFILE_UUID |
UUID of provisioning profile above |
HELPER_PROFILE_DATA |
Base64 encoded provisioning profile of QEMUHelper |
HELPER_PROFILE_UUID |
UUID of provisioning profile above |
LAUNCHER_PROFILE_DATA |
Base64 encoded provisioning profile of QEMULauncher |
LAUNCHER_PROFILE_UUID |
UUID of provisioning profile above |
APP_STORE_PROFILE_DATA |
Base64 encoded provisioning profile of main application for App Store submission |
APP_STORE_PROFILE_UUID |
UUID of provisioning profile above |
APP_STORE_HELPER_PROFILE_DATA |
Base64 encoded provisioning profile of QEMUHelper for App Store submission |
APP_STORE_HELPER_PROFILE_UUID |
UUID of provisioning profile above |
APP_STORE_LAUNCHER_PROFILE_DATA |
Base64 encoded provisioning profile of QEMULauncher for App Store submission |
APP_STORE_LAUNCHER_PROFILE_UUID |
UUID of provisioning profile above |
IOS_REMOTE_PROFILE_DATA |
Base64 encoded provisioning profile of iOS Remote for App Store submission |
IOS_REMOTE_PROFILE_UUID |
UUID of provisioning profile above |
IOS_SE_PROFILE_DATA |
Base64 encoded provisioning profile of iOS SE for App Store submission |
IOS_SE_PROFILE_UUID |
UUID of provisioning profile above |
IS_SELF_HOSTED_RUNNER |
Set to true to use a self hosted macOS runner set up by the owner |
Signing for release
The following certificates (and associated private keys) must be exported from Keychain as a PKCS#12 file (Cmd+click to select multiple and right click to export).
- Developer ID Application
- 3rd Party Mac Developer Application (Mac App Store) or Apple Distribution
- 3rd Party Mac Developer Installer (Mac App Store)
Give a password when prompted and save it to the repository secret SIGNING_CERTIFICATE_PASSWORD. Then, in Terminal, convert the PKCS#12 file to Base64 and copy it: cat Certificates.p12 | base64 | pbcopy and paste it to SIGNING_CERTIFICATE_P12_DATA.
Next you need to get each provisioning profile {3 profiles for macOS} X {1 for Developer ID, 1 for Mac App Store}. Save each UUID of the profile as *_PROFILE_UUID and the Base64 encoded data from cat name.provisionprofile | base64 | pbcopy as *_PROFILE_DATA.
AltStore Repository
The AltStore repository is generated by altstore-github. The repository utmapp/altstore-repo is created which contains its own GitHub Actions that is triggered on a repository_dispatch event. When the event is dispatched by the main repository's release Actions, the other repository will use altstore-github to generate an AltStore compatible JSON repository file from GitHub releases containing the release notes and download links to all the recent releases. The resulting repository file is hosted on GitHub Pages.
Cydia Repository
The Cydia repository is generated by silica-package-github. The repository utmapp/cydia-repo has its own GitHub Actions triggered by a repository_dispatch event sent from the main repository during the release GitHub Actions. It generates the repository index and HTML pages and uses GitHub Pages to host everything.
Debugging release pipeline
Go to the Build workflow and click the "Run workflow" button. Type "true" for "Test release?" and you can test out changes to the release pipeline without making a release. The built assets will be provided as artifacts instead of as release assets.
File: Documentation/TetheredLaunch.md
Tethered Launch
On iOS 14, Apple patched the trick we used to get JIT working. As a result, the next best workaround is significantly more involved. This only applies to non-jailbroken devices. If you are jailbroken, you do not need to do this.
Prerequisites
- Xcode
- Latest IPA Release
- iOS App Signer
- Homebrew
- ios-deploy (
brew install ios-deploy)
Signing
Install and follow the instructions for iOS App Signer. Make sure your signing certificate and provisioning profiles matches. Select the UTM.ipa release as the input file and press start.
Save the signed IPA as UTM-signed.ipa. Once the process is completed, rename UTM-signed.ipa to UTM-signed.zip and open the ZIP file. macOS should extract the files to a new directory named Payload/.
Deploying
To deploy UTM, connect your device and run in Terminal:
ios-deploy --bundle /path/to/Payload/UTM.app(Hint: you can drag Payload/UTM.app into Terminal to auto-fill in the path.)
Launching
You need to run the following each subsequent time you wish to launch UTM. (You cannot launch UTM from the home screen in iOS 14 or it will not work properly!)
ios-deploy --justlaunch --noinstall --bundle /path/to/Payload/UTM.app(Hint: if you open Xcode and go to Window -> Devices and Simulators and find your device, you can check "Connect via network" in order to deploy/launch without a USB cable. You just need the device unlocked and near your computer.)
Troubleshooting
Trust issue
If you see the message The operation couldn’t be completed. Unable to launch xxx because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user., you need to open Settings -> General -> Device Management, select the developer profile, and press Trust.
Failed to register bundle identifier
Xcode might show this message when trying to create a signing profile. You need to change the Bundle Identifier and try again.
File: Documentation/TetheredLaunch.zh-Hans.md
捆绑启动
在 iOS 14 中,Apple 修补了我们用来让 JIT 工作的“把戏”。因此,下一个最佳变通方案所涉及的范围更广。这一操作只适用于未经越狱(Jailbreak)过的设备。若你已经越狱,就不需要这样做了。
前置条件
- Xcode
- 最新版本的 IPA
- iOS App Signer
- Homebrew
- ios-deploy (
brew install ios-deploy)
签名
安装并按照 iOS App Signer 的说明进行操作。确保你的签名证书和配置文件相匹配。选择 UTM.ipa 版本作为输入的文件,然后点击“开始(Start)”。
将已签名的 IPA 保存为 UTM-signed.ipa,完成操作后将 UTM-signed.ipa 重命名为 UTM-signed.zip,打开 ZIP 文件。macOS 会将文件提取到名为Payload/的新目录中。
部署
若要部署 UTM,请连接你的设备,然后在终端中运行:
ios-deploy --bundle /path/to/Payload/UTM.app(提示:你可以把 Payload/UTM.app 拖放进终端来自动填充目录。)
启动
当你每次希望启动 UTM 时,都需要运行如下命令。(在 iOS 14 中,不要从主屏幕启动 UTM,否则它将无法正常工作!)
ios-deploy --justlaunch --noinstall --bundle /path/to/Payload/UTM.app(提示:如果你打开了 Xcode 并转到窗口(Window)> 设备和模拟器(Devices and Simulators)找到你的设备,可以勾选“通过网络连接”,以便在没有 USB 电缆的情况下部署/启动。只需要解锁设备并靠近你的电脑即可。)
疑难解答
信任问题
如果你看到了消息 The operation couldn’t be completed. Unable to launch xxx because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user.(无法完成操作。无法启动 xxx,因为它的代码签名无效,授权不足,或者其配置文件尚未被用户明确信任。),你需要打开设置 > 通用 > 设备管理,选择开发者描述文件,然后选择信任。
注册捆绑包标识符失败(Failed to register bundle identifier)
Xcode 可能在尝试创建签名配置文件时显示此消息,你需要更改绑定标识符并重试。
File: Documentation/TetheredLaunch.zh-Hant.md
不完美啟動
在iOS14中,蘋果修補了我們用來讓JIT工作的“把戲”。 因此,下一個最佳的解決方案所涉及的範圍更廣。 這只適用於非越獄設備。 如果你越獄了,你不需要這樣做。
前置條件
- Xcode
- 最新的正式版IPA
- iOS App Signer
- Homebrew
- ios-deploy (
brew install ios-deploy)
簽名
安裝並按照iOS App Signer的說明進行操作。 請確保您的簽名證書和配置文件匹配。 選擇UTM.ipa正式版作為輸入文件並且按下開始。
將已簽名的IPA保存為UTM-signed.ipa,過程完成後將UTM-signed.ipa重命名為UTM-signed.zip並且打開ZIP文件。 macOS會將文件提取至名為Payload/的新目錄。
部署
要部署UTM,連接你的設備然後在終端中運行:
ios-deploy --bundle /path/to/Payload/UTM.app (提示:你可以把 Payload/UTM.app 拖放進終端來自動填充目錄。)
啟動
當你每次希望啟動UTM時,都需要運行以下命令。 (你無法在iOS14中從主屏幕正常啟動UTM否則它無法正常運行!)
ios-deploy --justlaunch --noinstall --bundle /path/to/Payload/UTM.app(提示:如果您打開Xcode並轉到Window->Devices and Simulators並找到您的設備,那麼您可以選中“Connect via network”(通過網絡連接)以便在沒有USB電纜的情況下部署/啟動。你只 需要解鎖設備並靠近你的電腦。)
疑難解答
信任問題
如果你看見了消息:The operation couldn't be completed. Unable to launch xxx because it has an invalid code signature, inadequate entitlements or its profile has not been explicitly trusted by the user.(無法完成操作。無法啟動xxx, 因為它的代碼簽名無效、授權不足或其配置文件未被用戶明確信任。 ),你需要打開設置-> 通用-> 設備管理,選擇開發者描述文件,然後選擇信任。
註冊捆綁標識符失敗
Xcode 可能在嘗試創建簽名配置文件時顯示此消息,您需要更改綁定標識符並重試。