Build
unixnu is the build driver. It reads a device repository, resolves modules, assembles a build tree and runs each step.
Quick start
git clone https://github.com/UniXNU/UniXNU
git clone https://github.com/UniXNU/UniXNU-sheng
git clone https://github.com/UniXNU/uefi-sheng
cd UniXNU
./unixnu defconfig sheng_defconfig -D ../UniXNU-sheng
./unixnu fetch
./unixnu ipsw fetch
./unixnu build kernel
./unixnu build package
./unixnu build firmware --uefi ../uefi-sheng
./unixnu bootimg --uefi ../uefi-sheng --dtb /path/to/device.dtbThe device repository is remembered in out/state.json after the first -D.
The boot image is out/<board>/boot/boot.img with out/<board>/boot/boot.json next to it. Options follow the subcommand.
Commands
| Command | Action |
|---|---|
defconfig [NAME] | Lists configs/*_defconfig of the device repository, or copies one to out/<board>/.config. |
fetch [NAME...] [--update] | Fetches git sources and writes unixnu.lock in the device repository. --update ignores the lock. |
tree | Resolves modules, fetches the references the steps need, and assembles out/<board>/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 is all four steps. 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 | Prints the device, configuration and the origin of every module. |
clean | Removes the board output and keeps fetched sources. |
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. Repeatable. |
--trust-cache FILE | Trust cache bundle. |
--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 copies configs/<name>_defconfig from the device repository to out/<board>/.config. It parses the file to reject a malformed line 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. One clone exists per repository and ref. The commit of each source is written to unixnu.lock in the device repository. A later build uses the locked commit.
tree
tree assembles out/<board>/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>.
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:
- Applies
patches/xnuandkits/NativePrelink/patchesto the pinned XNU source. - Runs
prepare-xnu-source.pyto restore omitted DriverKit headers. - Builds XNU for
ARM64, machineBCM2837, configurationDEVELOPMENT, iOS 17. - Prelinks the native kernel against the original kernelcache with
kits/NativePrelink.
The kernelcache comes from the IPSW unless --kernelcache supplies one. The LZFSE payload of the KernelCache IM4P is decoded with macOS compression_tool, the arm64 slice is selected, and the result is checked against [kernel.prelink] kernelcache_sha256.
The output is out/<board>/kernel/kernel.macho.
package
The package step builds package/ with EDK II. The output is out/<board>/package/Build/Out. It contains UniXNU.efi, UniXNUDxe.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 plus every --kernel-arg. The volume is written with mtools without mounting. The output is out/<board>/payload/payload.img with payload.json.
--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. The disk images are attached read-only with hdiutil. The output is out/<board>/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/<board>/.config | Selected defconfig |
out/<board>/tree/ | Assembled build tree |
out/<board>/logs/ | Step logs |
out/<board>/kernel/kernel.macho | Prelinked kernel |
out/<board>/package/Build/Out/ | EFI binaries |
out/<board>/payload/ | Payload image and report |
out/<board>/boot/ | Boot image and report |
out/<board>/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.