AOSP Foundations
6 min read

Android.bp and Blueprint Language

Discover Blueprint, the strict, JSON-like language that powers modern Android builds via Soong.

Why Make Stopped Scaling

AOSP engineers used to wait 15 minutes just for the build system to figure out what to compile. Every time a developer typed make, the GNU Make system read thousands of procedural scripts line by line. Make executed variables, expanded functions, and checked conditions in a strictly sequential order. Reading millions of files this way created a massive bottleneck. The AOSP team realized procedural evaluation could never scale. They needed a static index instead of a procedural script.

Google introduced Blueprint to replace these legacy Makefiles. Blueprint functions as a purely declarative language. The AOSP team designed it exclusively to describe build dependencies. This tool strips away all procedural logic and execution phases.

By treating build files as static data rather than scripts, the system parses thousands of files in parallel.

  1. This flowchart compares Make's procedural execution to Blueprint's concurrent parsing, helping visualize the performance bottleneck.
  2. The left side shows Make processing nodes sequentially, which causes massive delays.
  3. The right side illustrates Blueprint treating nodes as independent data blocks, allowing simultaneous parsing.

Tip: Blueprint targets mono-repos with millions of files, where scale remains the primary driver.

Now that we know why procedural scripts had to die, we can look at the strictly declarative language that replaced them.

Hello Blueprint: A Declarative World

Transitioning away from procedural scripts requires a completely different mental model for defining modules. Developers accustomed to setting LOCAL_PATH and clearing global variables often struggle with the syntax of the new system. A single missing CLEAR_VARS command in Make could silently break an entire build. The new language needed a structure that prevented these state leaks by design.

An Android.bp file uses a syntax heavily inspired by JSON. It relies on strict module definitions followed by sets of key-value properties. Every module functions as a completely encapsulated object.

You declare a module type, give it a name, and list its properties within curly braces.

cc_binary {
    name: "my_custom_daemon",
    srcs: ["main.cpp", "helper.cpp"],
    shared_libs: ["liblog", "libbase"],
    cflags: ["-Werror"],
}

Warning: Blueprint looks like JSON but features comments and strict property rules. Do not just paste generic JSON into these files.

This encapsulation eliminates the need for boilerplate state management. Developers immediately notice the syntax feels cleaner and safer. However, the biggest shock for Make veterans is not the brackets. Strict typing takes that honor.

Strict Typing and the End of String Soup

Make treated absolutely everything as a string. A misplaced space in a source file declaration could silently omit a file from compilation. Debugging these silent failures cost platform engineers countless hours. A modern build system must catch typos during the parsing phase, before compilation even begins.

Blueprint enforces strict types for every property. A property must resolve exactly to a string, a boolean, or a list of strings. The parser validates these types instantly and aborts the build upon detecting a mismatch.

If a module requires a list of source files, you must provide a list wrapped in brackets.

// This will cause a strict parser error
cc_binary {
    name: "my_binary",
    srcs: "main.cpp",
}

By requiring a proper list even for a single file, the parser guarantees predictable data structures.

Strict types prevent typos from surviving past the parsing stage. Developers catch errors in seconds instead of minutes. But what happens when you need to compile a file only for a specific device?

The Rule of No Conditionals

You might instinctively try to write an if block inside your new Android.bp file to target a specific piece of hardware. Doing so immediately crashes the parser. The system explicitly bans all conditionals and logic within these files. This restriction feels incredibly frustrating at first glance.

The new system separates configuration from description entirely. A configuration file only declares what exists, never how or when to build it. It acts as an architectural floor plan rather than a set of construction instructions.

If we allowed conditional statements, the parser could not evaluate files in parallel without knowing the global state of the device. Banning conditionals allows the system to read all configuration files concurrently. Device-specific logic must move to Go plugins or configuration modules instead.

Common Mistake: Trying to read environment variables or execute shell commands inside an Android.bp file will always fail.

By stripping away logic and execution, Blueprint enables the real powerhouse of the modern AOSP build system to take over.

From Blueprint to Ninja (The Speed Payoff)

Since Blueprint does not execute anything, the code still needs a way to actually compile. A static data structure cannot invoke a C compiler on its own. We need an engine that bridges the gap between the static declarations and the final binaries.

The modern build pipeline relies on Soong to read the Blueprint files and Ninja to execute the build. Soong acts as the intelligent parser. Ninja functions as the concurrent executor.

Soong reads thousands of Android.bp files in seconds. It builds a massive dependency graph in memory and generates a highly optimized execution file. Ninja then reads this generated file and runs the actual compilation rules across all available CPU cores.

  1. This flowchart maps the modern build pipeline from static declarations to compiled output.
  2. The initial stage shows configuration files feeding into the Soong parser.
  3. The final stage illustrates Soong generating a Ninja file for execution.

When debugging build failures, you often need to see the exact compilation instructions Soong created. Running the Make command for the ninja file generates the raw dependency graph without starting the full compilation process. You will see a massive text file containing every explicit compiler flag and source path.

m out/soong/build.ninja

A common mistake engineers make here involves trying to manually edit this generated file. Soong instantly overwrites any manual changes the next time it runs.

Interview Note: Blueprint serves as the language, Soong acts as the parser applying logic, and Ninja performs the raw execution.

This strict pipeline makes compiling millions of lines of code possible without losing your mind.

Replacing procedural Make scripts with declarative Blueprint files eliminated the worst bottlenecks in AOSP. Strict typing prevents the silent string errors that used to waste hours of developer time. Banning inline conditionals enables the build system to parse thousands of files simultaneously. We know Blueprint provides the static description and Ninja handles the raw execution. But the brain sitting in the middle must read these files and apply the complex device logic that Blueprint bans. Next, we explore exactly how Soong processes these rules.