April 23, 2026
8 min read

Building ArjunaOS #6: Understanding the Android Build System

ArjunaOS
LineageOS
AOSP
Android Build System
Soong
lunch
PPrasad Manoj Parulkar
Prasad Manoj Parulkar

AOSP Engineer

Article 5 proved the tree builds and boots. Now we trace what actually made that happen: envsetup shell glue, lunch target discovery, inherited product makefiles, Soong namespaces, and the output metadata files that explain where the build really went.

In the last article, we finally got a successful build and a booting emulator.

That was the fun milestone. But it also hides a lot.

We typed . build/envsetup.sh, lunch, and mka, and the tree eventually handed us system.img, vendor.img, super.img, and a booting emulator. If you stop there, Android still feels a bit like ritual. Type the right words, wait a long time, hope the tree is in a forgiving mood.

Article 6 is where we slow that down.

This is not going to be a full Soong internals lecture. That would be too much, too early, and honestly not very useful yet. What we do need is a practical map of the build path we already used: where the lunch target comes from, which product files it inherits, why the output directory ended up as emu64x, and which build artifacts are worth reading after the build finishes.

Figure 1: The path we are really following in this article. Shell setup, target selection, product inheritance, build orchestration, and then the output breadcrumbs that tell you what actually happened.

Start with the shell glue

The first thing to get straight is this: envsetup.sh is not the build system.

It is the shell glue around the build system.

That sounds small, but it matters. When you source:

. build/envsetup.sh

you are not compiling anything yet. You are loading helper functions and environment behavior into the current shell. That is why commands like lunch, m, and mka suddenly exist. It is also why the emulator later becomes easy to launch from the same shell.

In this tree, build/envsetup.sh also contains the flow that Article 5 used without spelling it out:

  • lunch() parses the target you asked for
  • _lunch_meat validates it
  • build_build_var_cache resolves build variables
  • set_stuff_for_environment exports target-aware environment values
  • printconfig prints the summary we used as our checkpoint

If you grep build/envsetup.sh for those function names, the shell-side build path becomes pretty readable very quickly. You can literally see the main entry points sitting there in the file.

That is already a more useful mental model than “source the file because tutorials say so.”

The important practical point is this: envsetup.sh gives the shell its build vocabulary, and lunch turns that vocabulary into a specific product configuration.

Pick the target up from the right place

In Article 5, we used:

lunch lineage_sdk_phone_x86_64-trunk_staging-userdebug

Now the obvious question is: where does lineage_sdk_phone_x86_64 actually come from?

For this tree, the first useful answer lives here:

vendor/lineage/build/target/product/AndroidProducts.mk

That file is one of the build system’s product index points. It lists the product makefiles LineageOS exposes and the lunch choices associated with them.

In our run, the first grep already gave us two useful breadcrumbs:

  • the lineage_sdk_phone_x86_64.mk product file entry
  • the lineage_sdk_phone_x86_64-userdebug lunch choice entry

For our emulator target, the important line is the makefile entry for:

  • vendor/lineage/build/target/product/lineage_sdk_phone_x86_64.mk

That is the first real breadcrumb.

It means the lunch target is not magic text. It resolves to an actual product makefile under vendor/lineage/build/target/product/.

Figure 2: The first useful source breadcrumb for this build. The LineageOS product file exists, and it leads into the inherited emulator product chain we used for the first build.

Follow the product inheritance chain

Once you open vendor/lineage/build/target/product/lineage_sdk_phone_x86_64.mk, the build starts looking less mysterious.

That file does not define everything itself. It inherits other product files:

  • device/generic/goldfish/64bitonly/product/sdk_phone64_x86_64.mk
  • vendor/lineage/build/target/product/lineage_generic_target.mk
  • device/generic/goldfish/board/kernel/x86_64.mk

This is the part a lot of beginners miss.

The final build target is usually not one giant file. It is a stack of inherited product fragments. Each one contributes something different:

  • base emulator product behavior
  • LineageOS-specific product wrapping
  • kernel selection
  • package lists
  • copy files
  • board details

For this specific build, the most important inherited file is:

device/generic/goldfish/64bitonly/product/sdk_phone64_x86_64.mk

That is where the emu64x identity becomes explicit:

  • PRODUCT_NAME := sdk_phone64_x86_64
  • PRODUCT_DEVICE := emu64x

Those lines do more to explain the output path from Article 5 than a page of abstract build-system talk.

So now the odd thing from Article 5 stops looking odd.

We lunched lineage_sdk_phone_x86_64, but the output directory landed under:

out/target/product/emu64x

That happened because the LineageOS wrapper product inherits a goldfish product definition whose device name is emu64x.

