18 KiB
Build and boot
unixnu resolves ARM64, SoC, OS, and device profiles, selects component dependencies, assembles a target tree, and runs each build step. Format 2 uses out/<device>/<os_profile>/<config>/. Format 1 uses out/<board>/.
::: warning Required firmware integration
Build the device firmware with ./unixnu build firmware --uefi ../uefi-<device> before packing the boot image. Its DSC and FDF must include UniXNU, UniXNUDxe, DarwinBoot and the payload volume driver. For the Mu-Silicium FDT initrd path, use the shared UniXNURamDiskDxe with DiskIoDxe and FAT support. Follow firmware integration.
:::
1. Prepare the host and repositories
Read Requirements, then choose a port from Devices. New ports follow Port a device first. Obtain the port's device and UEFI repositories, including their submodules. For example, sheng uses:
git clone --recurse-submodules https://github.com/UniXNU/UniXNU
git clone --recurse-submodules https://github.com/UniXNU/UniXNU-sheng
git clone --recurse-submodules https://github.com/UniXNU/uefi-sheng
cd UniXNU
./unixnu defconfig sheng_defconfig -D ../UniXNU-sheng
./unixnu fetch
./unixnu status --json
Use your port's repository and defconfig names. status reports the effective
profiles, revisions, patch order and output directory. -D is remembered in
out/state.json. Run subsequent commands from the main UniXNU checkout.
Expected result: the intended device/configuration and resolved source paths.
2. Save and obtain build inputs
cp unixnu.example.toml unixnu.local.toml
Fill the local file using local build configuration. Set the device and UEFI paths, firmware DTB, platform identity and required kernelcache, trust-cache and Data seed inputs. Use the board's inventory; sheng build inputs lists its additional SDK, firmware and ABI requirements.
./unixnu ipsw info
./unixnu ipsw fetch
./unixnu tree
ipsw info must show the OS build selected by the profile. ipsw fetch extracts
the configured members into out/ipsw/<build>/. Device firmware, Data seeds and
ABI audit inputs have their own preparation steps. Check those before building
a recipe that consumes them.
3. Build the kernel, package and firmware
./unixnu build kernel
./unixnu build package
./unixnu build firmware
Run each command after the preceding one succeeds. Kernel preparation explains archive/ABI requirements and prelink. Firmware integration explains the required DSC/FDF modules.
| Step | Expected output |
|---|---|
| Kernel | <output>/kernel/kernel.macho and build/prelink logs |
| Package | <output>/package/Build/Out/AARCH64-DEBUG/UniXNU.efi for the default target |
| Firmware | The FD path selected by [firmware] fd inside the UEFI checkout |
<output> is the directory printed by status. --target RELEASE changes the
package directory to AARCH64-RELEASE and selects the release firmware path.
4. Prepare root and payload
For a port using the userdata root layout, follow Userdata. The command requires macOS image tools and the target partition geometry:
./unixnu build userdata --dry-run
./unixnu build userdata
Inspect <output>/userdata/userdata-root.json for size, windows and the flash
command. Set the root UUID from the selected volume. For the writable Data volume,
prepare the seed described by the device; Skip Setup is an
optional transformation of that seed.
./unixnu build payload
This uses the local trust-cache, identity and Data seed inputs. Inspect
<output>/payload/payload.json and the printed command line. The generated FAT
volume contains unixnu.toml, the kernel and its ADT/selected sidecars. See
Payload for their layout.
Expected result: root and Data inputs match the device policy, and the payload contains the intended boot entry and arguments.
5. Pack and boot
./unixnu bootimg
Inspect <output>/boot/boot.json: the firmware, DTB and payload must be the
intended files, and ramdisk_bytes must be nonzero for a configured payload boot.
Follow Boot the image for flashing conditions and the one-time boot
command. Follow bring-up on a new port.
After changing boot arguments, run ./unixnu payload and ./unixnu bootimg.
Follow apply arguments to inspect the packed
configuration. After changing kernel code, also rebuild the kernel first.
macOS runner
The kernel step calls Xcode on macOS, and the Data seed steps call hdiutil and diskutil. A fork can run them on a GitHub macOS runner without a local Mac. The workflow is a build check: it publishes redacted text logs and no images.
- Fork the repository and enable Actions.
- Add the repository secret
UNIXNU_UNIQUE_CHIP_IDwhen the board requires a platform seed for its payload. - Run the
macOS buildworkflow. Choose the device repository, ref, defconfig, Xcode version and steps. - Read the step summary or download the log artifact.
The job runs on macos-26. xcode selects /Applications/Xcode_<version>.app and defaults to 26.6.
| Input | Meaning |
|---|---|
device_repository |
Device repository as owner/name |
device_ref |
Ref of the device repository |
defconfig |
Defconfig name from configs/ |
xcode |
Xcode version on the macos-26 image |
steps |
kernel, kernel payload, kernel userdata or kernel userdata payload. The default is kernel |
ipsw_url |
Optional IPSW URL. An empty value uses board.toml |
pre_build |
Optional shell commands run in the UniXNU checkout before the kernel build. The device repository is at DEVICE_DIR |
trust_cache_url |
Optional trust cache bundle URL, masked in the logs |
owner_trust_cache |
Treat the URL as an owner-signed bundle |
data_seed_url |
Optional Data seed URL, masked in the logs |
grow_gib |
Grow the Data seed to this size |
skip_setup |
Mark Setup as done in the Data seed |
Add the repository secret UNIXNU_DEVICE_TOKEN when the device repository is private.
The workflow uses the shared resolver to read effective profiles and output paths. It checks the inputs, the defconfig, the Xcode path and the platform seed before installing or building. It publishes only *.log files with secrets and signed URLs replaced, and writes a per-step summary.
A device archive chain that needs a bootstrap candidate, display ABI evidence or a staged kernelcache uses pre_build. Its commands run after checkout and before the kernel build. The workflow removes the fetched IPSW, the fetched feeds and the staged firmware cache at the end.
A bootstrap candidate example:
./unixnu defconfig bootstrap_defconfig -D "$DEVICE_DIR"
./unixnu build kernel -D "$DEVICE_DIR"
mkdir -p out/<device>/<os_profile>/<config>/tree/build/xnu-prelink
cp out/<device>/<os_profile>/<config>/kernel/kernel.macho \
out/<device>/<os_profile>/<config>/tree/build/xnu-prelink/<candidate>.macho
The candidate names and the display or kernelcache inputs come from the device repository.
The workflow runs kernel, userdata and payload. Package, firmware and boot image commands run locally on supported macOS or Linux hosts. Check the workflow and account runner settings before using hosted builds.
Linux host
The installer supports apt, dnf, pacman and zypper on glibc Linux. Use --package-manager none with prepared dependencies and --llvm-config FILE for a specific host LLVM. See Linux requirements.
The kernel step runs on Linux after a one-time toolchain install (see Requirements):
scripts/linux/setup-toolchain.py --print-packages
sudo scripts/linux/setup-toolchain.py
./unixnu defconfig <name> -D ../UniXNU-<device>
./unixnu fetch
./unixnu build kernel
The driver selects the Linux path from the host. It sets the toolchain environment, and build-xnu-4k-baseline.sh runs scripts/linux/prepare-xnu.py:
source, after the xnu patches: rewrites the few xnu sources that assume Apple's clang or BSD tools (the SHA and AES assembler macros, the syscall table generator under mawk,kextsymboltoolwriting to/dev/null) and installs aTargetConditionals.hinto the fakeroot.libs, after the headers: buildslibcc_kext.a, which a Mac takes from Apple clang, from compiler-rt.
build-firehose.sh compiles libfirehose_kernel without Xcode, build-corecrypto-14.py reads the corecrypto project with scripts/pbxproj.py instead of plutil, and the kernelcache is decoded with lzfse.
The Linux path uses the same selected profiles, archive recipes and prelink checks. Record the host toolchain with the build result; device recipes may require additional SDK or image tools.
Commands
| Command | Action |
|---|---|
defconfig [NAME] |
Lists configs/*_defconfig of the device repository, or merges the selected configuration into the target .config. |
fetch [NAME...] [--update] |
Fetches git sources and writes unixnu.lock in the device repository. --update refreshes unpinned refs; explicit format 2 commits remain selected. |
tree |
Resolves modules, fetches the references the steps need, and assembles out/<device>/<os_profile>/<config>/tree. |
ipsw info |
Prints the product, build and every component of the target IPSW. |
ipsw fetch [COMPONENT...] |
Extracts the named components into out/ipsw/<build>/. |
build [kernel] [package] [firmware] [payload] [prelink] |
Runs the named steps. The default selects kernel, package, payload and firmware. prelink repeats the prelink alone. |
build userdata [--dry-run] |
Builds the raw userdata image. This step is large and runs only when named. |
bootimg |
Packs BootShim, the firmware volume, the device tree and the optional payload into an Android boot image. |
status [--json] |
Prints effective profiles, component scope, revisions, overrides, and patch order. |
clean |
Removes target tree, logs, kernel, package, boot and edk2-conf. |
Common options
| Option | Meaning |
|---|---|
-D DIR |
Device repository. Remembered after the first use. |
--out DIR |
Output root. Default out/ in this repository. |
-j N |
Parallel jobs. Default is JOBS or the CPU count. |
--target DEBUG|RELEASE |
EDK II build target. Default DEBUG. |
--uefi DIR |
Mu-Silicium checkout for the device. |
--edk2 DIR |
Mu_Basecore checkout. |
--kernelcache FILE |
Original decompressed kernelcache. |
--kernel FILE |
Prelinked kernel Mach-O for the payload. |
--kernel-arg NAME[=VALUE] |
Extra kernel argument. A name already in [payload] kernel_args is replaced. Repeatable. |
--trust-cache FILE |
Trust cache bundle. |
--owner-trust-cache FILE |
Owner-signed bundle with the original caches and extra modules. Replaces --trust-cache and skips the board allow-list. |
--data-ramdisk FILE |
Data ramdisk seed. |
--skip-setup |
Marks Setup as done in a copy of the Data seed. Requires --data-ramdisk. |
--unique-chip-id HEX |
Platform seed for the device tree. |
--ipsw PATH_OR_URL |
Target IPSW, local or remote. |
--skip-preflight |
Skips the board preflight scripts of the firmware step. |
--dry-run |
Userdata only: check sizes and pins, write nothing. |
The environment variables UNIXNU_DEVICE, UNIXNU_OUT, UNIXNU_UEFI, UNIXNU_EDK2, UNIXNU_KERNELCACHE, UNIXNU_DTB, UNIXNU_IPSW, UNIXNU_UNIQUE_CHIP_ID and JOBS supply the same values.
Steps
defconfig
defconfig merges the ordered configuration fragments and the selected device defconfig into the target .config. The selected defconfig is applied last. It rejects malformed lines before the build starts.
fetch
fetch resolves every module and fetches the git sources. It uses a shallow, blobless clone with a cone sparse checkout. Format 2 caches each repository and selected revision. The resolved commits are written to unixnu.lock; explicit source commits take priority over recorded locks.
tree
tree assembles out/<device>/<os_profile>/<config>/tree with the layout the scripts expect:
tree/
drivers/ kits/ patches/ refer/
scripts/ package/
build/ .cache/
Modules are symbolic links to their sources. Scripts are copied. The device package/ appears as kits/<board.kit>. .cache/firmware links to the device repository's .cache/firmware, so every tree uses the same proprietary files.
ipsw
The IPSW is read member by member. A remote IPSW is never downloaded as a whole. The zip directory and each needed member are read with HTTP range requests. Extracted members are cached in out/ipsw/<build>/ and checked against the sha256 pins in board.toml [ipsw.sha256].
The image must match [ipsw]: product, version, build, device class, chip ID and board ID.
kernel
The kernel step:
- Resolves the declared patch series and component patches, then applies them to target-local XNU sources. Format 1 uses its directory patch order.
- Runs
prepare-xnu-source.pyto restore omitted DriverKit headers. - Runs one XNU pass for the compile records when
startup.o.jsonis absent. The pass may stop at the link. - Runs selected component recipes and auxiliary
[kernel] archivescommands.component:<ID>entries place component recipes at explicit positions; dependencies run first. - Builds XNU for
ARM64, machineBCM2837, configurationDEVELOPMENT, iOS 17, and links the archives. - Prelinks the native kernel against the original kernelcache with
kits/XnuPrelink. A rebuild replaces the earlier candidate.
The kernelcache comes from the IPSW unless --kernelcache supplies one. The LZFSE payload of the KernelCache IM4P is decoded with macOS compression_tool (lzfse on Linux), the arm64 slice is selected, and the result is checked against [kernel.prelink] kernelcache_sha256.
The output is out/<device>/<os_profile>/<config>/kernel/kernel.macho.
package
The package step builds package/ with EDK II. The output is out/<device>/<os_profile>/<config>/package/Build/Out. It contains UniXNU.efi, UniXNUDxe.efi, UniXNURamDiskDxe.efi, HelloPayload.efi, SampleDriver.efi and XnuTest.efi.
firmware
The firmware step builds the device firmware from the Mu-Silicium checkout:
- Runs the board preflight scripts.
- Applies the board patches to the shared edk2 tree for the build, then reverts them.
- Runs EDK II
buildwith the board DSC and defines.
FD_BASE, FD_SIZE and FD_BLOCKS come from the Mu-Silicium device configuration. The firmware volume must match [uefi_fd] size from that configuration.
payload
The payload step writes a FAT volume with:
EFI/UniXNU/unixnu.tomlEFI/UniXNU/Payloads/kernel.machoEFI/UniXNU/Payloads/kernel.macho.adt- the optional
kernel.macho.trustcache - the optional
scratch-data.img
The kernel command line comes from [payload] kernel_args. An extra --kernel-arg NAME=VALUE replaces the board argument with the same name. The volume is written with mtools without mounting. The output is out/<device>/<os_profile>/<config>/payload/payload.img with payload.json.
--owner-trust-cache supplies an owner-signed bundle with the original caches and extra modules. It replaces --trust-cache and skips [payload] trust_cache_sha256.
--skip-setup marks Setup as done in a copy of the Data seed supplied with --data-ramdisk.
userdata
The userdata step builds the raw image for the userdata partition. It holds the unchanged OS and SystemOS disk images of the IPSW behind the SHNGROOT manifest. On macOS the disk images are attached read-only with hdiutil; on Linux scripts/unixnu/udif.py decodes them, with lzfse for LZFSE chunks. Both give the same bytes. The output is out/<device>/<os_profile>/<config>/userdata/userdata-root.img with userdata-root.json.
The report prints the fastboot flash userdata command.
bootimg
The boot image step:
- Compiles BootShim for the firmware base and size.
- Appends the firmware volume.
- Compresses the pair with gzip.
- Appends the device tree after applying
[[bootimg.dtb_node]]. - Packs the result with
mkbootimg.pyand the image parameters of the Mu-Silicium device configuration. - Unpacks the image and compares every part with its input.
The ramdisk is the payload output when it exists, --ramdisk FILE, or an empty ramdisk with --ramdisk none.
Local modules
A module can come from a local directory. The first match wins:
--local-module [KIND:]NAME=PATH--local-drivers|kits|patches|scripts|package DIR--local DIR, a directory with this repository's layout- a
local = "DIR"entry infeeds.toml, orlocal.tomlin the device repository - the device repository
- this repository
- a git source in
feeds.toml
Local modules are linked into the build tree. unixnu status marks them and reports that they are not pinned by the lock.
Output
All output goes to out/:
| Path | Contents |
|---|---|
out/state.json |
Remembered device repository |
out/feeds/ |
Fetched sources |
out/ipsw/<build>/ |
Extracted IPSW members |
out/<device>/<os_profile>/<config>/.config |
Selected defconfig |
out/<device>/<os_profile>/<config>/tree/ |
Assembled build tree |
out/<device>/<os_profile>/<config>/logs/ |
Step logs |
out/<device>/<os_profile>/<config>/kernel/kernel.macho |
Prelinked kernel |
out/<device>/<os_profile>/<config>/package/Build/Out/ |
EFI binaries |
out/<device>/<os_profile>/<config>/payload/ |
Payload image and report |
out/<device>/<os_profile>/<config>/boot/ |
Boot image and report |
out/<device>/<os_profile>/<config>/userdata/ |
Userdata image and report |
Reproducibility
unixnu.lock pins every fetched source. The firmware build sets SOURCE_DATE_EPOCH to the Mu-Silicium commit time, so EDK II does not embed the build time. The boot image is deterministic for a given firmware volume, device tree and ramdisk. The payload contains a random seed, so two payload builds differ.
Next steps
- Build the kernel describes the kernel flags and archives.
- Boot the image describes the fastboot commands.
- Reference lists every report field.