Building Motor OS
Motor OS is cross-compiled on Linux. One script builds the compilers, the
system, and the VM images from a clean checkout; after that, make rebuilds the
system in minutes. The script is tested on Ubuntu 24.04 and 26.04.
What you need
- An x86-64 Linux host with KVM (
/dev/kvm), which the VM scripts use. Ubuntu 24.04 or 26.04 is what the build script provisions; on another distribution, install the packages listed below yourself and the script carries on. sudo, for installing packages and for the host side of VM networking, and network access, for the toolchain sources.- Disk space and time. The first run checks out and builds the Rust and LLVM forks twice, for the Linux host and for Motor OS: budget about 45 GB (the managed sources and their build trees alone take over 30 GB) and one to two hours on a fast desktop. Later runs reuse everything that is unchanged.
The packages the script installs are git, build-essential,
nasm, clang, cmake, ninja-build,
zlib1g-dev, libssl-dev, pkg-config, curl,
qemu-system, and qemu-utils, plus rustup without a default
toolchain. Cloud Hypervisor and Firecracker are not installed; QEMU is enough to build
and test.
The one-script build
mkdir motor-dev
cd motor-dev
git clone https://github.com/moturus/motor-os.git
cd motor-os
src/build-motor-os.sh
src/build-motor-os.sh is the single supported entry point. In order, it:
- installs the missing host packages and rustup, creates the
moto-tapinterface with the forwarding and masquerade rules that let VMs reach the Internet, and makes/dev/kvmaccessible. Each step is skipped when it has already been done;MOTOR_SKIP_HOST_NETWORK_SETUP=1skips the network part if you have configured it yourself; - checks out the exact Rust, LLVM, Cargo, and mlibc revisions declared in
src/toolchain-versions.sh. A checkout that does not match the declaration, including one with a modified or untracked file, is an error; the build never advances a branch or runscargo update; - builds a Linux-host Clang and LLD from the LLVM fork;
- runs Rust's bootstrap once and installs the result as an immutable toolchain: rustc,
rustdoc, Cargo, the standard library for Linux and for
x86_64-unknown-motor, Clippy, rustfmt, andrust-src. The toolchain is registered with rustup under a name that ends in a 64-character key derived from its inputs, and validated component by component; - builds the native side, called an assembly: the C-ABI shim over
rt.vdso, compiler-rt, mlibc, libunwind, libc++abi, and libc++, the native LLVM multicall binary, Lua, native rustc, and ripgrep. An assembly is keyed by its inputs too, so a change tomoto-rtor mlibc produces a new assembly without a new compiler; - writes the toolchain and assembly manifests, then builds the base, standard, and
developer images into
vm_images/release/.
It does not start a VM. Its outputs live next to the checkout, in the directory
MOTORH names (the parent of the checkout by default; set it before the first
run to put them elsewhere):
| Directory | Contents |
|---|---|
toolchain-src/ | The managed Rust checkout, with LLVM and Cargo as submodules, and the mlibc checkout. |
toolchains/<name>/ | One immutable host toolchain per key: the rustup toolchain the repository selects. |
assemblies/<key>/ | One assembly per key: build/
(component build trees), sysroot/ (the C/C++ cross sysroot and the
motor-clang linker wrappers), and images/ (the libc, ripgrep,
LLVM, and rustc files the imager places into the images). |
The build is incremental and safe to rerun: after a failure, run the same command
again. A completed toolchain or assembly is reused only after its manifest and artifacts
validate, and older keys are kept rather than deleted. A .building directory
next to an output means a build is in progress or was interrupted; a
MOTOR-TOOLCHAIN-REJECTED or MOTOR-ASSEMBLY-REJECTED marker keeps an
invalid result for diagnosis. Fix the cause before removing either.
The toolchain the repository selects
The checkout's rust-toolchain.toml names the exact toolchain the build
installed, key included, so ordinary commands run from the checkout use it without a
+nightly, +stable, or +dev-* selector; do not add one.
To see what is selected:
rustc -vV
cargo -Vv
rustc --print sysroot
Cargo reports 1.99.0-dev and the exact Cargo commit: the current toolchain
is built from the Rust 1.99 beta, and the stable 1.99.0-motor.1 release waits
for upstream Rust 1.99.0. The sysroot contains MOTOR-TOOLCHAIN-MANIFEST and
lib/rustlib/MOTOR-TOOLCHAIN-KEY; every assembly, and the developer image under
/devtools/toolchain/manifest, contains MOTOR-ASSEMBLY-MANIFEST. The
manifests record the revisions, keys, and hashes everything was built from, and
make refuses to run with a toolchain that has no key.
The build also records which assembly this checkout uses, in the ignored directory
.motor-os/. make clean keeps that record, and a second clone under
the same MOTORH makes its own. When the runtime sources change and no assembly
matches any more, run src/build-motor-os.sh again: it derives the new key and
builds only what is missing. To inspect or change the selection by hand:
src/select-toolchain-assembly.sh --show
src/select-toolchain-assembly.sh --list
src/select-toolchain-assembly.sh --pin ASSEMBLY_KEY
Building with make
With the toolchain in place, the Makefile in the repository root builds the system, the programs, and the images. Debug is the default profile:
make -j"$(nproc)" # base and standard images, debug, into vm_images/debug/
make -j"$(nproc)" images # the developer image as well
make -j"$(nproc)" BUILD=release # the same into vm_images/release/
make -j"$(nproc)" dev.img BUILD=release
make sys-io rush # individual components
make system-tty.img # the test-only System-console image
make clippy
make clean # removes build/ and vm_images/; keeps the toolchains and assemblies
Every component is a target named after its binary: kernel,
vdso, sys-io, sys-init, sys-tty,
strobe, dns-resolver, sysbox, rush,
russhd, red, rmux, kibim,
httpd, httpd-axum, lorry, curl,
gears, mdbg, systest, and so on; the image targets
are base.img, main.img, dev.img, and
system-tty.img. Stripped binaries go to build/bin/<profile>/,
and Cargo's target directories, with the unstripped binaries that addr2line
needs, to build/obj/<toolchain-key>/<profile>/<component>/.
The imager (src/imager) assembles each image from a YAML description and
the permission policy; see Filesystem. The standard and
developer images additionally take the libc, ripgrep, LLVM, and rustc files from the
selected assembly, so they cannot be built without one; the base image needs none.
Running the tests
src/tests/full-test.sh # debug
src/tests/full-test.sh --release
src/tests/full-test-dev.sh --release
full-test.sh is the suite every change is expected to pass. It runs the
host-side tests of the build scripts, builds the standard image and the test programs,
runs the host cargo test suites (the imager, Motor FS, the networking stack,
rush, rmux, red, russhd, rnetbench, sys-init, and the runtime libraries) and the
System-console, TUI, and terminal-size tests, then boots the image under QEMU and drives
it over SSH: systest, mio-test, tokio-tests, crossterm-smoke, SFTP, and the rest. A run is
limited to fifteen minutes. full-test-dev.sh does the same against the
developer image, then compiles the source snapshots natively inside the VM and runs
Lorry's own suite; it wants a 4 GiB VM. full-test-networking.sh is the
networking subset with rnetbench throughput runs, and stress-soak.sh keeps one
VM under concurrent load for as long as you ask, watching for crashes and stalls.
On the developer image the same test programs are in /devtools/tests and can
be run from the shell; see Developer image.
Working on the compilers
Changes to the Rust or LLVM forks are built from your own checkout, without touching the managed one:
src/build-motor-os.sh \
--source-mode authoring \
--rust-source /absolute/path/to/rust \
--authoring-base FULL_40_CHARACTER_BASE_COMMIT
The checkout owns its src/llvm-project submodule. Local commits, modified
files, and untracked files in both trees become part of the toolchain's identity, and the
build reads them as they are, without fetching, switching, resetting, or cleaning
anything. Such a toolchain gets a distinct, non-release name and key and cannot replace
the one named in rust-toolchain.toml. A managed source is updated only by
changing its revision in src/toolchain-versions.sh.
Further reading
The repository describes the build in more depth:
docs/build-motor-os.md
(the source modes, build order, and manifests),
docs/build-llvm.md
and
docs/build-rustc.md
(the C/C++ and Rust components and their layout on the developer image), and
docs/assembly-selection.md
(how an assembly is chosen and validated, and how to recover a stale selection). What the
toolchains contain and how programs are compiled with them is on the
Toolchains page.
Once you have images, Running Motor OS describes the VM scripts, and Hello Motor OS walks through running your own program.