That is not a naming accident. That is the inheritance chain doing exactly what it is supposed to do.

Figure 3: The product chain for the build we already ran. LineageOS wraps the target, goldfish defines the emulator product details, and that inherited device identity is why the output lands under emu64x.

Where Soong enters the picture

This is usually where people get fuzzy and start treating “Soong” as a word that just means “Android build stuff.”

Let’s keep it more practical than that.

The product makefiles decide what kind of thing we are building. They describe product identity, inheritance, copy files, package selection, partition sizing, board details, and the namespaces the build should expose.

Soong is the module build system that takes that configured world and turns module definitions into real build actions.

There is more machinery under it, especially Kati and Ninja, but we do not need to unpack all of that in one article. For now, the useful thing is to understand that Soong is not guessing in isolation. It is working from the target shape that lunch and the product files helped define.

That is why the lunch summary in Article 5 was useful. It showed values like:

  • TARGET_PRODUCT=lineage_sdk_phone_x86_64
  • PRODUCT_SOONG_NAMESPACES=device/generic/goldfish hardware/google/camera/devices/EmulatedCamera

Those namespaces are not random decoration. They tell you which module definition trees are being exported into the Soong world for this build. In this emulator product chain, you can see those namespace additions in goldfish product files like device/generic/goldfish/product/generic.mk.

So the relationship is not:

  • makefiles on one side
  • Soong on another side
  • and nobody talking to each other

It is more like this:

  • product files shape the target
  • build variables capture that target state
  • Soong sees the relevant namespaces and module world for that state
  • the build then materializes into out/target/product/<device>

That is enough understanding for this article. We do not need to dive into Blueprint syntax yet just to be useful.

Read the breadcrumbs in out/target/product

After the build succeeds, most people only look for the flashy files:

  • system.img
  • vendor.img
  • super.img
  • vbmeta.img

Those matter, yes. But Article 6 is a good place to notice the quieter files too.

Under:

out/target/product/emu64x

the build also left some very useful breadcrumbs:

  • module-info.json
  • installed-files.txt
  • misc_info.txt
  • build_fingerprint-lineage_sdk_phone_x86_64.txt

These are the kinds of files that help you reason about the build instead of just admiring the output images.

module-info.json is a module inventory. It is huge, but useful when you want to know what the build knows how to produce.

installed-files.txt is one of the cleanest “what actually landed?” reports in the tree. It shows installed artifacts and sizes. If you want to understand why an image got big, this is one of the places to start.

misc_info.txt is full of packaging and partition metadata. It is not pretty, but it tells you real things about how the target was assembled.

And the fingerprint file is a nice sanity breadcrumb tying the built output back to the product identity you selected.

Even the first few lines are useful. In this run, misc_info.txt immediately showed AVB and image-packaging settings, while installed-files.txt started with framework jars, APEX packages, native libraries, and binaries. That is already enough to make the output directory feel readable instead of opaque.

Figure 4: The output directory is not just partition images. It also holds the metadata files that explain what the build assembled, how it was configured, and which product identity it belongs to.

This is why this article fits well right after the first build. Once you know these files exist, the tree stops feeling like a black box. You have places to look when something gets weird later.

What this article should leave you with

If Article 5 proved that the tree builds, this article should leave you with a better question: what exactly told it how to build?

By this point, you should be able to say all of this without hand-waving:

  • envsetup.sh prepares the shell and build helpers
  • lunch resolves a real product target, not a magical string
  • lineage_sdk_phone_x86_64 comes from LineageOS product makefiles
  • that product inherits goldfish emulator definitions
  • the inherited device identity is why output lands under emu64x
  • Soong is seeing a target-shaped world, not building in a vacuum
  • the output directory contains metadata breadcrumbs, not just flashable images

That is enough understanding for now.

We do not need to become build-system archaeologists before changing ArjunaOS. But we do need enough of the mental map that later edits feel intentional.

Coming up next

In the next article, we finally start changing what the system looks like.

The build and boot path is proven now. The build system is less mysterious. That means we can move into rebranding without doing it blindly.

Article 7 is where LineageOS finally starts turning into ArjunaOS.

Article 6 of 30

Building ArjunaOS: Create Your Own Custom Android OS

Learn to build your own custom Android OS from scratch. This series walks you through creating ArjunaOS, a custom ROM based on LineageOS, starting from setting up your build environment and compiling your first build, through branding and system customization, to adding custom system services and advanced features. Covers the complete journey across three parts: building the OS, adding features, and deep customization.

Series Progress20%

Comments (0)

Sign in to join the conversation

Loading comments...