Skip to content
5 October 2026· EVOLUTION-X-RUBYX

Building Android 16 for my own phone, on a Mac

113,730 build targets, ten roadblocks and a bootloop I traced to the fast-charging driver, all without wiping 64 GB of data.

My phone is a Redmi Note 12 Pro 5G, codename rubyx. The public Android 15 and 16 builds for it all had the same problems: banking apps refused to open when Developer Options were on, Play Integrity failed, the camera lost features, and installing any of them meant wiping a phone that held more than 64 GB of data I wasn’t willing to lose.

So I built Evolution X 16 myself, from source, on the Mac on my desk.

A build server that costs nothing

Android’s build system still leans on x86_64 Linux host tools, and my Mac is Apple Silicon. Emulating x86 with QEMU is up to ten times slower and falls over under memory pressure. Instead I ran Ubuntu 24.04 ARM64 in a Lima VM, mounted Rosetta 2 through binfmt_misc, and installed the amd64 libraries with Debian multiarch. The Intel host tools then ran at close to native speed.

The disk grew from 260 to 370 GiB along the way, and I gave the VM 24 GB of swap across four swapfiles on top of 11 GB of RAM.

113,730 targets, ten roadblocks

The full build was 113,730 Ninja targets over about 36 hours. It stopped ten times. A few of the fixes:

  • Soong’s Go heap grew to 54 GB and the build stalled at 0% in swap. Capping it with GOMEMLIMIT=10GiB GOGC=40 took CPU use from 130% to 620%.
  • The kernel expected clang-r530567, which I didn’t have. A symlink to my clang-r563880c toolchain fixed it.
  • R8 crashed with a NullPointerException at target 12,400 on an experimental flag. A small wrapper script strips the flag.
  • The x86 Ninja process was OOM-killed at target 104,979 after growing past 13.9 GB under Rosetta. Swapping in native ARM64 Ninja cut its memory by 75%.
  • mkfs.erofs deadlocked with all eight worker threads stuck in futex_wait under Rosetta. Single-threaded, vendor.img built in 42 seconds.

The result was EvolutionX-16.0-20261003-rubyx-11.11-Unofficial.zip: 3.0 GB, exit code 0.

The bootloop

The sideload finished with status 0. The phone showed the Mi logo, switched off and dropped back into recovery.

Instead of wiping anything, I read the kernel’s last words from /sys/fs/pstore/console-ramoops-0, a slice of RAM that survives a soft reboot. The panic was one second into boot, inside ln8000_probe: the driver for the LN8000 fast-charging chip dereferenced a bad pointer. The kernel I had built carried an upstream regression in that driver. The official kernel from my backup didn’t.

So I paired the two: my Android 16 system with the known-good kernel, flashed to boot_b through fastbootd. The phone booted, with all 64 GB of data intact.

What I took from it

Read the crash before you wipe anything. Pstore told me the exact file and line in minutes; guessing would have cost the data.

From the work on Evolution X 16 for rubyx.

See the project

Start a project

I need help building

called . It should ,

and I'd like to start .

You can reach me at .

Opens your mail app with this written out. Nothing is stored here.