AOSP Foundations
7 min read

The build/ Directory

Discover the heart of the AOSP build system, including Make, Soong, Blueprint, and the environment setup scripts.

Downloading the AOSP source tree leaves you with 800 repositories sitting on your hard drive. Your instinct directs you to look for a Gradle file or a CMake manifest to compile the code. You search the root directory and find nothing familiar. Standard build tools simply cannot handle this scale.

Why Gradle Cannot Build Android

Android application developers rely heavily on Gradle to compile their projects. If Gradle works perfectly for an app, why did Google spend years writing a completely custom build system for the operating system? The answer comes down to sheer scale.

Building a single app compares to constructing a house. You need materials, a blueprint, and a single crew. Constructing AOSP compares to building an entire city infrastructure concurrently.

The OS requires compiling thousands of modules written in C++, Java, Go, and Rust. A traditional build tool like Gradle would take hours just to parse the dependency graph before compiling a single line of code.

Since standard tools fail at this scale, AOSP created a specialized multi-stage compilation pipeline, starting with your terminal.

Tip: Do not confuse building an Android app with building the Android platform. AOSP builds the OS itself, utilizing entirely different tools optimized for massive scale.

Preparing the Terminal: envsetup.sh and lunch

Typing the m command in a fresh terminal throws a "command not found" error. The build tools exist on your drive, but your shell has no idea where they reside.

The AOSP build system requires a specific environment to function. Before you can compile anything, you must inject hundreds of helper functions into your active shell session. You achieve this by sourcing a script called envsetup.sh.

# Sourcing the environment setup script is always step 1 of building AOSP
source build/envsetup.sh

A common mistake engineers make involves executing this script directly as a program.

Warning: Never run ./build/envsetup.sh as an executable. You must use source build/envsetup.sh so the script can modify your current shell session.

After you source the file, the script provides commands like lunch. You use lunch to select your target product and variant.

# Select the target device and build flavor
lunch aosp_cf_x86_64_phone-userdebug

A common mistake developers make involves forgetting to run lunch in a new terminal window, causing subsequent commands to fail.

Providing the target lets lunch configure variables like TARGET_PRODUCT and TARGET_BUILD_VARIANT. Now, typing m works because the shell knows exactly what that function means.

After the terminal acquires these commands, the build system must first deal with decades of legacy code.

The Legacy Layer: Android.mk and Kati

Early versions of Android used standard GNU Make to build the system. Developers wrote Android.mk files to define their modules. Make evaluates rules dynamically during compilation, which becomes unbearably slow when thousands of modules depend on each other.

AOSP needed a faster engine but could not instantly rewrite thousands of existing Android.mk files. The team needed a bridge. Platform engineers built Kati to serve as a specialized translator.

Kati reads older Android.mk files and evaluates them entirely in memory. Kati strips out the dynamic behavior of Make. Then, the tool outputs a static list of exact compilation steps.

# A legacy Android.mk file defining a simple module
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := legacy_binary
LOCAL_SRC_FILES := legacy.cpp
include $(BUILD_EXECUTABLE)

Engineers frequently copy outdated Android.mk templates from old Stack Overflow posts.

Tip: Never use Android.mk for new code. Always write your build rules using the modern .bp format.

Using a translator allows the build system to convert an old language into universal machine instructions without executing the old language directly. While Kati handles the past, the future of the build system lies in a much faster engine.

The Modern Engine: Soong and Blueprint

Google required a primary build system that could parse thousands of files concurrently. Make ran too slowly, and standard tools could not handle the multi-language requirements. The platform team created a two-part engine to solve this problem.

The generic build parser bears the name Blueprint. The Android-specific logic lives inside a tool named Soong.

Blueprint defines a strict dictionary and syntax. The tool enforces a JSON-like structure for build files named Android.bp.

// A modern Android.bp file defining a C++ binary
cc_binary {
    name: "modern_binary",
    srcs: ["main.cpp"],
    shared_libs: ["liblog"],
}

Developers often forget to include required shared libraries in the Android.bp file.

Soong takes the generic syntax and applies Android rules to the code. Blueprint acts as the grammar, while Soong gives the words meaning for Android components. Soong reads every Android.bp file, resolves all dependencies across 800 repositories, and generates a massive build graph.

Both Soong and Kati do a massive amount of analysis, but surprisingly, neither of them actually compiles a single line of code.

The Execution Phase: Ninja

Generating a list of compilation rules does not create a bootable operating system. Something has to physically execute compilers against the source files.

Ninja acts as the ultra-fast executor of the build graph. This tool focuses entirely on speed and parallel execution.

Soong acts as the architect drawing the blueprint, while Ninja serves as the foreman blindly directing thousands of workers to build the structure. Ninja reads a massive file named build.ninja. This file contains the explicit commands generated by both Kati and Soong. Ninja then spawns compiler processes, utilizing every CPU core without any complex configuration.

External tools like Ninja live in the prebuilts/ directory because they originated outside the Android project. Now that we have all the individual pieces, let us see how they orchestrate the full build from start to finish.

The Full Compilation Flow

Having multiple tools interact across different languages creates confusion. When a build fails, you need to know exactly which phase of the pipeline broke.

The build system executes a multi-stage pipeline every time you run a compilation command. You can trace this flow directly from your terminal input down to the final artifacts.

To understand this pipeline, we will map out the complete end-to-end execution flow. The diagram below shows how legacy and modern configuration files flow through Kati and Soong before reaching Ninja for final execution.

Triggering the pipeline with the m command starts everything. Kati and Soong run in parallel to generate the static rules, which Ninja then executes to produce the final artifacts.

# Force only the generation phase to debug build scripts
m generate_ninja_build

Many engineers wonder why their terminal pauses for several minutes before any compilation logs appear.

Debugging Note: The initial pause when running m represents the Ninja file generation phase. You can debug this specific phase using the m generate_ninja_build command to skip compilation.

With the build system demystified, you are now ready to add your own code and tell Soong how to compile the new components.

The AOSP build system solves the problem of massive scale by splitting generation from execution. You start by arming your terminal with envsetup.sh. Kati translates legacy Makefiles while Soong parses modern Blueprint definitions. Both tools generate static rules that Ninja blindly executes to build the system as fast as possible.

Our new build system acts as a machine waiting for instructions. Next, we will write our first Android.bp file and feed the new instructions to Soong.