What it costs to build AOSP 16 on a 2010 IBM x3650 M3

I needed to build /e/OS to work on the prototype of a new feature. I had the idea of restarting an old machine, and I honestly wondered whether it would still cope with a modern AOSP tree.

It did, after two repairs: I added 32 GB of RAM (48 GB in total) and replaced a failing disk in the RAID array. Here is the full /e/OS build for the Fairphone 6 (AOSP 16, LineageOS 23.0 base) on a rack server whose CPUs predate AVX: the time, memory, disk, bandwidth and power it takes, and what breaks along the way.

Result: 8 h 54 min of build time, 124 GB of build output, about 344 GB of disk in total, and roughly €0.55–0.70 of electricity.

The machine

The x3650 M3 (machine type 7945) shipped in 2010, one generation after the 2009 M2. It is a 2U, dual-socket Westmere-EP box with triple-channel DDR3 per socket.

CPU2 × Xeon E5620, 32 nm Westmere-EP, 4 cores / 8 threads each, 2.40 GHz (2.66 turbo), 12 MB L3 each, 80 W TDP each
Threads16
ISASSE4.2, AES-NI. No AVX (that arrived with Sandy Bridge in 2011)
Memory48 GB DDR3 ECC RDIMM over six channels; DDR3-1066 is this CPU’s ceiling
StorageServeRAID M5014 (LSI SAS2108). Sources and output on a 5.9 TB RAID 5 volume, OS on a separate volume
Host OSUbuntu 20.04, kernel 5.4, glibc 2.31, Python 3.8

AOSP’s host toolchain still runs on a pre-AVX x86-64 CPU: the prebuilt Clang, the JDK 21 used by the build, and Rust 1.83 all ran without complaint. The old userland is the real constraint (see below).

The bill

Source sync34 min for about 970 repositories (repo sync -c -j8 --no-tags)
Git objects81 GB
Checked-out tree93 GB
Device images3.75 GB vendor factory image + 2.4 GB official /e/OS OTA, used to extract proprietary blobs and firmware
Build graph174,567 actions
Soong / Kati analysisabout 10 min before the first compile step, with about 26 GB of RAM in use at the peak
During compile16 threads at 92–100%, load average around 18, 8–10 GB of RAM in use (Java-heavy phases)
Main compile7 h 52 min to reach 96% (168,409 actions), when a packaging step failed
Finishing runs10 min + 19 min + 33 min (three incremental restarts after fixes)
Total build time8 h 54 min
Build output (out/)124 GB
ccache20 GB
Disk, everythingabout 344 GB: git objects, tree, out/, ccache and the two device images
Deliverables2.44 GB OTA package, 2.71 GB fastboot image package
Previous run, AOSP 15Full build of the same device: 10 h 23 min, with only 20 GB of RAM. Incremental rebuilds: 27 min to 1 h 22 min
Measured on this machine
Powerabout 250–300 W at the wall under full load, so 2.2–2.7 kWh for the 8 h 54 min
Electricityroughly €0.55–0.70 per full build at €0.25/kWh
Uninterrupted buildabout 8 h 30 min, if none of the failures below had happened
Estimated (not metered)

The electricity is cheap. The real cost is time: a current 32-thread desktop does the same build in a fraction of it, and every failed run on this box costs minutes to hours before the error shows up.

 1 [|||||||||||  94.1%]   2 [|||||||||||  93.5%]   3 [|||||||||||  94.1%]   4 [|||||||||||  94.7%]
 5 [|||||||||||  94.8%]   6 [|||||||||||  92.9%]   7 [|||||||||||| 96.8%]   8 [|||||||||||  94.8%]
 9 [|||||||||||  93.5%]  10 [|||||||||||  95.4%]  11 [|||||||||||  93.5%]  12 [|||||||||||  92.1%]
13 [|||||||||||| 96.1%]  14 [|||||||||||  94.0%]  15 [||||||||||||100.0%]  16 [|||||||||||  92.7%]
Mem 8.56G/47.1G   Tasks 95, 471 thr; 16 running   Load 18.29 15.44 10.99
  CPU% COMMAND
  161. prebuilts/jdk/jdk21/linux-x86/bin/java -XX:OnError="cat hs_err_pid%p.log" …
  125. java -XX:CICompilerCount=6 -XX:+UseDynamicNumberOfGCThreads …
  99.9 prebuilts/rust/linux-x86/1.83.0/bin/rustc -C linker=out/host/linux-x86/bin/…
  37.9 prebuilts/jdk/jdk21/linux-x86/bin/javac -J-Xmx4096M …
  … about 40 more JVMs

