The Nightmare of Copy-Pasting Device Configurations
Imagine building a smartphone lineup with a Base, Pro, and Ultra model. You copy a massive makefile from the Base model to create the other two. A week later, the camera team releases an urgent fix requiring a new property flag. You update the Base and Pro models but forget the Ultra. The Ultra model ships with a broken camera because you relied on copy-pasting configuration files. We need a way to absorb common configurations without rewriting them.
Why AOSP Forces You to Think in Layers
Hardware rarely changes as fast as marketing does. A company might use the exact same motherboard for a phone, a tablet, and a smart display. Defining the Wi-Fi drivers in three different places creates three distinct points of failure. The solution requires separating the generic Android system from the vendor hardware and product branding. This mirrors how object-oriented programming handles complexity through subclassing. The build system implements this approach using a specific Makefile concept.
Product Inheritance: Object-Oriented Makefiles
How do we actually share variables across these distinct levels? Product inheritance allows one makefile to pull in every property and package defined in another file. You can think of this as creating a subclass in Java or C++. The child configuration receives all the upstream rules automatically. If the base configuration adds a Bluetooth package, the child receives that package without mentioning it.
This mechanism prevents duplicated definitions across your product family. Before looking at the code, we must understand the structure of the layers we are going to inherit.
The Four Standard Layers of an AOSP Device Tree
Where exactly do we split these configurations to maximize reuse? Android's standard build architecture divides the device tree into four distinct tiers. The foundational tier is the AOSP base, which contains Google's core framework and standard apps. Above that sits the System on Chip tier, providing CPU and GPU drivers from vendors like Qualcomm. Motherboard components, like custom camera sensors, reside in the Board tier. Finally, the Device tier sets the consumer branding, screen resolution, and final applications.
This hierarchical tree visualizes how the generic operating system transforms into a specific consumer product. The flowchart illustrates the flow of inheritance from the open source core down to the final retail devices.
The base Android framework flows into the chip architecture, which then branches into specific product SKUs. This structure allows the Pro and Ultra devices to share the exact same board logic while maintaining separate brand identities.
Tip: Some manufacturers combine the Board and Device layers for simple products. Keeping them separate remains the best practice for future scaling.
With the architectural tree in mind, we can examine the exact macro that links these layers together.
The inherit-product Macro and Variable Overrides
We need a way to tell the build system to pull the parent configuration into the current file. The inherit-product macro executes this exact function. This command takes a single argument containing the path to the parent makefile. Make evaluates this path and merges all variables from the parent into the current context. After inheriting, you can append new packages to PRODUCT_PACKAGES or override existing ones.
This command solves the copy-paste problem by absorbing the base configuration and appending a custom launcher. You should expect the final build to include the standard Android system plus your new proprietary launcher app.
$(call inherit-product, vendor/company/base.mk)
PRODUCT_PACKAGES += MyCustomLauncher
The sequence of your Makefile dictates the final outcome. Understanding this execution order becomes critical when you start stacking multiple devices.
Warning: Placing the
inherit-productcall at the end of your file is a critical mistake. Because variables evaluate sequentially, the inherited configuration will silently overwrite any custom properties you defined above it.
Building the Base, Pro, and Ultra Lineup
How do we combine multiple layers to build an entire smartphone family? We start by creating a central base configuration that inherits the chip and board layers. This central file becomes the single source of truth for all shared hardware settings. Our specific Pro and Ultra makefiles will then inherit this shared foundation.
This setup prevents us from duplicating the camera application across every single device model. The output will be three separate ROMs that share the same foundation but contain unique branding packages.
$(call inherit-product, vendor/company/base.mk)
PRODUCT_PACKAGES += ProCameraFeatures
The build system processes these files from top to bottom, meaning later variable assignments take precedence. A common mistake is redefining PRODUCT_PACKAGES with = instead of appending with +=, which completely erases the inherited applications. This layered execution forms the bedrock of Android scalability.
The Modularity of the Build System
Managing hundreds of hardware variations could easily collapse into unmaintainable chaos. Google engineers designed AOSP to solve the configuration explosion problem by treating makefiles like object-oriented classes. The four standard layers keep the generic system isolated from the proprietary hardware. Our inherit-product macro glues these tiers together into a single cohesive target.
We have the apps and the configuration structured beautifully across our hardware tree. But what if the apps we want to include are closed source binaries that cannot be compiled from scratch?