Lorry: the package manager
Lorry is Motor OS's Rust package manager: a smaller, stricter Cargo
replacement for security-sensitive environments. It is Cargo-compatible: it takes a
package's Cargo.toml and Cargo.lock as they are and aims to produce
byte-identical binaries. What it changes is how dependencies get in: builds are offline,
from dependencies that were fetched once, checked, shown to a person, and recorded, and
Lorry never invokes Cargo. It runs on Linux and on Motor OS, where it is the Rust build
driver because the developer image has no Cargo.
Cargo compatibility
A Lorry project is a Cargo project. Nothing about the package changes, and Cargo can still build it:
- The inputs are the package's own
Cargo.tomlandCargo.lock(lock versions 3 and 4), read as Cargo reads them: dependencies from crates.io, Git, and local paths, renamed and optional dependencies, features, target-conditioned dependencies, patches, profiles, editions 2015 through 2024, and resolvers 1 through 3.lorry vendorresolves versions the way Cargo does and writes the same lock file. - The commands mirror Cargo's:
lorry new,build,run,test,clean, with--release,--target,-p,--bin, and arguments after--meaning what they mean in Cargo. Output goes to Cargo-shapeddebugandreleasedirectories undertarget/lorry. - A clean release build is required to be byte-identical whichever tool builds it: Cargo on Linux, Lorry on Linux, or Lorry natively on Motor OS. Lorry's test suite checks this, and compares Lorry's dependency resolution against Cargo's.
- A configured
cargo-compat-version(1.99 on the developer image) says which Cargo's behavior Lorry follows, and--use-cargo-registrycan build from an already populated Cargo cache after verifying it.
The compatibility is with the package model Lorry supports, which is deliberately smaller than Cargo's; what is left out is listed under The package model, and anything outside it is rejected with a clear error rather than approximated.
Why a smaller, stricter Cargo replacement
In many corporate environments a build tool that goes out to the Internet on its own
and silently downloads and runs code from hundreds of crates is a weakness, not a
convenience. That is what cargo build does: the lock file names the crates,
Cargo fetches them, runs their build scripts, and loads their procedural macros into the
compiler, and nothing in that sequence asks anyone anything. Lorry is designed for the
environments where that is not acceptable:
- Network access is one explicit step. Only
lorry vendortalks to the network. Every other command is offline and fails rather than fetching. - Every package is admitted individually. Vendoring shows each new package with its exact version, checksum, license, size, whether it has a build script or is a procedural macro, and the dependency edges it adds, and asks before accepting it. The result is a review document that can be read, diffed, and checked into version control, and a record that later builds verify.
- Code that runs at build time needs a grant. A build script or a procedural macro runs only for a package named in the policy at an exact version and checksum. Build scripts run in a sandbox on Linux; procedural macros run in a separate process on Motor OS.
- Fetching is locked down. HTTPS only, through a small curl-compatible
client run without a shell, with proxies and credentials stripped, no redirects unless
the site is on your list, and Git served by an embedded client rather than a
gitexecutable. - Limits are explicit: how many packages, how deep, how large, and how long a build script may run.
- Unsupported means rejected. Cargo behavior that Lorry does not implement is refused explicitly, never approximated, so a build cannot quietly do something the reviewer did not see.
- The same tool everywhere. Lorry runs on Linux and natively on Motor OS, and a clean release build of a package is required to be byte-identical whichever of the three builds it: Cargo on Linux, Lorry on Linux, or Lorry on Motor OS.
Lorry does not replace reading the code you depend on; it makes sure that nothing enters a build without having been seen, and nothing runs during a build without having been allowed.
Commands
lorry new hello # a package with an edition-2024 binary and a lock file
cd hello
lorry build [--release] # build, offline
lorry run [-- args...] # build and run the binary, forwarding arguments
lorry test [--no-run] [-- args] # build and run the library, binary, and integration tests
lorry clean
lorry vendor [--accept-all] # fetch, check, and record dependencies (the only networked command)
lorry vendor upgrade PKG[@OLD] --to VERSION
lorry review # print the canonical review of the dependency set
lorry cache clean
| Option | Meaning |
|---|---|
+toolchain | On Linux, a rustup toolchain selector, as with Cargo. On Motor OS the compiler is /devtools/bin/rustc. |
-p NAME | Select a member of an explicit workspace. |
--bin NAME, --test NAME | Choose a binary or an integration test. run otherwise takes package.default-run or the sole binary. |
--release | The release profile: lto, strip, and codegen-units are honored in addition to the dev profile's settings. |
--target TRIPLE | An installed target; custom JSON targets are rejected. |
--bundle | Package the test harnesses and the binary into one self-extracting executable for the target, to run on another machine. |
--strict-validation | Stricter checks for build, run, and test. |
--use-cargo-registry | Build from an already populated local Cargo cache instead of a Lorry repository, after verifying it (Linux). |
-q, -v, --color | Output control. |
lorry run executes the program directly, without a shell, and returns its
exit status. lorry test builds separate harnesses for the library, the binary,
and each tests/*.rs file and runs them in Cargo's order, stopping at the first
failure; arguments after -- go to every harness. Usage errors exit 1; build,
vendoring, policy, and operational failures exit 101; an interrupted run exits 130. There
is no lorry clippy.
Where things live
| On Motor OS | Purpose |
|---|---|
/devtools/cfg/lorry.toml | System configuration: the Cargo compatibility version (1.99), the curl program and CA bundle, vendor targets, policy limits, native C tools, and the reviewed grants for build scripts and procedural macros. |
/user/cfg/lorry.toml | User configuration; on the image it names the user repository. |
/user/cfg/lorry/vendor | The user repository: content-addressed, immutable objects under objects/crates-io/sha256/ and objects/seeded-git/sha256/, a .staging area, and a repository.toml. Ships empty. |
/user/cfg/lorry/cache | The cache of compiled dependency units, shared between projects. |
/user/cfg/lorry-redirect-sites.toml | The list of sites whose HTTP redirects have been allowed or denied. |
PROJECT/.lorry/dependencies-v2.toml | The project's admission record: which packages, at which checksums, were reviewed for which host and target pairs. Committed with the project, never edited by hand. |
PROJECT/target/lorry/ | Build output, in Cargo-shaped debug and release directories, plus incremental state for the root package. |
Configuration files are merged in order: the system file, the user file, and the
nearest lorry.toml above the project. Every file declares
config-version = 1, and an unknown key is an error. On Linux the user file is
$HOME/.config/lorry/lorry.toml and the cache is $HOME/.cache/lorry.
Repositories come in three roles, system (read-only), user, and
local; lookup goes local, then user, then system, and vendoring writes to the
local repository if one is configured, otherwise to the user's.
Vendoring and admission
lorry vendor is the only command that touches the network. It reads the
package's Cargo.toml and Cargo.lock, resolves what is missing from
the repositories, and for each new package fetches it over HTTPS, checks the checksum
and the archive structure, applies the policy, and asks for approval, showing the exact
version, checksum, license, whether the package has a build script or is a procedural
macro, its size, and the dependency edges it adds. --accept-all answers yes
to those questions after all checks have passed; it cannot bypass an integrity failure,
a policy denial, the redirect list, or a native-tool restriction, and it cannot change an
existing admission record. Approved objects are written with fsync and published
atomically; an object is never replaced. Cargo.lock is committed last.
- Sources are the crates.io sparse index, the only supported registry, and Git over
smart HTTP. Git is handled by an embedded client, never a
gitexecutable: anonymous HTTPS, shallow, with credentials, proxies, hooks, and filters disabled. - The configured curl (
/system/bin/curlon Motor OS, Motor's own curl-compatible client) is run directly, never through a shell, restricted to HTTPS, without following redirects, and with proxy and credential settings stripped from the environment. A redirect is followed only for a site you have allowed in the redirect list. - Dependencies are vendored for every configured target, by default
x86_64-unknown-motorandx86_64-unknown-linux-muslplus the host, so that one review covers cross-compiling on Linux and building natively. - The result is recorded in
.lorry/dependencies-v2.tomlwith a checksum of the canonical review and one entry per reviewed host-and-target pair. A laterbuild,run, ortestrequires its own pair to be present and re-verifies every recorded pair, which is why a Motor-capable rustc is needed even for a host-only build.lorry reviewregenerates the canonical review offline so that it can be read, diffed, and checked into version control.
Policy
The policy in lorry.toml decides what may be admitted. It has a default
action, optional path roots, limits (on the developer image: at most 192 packages, a
dependency depth of 16, 16 MiB per package archive and 128 MiB extracted, 256 MiB per
transaction and 1 GiB extracted, a build script limited to 300 seconds and 8 MiB of
output), and per-package rules. A rule names the package, its exact version, its source,
and its checksum, and it is the only way to grant the two things that run code at build
time:
[policy.rules.serde_derive-1_0_229]
action = "allow"
name = "serde_derive"
version = "=1.0.229"
source = "crates.io"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
allow-proc-macro = true
[policy.rules.libc-0_2_189]
action = "allow"
name = "libc"
version = "=0.2.189"
source = "crates.io"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
allow-build-script = true
allow-build-script and allow-proc-macro are independent grants,
required even when the default action is allow. A build script that needs a C
compiler or an archiver uses the tools declared under
[native-tools."x86_64-unknown-motor"], which on the image are
/devtools/bin/cc and /devtools/llvm/bin/llvm ar, and only with the
build-script grant. The developer image ships grants for the common crates that the
in-tree programs need (serde and its derive macro, libc, proc-macro2, quote, rustls,
thiserror, and others).
Building
- Profiles are Cargo's dev and release, honoring
panic,lto,strip, andcodegen-units. Output goes undertarget/lorry;CARGO_TARGET_DIRis an error. - Caching. Immutable dependency units are compiled once into the shared
cache and reused across projects; the root package and path dependencies are built
incrementally under
target/lorry/.incremental/. The first miss on the shared cache printsRebuilding global dependency cacheonce. Dependency units are compiled in parallel. - Procedural macros are compiled for the machine that runs the compiler. On Linux they load as ordinary dynamic libraries. Motor OS has no dynamic loading, so on Motor a procedural macro is a static executable, and rustc talks to it over the compiler's own proc-macro bridge protocol, carried in bounded, versioned frames on the child's stdin and stdout. This gives process separation, not a sandbox.
- Build scripts of dependencies run on Linux in a mandatory sandbox: no
network, read-only inputs, writes only to
OUT_DIRand a private temporary directory, a cleared environment, and only approved child tools. On Motor OS they run without isolation, and Lorry prints a warning every time it applies one. - Bundles.
--bundleproduces one self-extracting executable for the target that carries the test harnesses and the binary, verifies its payload table, extracts under a private root (/user/tmp/lorry/test-extractionon Motor OS), runs each payload without a shell, and reports the aggregate result.
The package model
Supported: one root package, optionally a member of an explicit workspace chosen
with -p; at most one library and 64 binaries; top-level
tests/*.rs; a current Cargo.lock (version 3 or 4); crates.io, Git,
and path dependencies, including renamed and optional ones, default and forwarded
features, target-conditioned dependencies, dependency build scripts, procedural-macro
dependencies, and required local patches; editions 2015 through 2024; resolvers 1
through 3; rust-version, autobins, default-run, and
[lints.rust].
Not supported, by decision: root build scripts and root build-dependencies, root
dev-dependencies selected for the build target, workspace-wide commands and workspace
inheritance, --manifest-path and parent-directory discovery, alternative
registries, a procedural-macro package as the root, examples, benches, explicit
[[test]] targets, feature selection on the command line, custom JSON targets
and build-std, and rustc wrappers. One known defect is stated in Lorry's own
documentation: a dependency-free root build.rs is accepted by the parser but
never run, so such a package should not be built with Lorry until that is fixed.
Self-hosting
Lorry builds Motor OS programs on Motor OS. The developer-image test vendors and
builds red and then Lorry itself natively in a VM, from the source snapshots under
/devtools/src, and checks that the freshly linked build-script and program
files carry the expected permissions. Replacing the repository's Cargo-driven build of
every component with Lorry is not there yet; Lorry's full-native-build.md
records what remains: the root build.rs defect above, a few Cargo features the
in-tree packages use (lock version 3, a profile override through the environment, root
feature flags), the kernel's custom target and its build-std needs, the imager's Linux-only
dependencies, and the absence of a native Clippy.
Tests
src/bin/lorry/tests/test-all.sh runs Lorry's whole suite within a fixed
time budget: the unit and integration tests offline, a dependency-resolution oracle
that compares Lorry's resolution with Cargo's, contract scripts for the review format,
Cargo identity, workspaces, procedural macros, the registry protocol, and curl, and a
native leg that boots a Motor OS VM and proves that the cross-built Lorry rebuilds itself
there byte-identically and reuses its incremental state across two builds. The
developer-image test run (src/tests/full-test-dev.sh) includes it; the main
full-test.sh does not. Lorry's own README, specification, and design
document are under src/bin/lorry/ in the repository.