htop about 25 minutes into the compile phase. Dozens of JVMs (javac, plus the Java tools that generate API stubs and docs) and rustc keep all 16 threads busy.

What broke, in order

  • Rate limiting during sync. One AOSP repository (external/crosvm) failed with HTTP 429 from googlesource at -j8. Re-syncing that single project with -j1 fixed it.
  • A private dependency. The device tree inherits a launcher package whose repository is no longer public. An empty config.mk in its place removes it from the product cleanly.
  • glibc 2.34 required by a prebuilt. prebuilts/extract-tools/.../stripzip is linked against glibc 2.34; Ubuntu 20.04 has 2.31. It only normalises zip timestamps during blob extraction, so a no-op stub keeps the files byte-identical to the vendor’s. The other extract tools and the compiler toolchain still run on 2.31.
  • EROFS partitions. The official /e/OS OTA ships EROFS images, which Ubuntu 20.04 cannot read. Building fsck.erofs from the tree’s own external/erofs-utils, statically linked against the in-tree external/lz4, took a minute and allowed fsck.erofs --extract.
  • Pinned firmware from an older release. Sixteen Wi-Fi firmware files (vendor/firmware/qca6750/*) are pinned by SHA-1 to an older vendor release (FP6.QREL.15.176.0) that is no longer downloadable. The official /e/OS image contains exactly those hashes, so they were taken from there and verified one by one.
  • Bootloader firmware. 20 of the 24 radio images (xbl, abl, tz, hyp, …) in the vendor factory image differed from the ones the official /e/OS release ships. To avoid flashing anything older than what the phone already runs (anti-rollback), all 24 were taken from the official release. The generated Android.mk checks each radio file’s SHA-1, so those checksums had to be regenerated to match.
  • Missing HAL repository. The product requires android.hardware.nfc-service.sec, provided by hardware/samsung_slsi/nfc, which is not in the manifest. LineageOS lists it in the device’s dependencies; adding it to a local manifest (branch lineage-23.0) let Kati finish.
  • repo inside the build sandbox, at 96%. The rule that writes product/etc/build-manifest.xml runs repo manifest, which keeps a JSON cache of the user’s git config in ~/.repo_.gitconfig.json. When ~/.gitconfig does not exist, repo considers the cache stale on every call and rewrites it, and the build sandbox mounts $HOME read-only. Creating an empty ~/.gitconfig and running repo manifest once outside the build fixed it.
  • A presigned app in the image. Prebuilt APKs that keep their own signature and target API 30 or higher must be declared preprocessed: true, so the build checks them (zip-aligned, uncompressed dex for a privileged app) instead of rewriting them and breaking the v2 signature.

Most failures surfaced during Kati, 2 to 7 minutes into a run. The sandbox one appeared after 7 h 52 min. Thanks to the incremental build, fixing it cost 10 minutes rather than a second night.

Takeaways

  • A 16-thread Westmere box with 48 GB of RAM can still build AOSP 16. CPU age is not the blocker; memory and the host’s userland are.
  • Give Soong’s analysis phase about 30 GB of headroom, or enable swap before the first run.
  • Ubuntu 20.04 works as a host if you are ready to work around a few tools that expect glibc 2.34.
  • Take radio firmware from the release the phone is running, not from the newest vendor image.
  • Make sure ~/.gitconfig exists before a long run, even empty.
  • Use ccache from the start. The first build pays for every later one.

Build target: lineage_FP6-userdebug, /e/OS 4.3.1-a16, BUILD_ID BP2A.250805.005, AOSP 16. Built with breakfast FP6 userdebug && m bacon -j12, ccache enabled.

And you?

What does it cost you to build an AOSP-based OS: which machine, how many hours, how much disk? Which tricks and practices do you recommend to make it faster or less painful? Share your numbers and setups in the comments.


Gaël Duval
CEO at Murena, President at e Foundation

Written with the help of an AI assistant. The machine, the build and the ideas are mine.

Leave a Reply

Your email address will not be published. Required fields are marked *