How to build LineageOS inside a container

6 min read

LineageOS is a free and open-source Android distribution. To port LineageOS to a new device, to add a kernel module such as WireGuard, or to make your own version, you need to setup a build environment.

Android is composed of a multitude of software, with a build process tested on Ubuntu. If you do not use Ubuntu as your main distribution, you can still build Android inside a container. systemd-nspawn is a container runtime shipped with systemd. This tutorial explains how to setup a LineageOS build environment in a systemd-nspawn container.

I encourage you to consult the LineageOS wiki alongside this article. The references to the wiki point to the Google Pixel XL pages, code name marlin, but the instructions for all the devices are based on a common template.

§
Prerequisites

Building Android requires a decent machine and network connection:

  • RAM: at least 8 GB (along with some swap space).
  • Storage: around 200 GiB (including the source code, cache, and output files).
  • Network: approximately 30 GB of data for the initial download.

The last resource you need is a lot of time. The first download and build are unbearably long (as in hours, or even days with a slow network connection). Subsequent builds are much quicker.

§
Setup the build environment

The LineageOS developers recommend Ubuntu 20.04 LTS (Focal Fossa). The system is installed inside a systemd-nspawn container. The purpose is to keep a clean build environment, separate from your main system, while avoiding the overhead associated with virtual machines.

§
Install Ubuntu

You need debootstrap to install the base system, and optionally the Ubuntu archive keyring so it can verify package signatures. On Arch Linux, they can be installed with the following command:

# pacman -Sy debootstrap ubuntu-keyring

Then, you can bootstrap the base system in /var/lib/machines/ubuntu (but you can choose any other directory for this tutorial):

# debootstrap focal /var/lib/machines/ubuntu http://archive.ubuntu.com/ubuntu

Once installed, you can start the machine:

# systemd-nspawn -D /var/lib/machines/ubuntu
ubuntu:~# 

Use the --bind option to mount the cache and build directories from another location on the host:

# systemd-nspawn -D /var/lib/machines/ubuntu   \
	--bind=/storage/android:/home/user/android \
	--bind=/storage/ccache:/home/user/.ccache

This is useful if you have a secondary hard drive with more available space.

§
Install the build dependencies

debootstrap only adds the main Ubuntu repository. To get access to all the required packages, you need to add the focal-updates and focal-security repositories and the universe branch to APT's sources.list configuration file:

ubuntu:~# cat /etc/apt/sources.list
deb http://archive.ubuntu.com/ubuntu focal main universe
deb http://archive.ubuntu.com/ubuntu focal-security main universe
deb http://archive.ubuntu.com/ubuntu focal-updates main universe

You can now run the upgrade procedure:

ubuntu:~# apt update
ubuntu:~# apt full-upgrade

Finally, install the LineageOS build dependencies:

ubuntu:~# apt install bc bison build-essential ccache curl flex g++-multilib gcc-multilib git gnupg gperf imagemagick lib32ncurses5-dev lib32readline-dev lib32z1-dev liblz4-tool libncurses5 libncurses5-dev libsdl1.2-dev libssl-dev libxml2 libxml2-utils lzop pngcrush rsync schedtool squashfs-tools xsltproc zip zlib1g-dev python-is-python3

§
Add an unprivileged user

Since building things as root is not recommanded, create an unpriviliedged user:

ubuntu:~# adduser --disabled-password user

No need to set any password since you can log into this account from root:

ubuntu:~# su - user
ubuntu:~$

§
Install ccache

ccache helps to speed up the build time thanks to a cache. Start by increasing its maximum size to 50 GB:

ubuntu:~$ ccache -M 50G
Set cache size limit to 50.0 GB

Then, set USE_CCACHE=1 in ~/.bashrc:

ubuntu:~$ echo 'export USE_CCACHE=1' >> ~/.bashrc

§
Download the LineageOS source files

repo downloads and manages the hundreds git repositories required to build LineageOS.

§
Install repo

Create the directory for the user's local binaries:

ubuntu:~$ mkdir -p ~/.local/bin/
ubuntu:~$ source ~/.profile  # to include ~/.local/bin/ in PATH

Download repo and make it executable:

ubuntu:~$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.local/bin/repo
ubuntu:~$ chmod a+x ~/.local/bin/repo

To use repo, you need to set a git name and email:

ubuntu:~$ git config --global user.email user@localhost
ubuntu:~$ git config --global user.name user

§
Fetch the source code

Create the source directory:

ubuntu:~$ mkdir -p ~/android/lineage/
ubuntu:~$ cd ~/android/lineage/

Next, initialize the repository (specify a valid branch for your device, e.g., lineage-17.1):

ubuntu:~/android/lineage$ repo init -u https://github.com/LineageOS/android.git -b lineage-17.1

Finally, you can download the source files (this may take a while):

ubuntu:~/android/lineage$ repo sync --force-sync

repo automatically fetches its latest version, so you can replace the previous binary with a symlink to it:

ubuntu:~$ ln -sf ~/android/lineage/.repo/repo/repo ~/.local/bin/repo

§
Download the device-specific source files

The first step is to source the build environment, which includes the main commands for the build process (such as breakfast and brunch):

ubuntu:~/android/lineage$ . ./build/envsetup.sh

You can now download the device-specific source files, including the kernel (replace marlin with your device code name):

ubuntu:~/android/lineage$ breakfast marlin

If you encounter an error, continue to the next section to extract the proprietary blobs. Then, you should be able to run breakfast again without errors.

§
Extract the proprietary blobs

If you already have LineageOS installed on your device, you can extract the blobs from it. Install the Android platform tools, connect the device to your computer through ADB, and run the following command (replace google/marlin with the device path):

$ ~/android/lineage/device/google/marlin/extract-files.sh

If you do not have LineageOS installed on your device, then you will have to follow the instructions for extracting proprietary binary blobs from the installation zip.

§
Build the system image

If you encountered an error the first time you ran breakfast, run it again:

ubuntu:~/android/lineage$ breakfast marlin
Warning

With the minimum system requirements of 8 GB of RAM + 8 GB of SWAP, the build process may stay stuck for hours on some Metalava documentation targets until it finally runs out of heap memory:

FAILED: out/soong/.intermediates/frameworks/base/api-stubs-docs/android_common/api-stubs-docs-stubs.srcjar...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

The solution is to increase the Java heap size and to build the offending targets sequentially:

$ _JAVA_OPTIONS=-Xmx6114m mka -j1 api-stubs-docs hiddenapi-lists-docs system-api-stubs-docs test-api-stubs-docs

Then you can continue with brunch.

Close uneeded application to free up some RAM and start the build (this may take a while):

ubuntu:~/android/lineage$ brunch marlin
Note

If you encounter an error with resize2fs not being able to open /etc/mtab, create the following symlink before restarting the build:

$ ln -s /proc/mounts /etc/mtab

If the build succeeded, you can head to the output directory:

ubuntu:~/android/lineage$ cd "$OUT"

You will find the following files:

  • boot.img: the boot image (kernel, ramdisk).
  • lineage-17.1-202XXXXX-UNOFFICIAL-<DEVICE>.zip: the system installation package.