This chapter makes a tiny native executable in the pinned Android 14 checkout. It separates three outcomes: a module compiles, a file is installed on a device, and that exact file runs with expected output. Runtime exercise is only for a disposable, matching Cuttlefish userdebug instance; compilation is a useful but weaker alternative.
Before you start#
- C/C++ source reading — basic function, include, braces, and return syntax.
- Find your way around AOSP — checkout root and project ownership.
- Build and run AOSP — Android 14 target and optional matching Cuttlefish setup.
You need the android-14.0.0_r1 tree. Do not make this change in a shared checkout with unknown local modifications.
Learning goals#
- Read a simple
cc_binarydeclaration and know what each file contributes. - Build only a named module and locate its exact target artifact.
- Safely run and remove only the demonstration binary on a designated disposable device.
- State what a compile-only result does not prove.
Practice#
What Soong does
- Android.bp + hello.cpp
cc_binary name hello_source declares the input and output type.
- Soong dependency graph
m hello_source builds the named module with the selected target toolchain.
- Target artifact
For the shown default placement: PRODUCT_OUT/system/bin/hello_source.
- Optional matching-target run
Push the exact artifact to the named temporary path, run, compare output, and remove only that file.
Soong reads Android.bp declarations, constructs a dependency graph, and invokes the required compilers/linkers. A module is a named build unit; m builds from the top of the tree and is the environment’s build wrapper. Blueprint is declarative: it describes inputs and output type instead of spelling out each compiler command (Android.bp format). cc_binary describes a native executable, cc_library a native library, and cc_test a native test (the tag's init/Android.bp declares cc_test CtsInitTestCases). The exact module name comes from the name property, not a guessed directory or file name.
At this Android 14 tag, system/core/init/Android.bp contains this real excerpt:
cc_binary {
name: "init_second_stage",
recovery_available: true,
stem: "init",
defaults: ["init_defaults"],
static_libs: ["libinit"],
srcs: ["main.cpp"],
This is a verbatim partial block, not a complete file. name is the build graph name; stem selects the output filename (cc/cc.go defines the Stem property); srcs lists inputs (Android.bp format: "specifies the source files used to build the module"); static_libs adds a dependency (cc/linker.go: "statically linked into this module"); defaults reuses properties (Android.bp format defaults modules). The remainder includes platform-specific details. Do not copy boot-specific settings into a greeting program.
Why one module can have several variants
A module name identifies a build-graph unit, not necessarily one universally usable binary. Soong may build distinct host/target, architecture, partition, and other variants. A host tool runs on your workstation; a target executable runs under Android's ABI and libraries. A system-side dependency is not automatically an allowed vendor-side dependency. These distinctions let the same source participate in different supported environments without pretending the resulting binaries are interchangeable. The Android 14 Soong README and native module definitions provide the implementation context.
There is also a difference between a dependency being built and an executable being selected for a product image. A library dependency supplies code/symbols needed to link a module; product selection and installation properties decide which artifacts are packaged and where. Worked failure: m demo succeeds, but the device still runs an old demo. Candidate causes include no deployment, the wrong product/serial, a different install variant, or the process retaining the old executable mapping. Editing the source again does not distinguish those causes. Match module variant → output path → packaged/deployed bytes → running process before blaming the compiler.
The HAL implementation chapter later supplies a concrete variant/packaging example: a vendor binary with versioned AIDL NDK dependencies, an init file, and a VINTF fragment. The greeting exercise deliberately needs none of that machinery.
Add one small native program
First make sure the project has no unfamiliar edits:
cd ~/src/aosp-android14
repo status
git -C system/core status --short
If changes are present and you cannot identify who owns them, stop. Do not overwrite, stage, or clean them. In your own system/core worktree, create hello_source/hello.cpp:
#include <iostream>
int main() {
std::cout << "Hello from AOSP source!\n";
return 0;
}
#include makes standard-library declarations available; int is the return type; main is the entry function. Braces group its body and semicolons end statements. std::cout is the standard output stream (std names the standard library namespace); \n adds a newline; returning zero conventionally reports success. This C++ source is not a shell script or a build file.
In the same directory create Android.bp:
cc_binary {
name: "hello_source",
srcs: ["hello.cpp"],
}
This tells Soong to compile hello.cpp as the executable module hello_source. The module is deliberately harmless and has no system-setting or file-writing behavior. Do not add vendor placement, product makefiles, linker options, or dependencies without a real requirement.
Build the named module
From the checkout root, source the environment and use the Android 14 product verified in the previous lesson:
source build/envsetup.sh
lunch aosp_cf_x86_64_phone-userdebug
m hello_source
PRODUCT_OUT="$(get_build_var PRODUCT_OUT)"
test -x "$PRODUCT_OUT/system/bin/hello_source"
printf 'Artifact: %s\n' "$PRODUCT_OUT/system/bin/hello_source"
For this cc_binary with default system placement, the expected target path is $PRODUCT_OUT/system/bin/hello_source; the Android 14 Soong cc/binary.go gives cc_binary a bin installer in the system partition. The test -x is an explicit check, not a fabricated successful build transcript. If it fails, do not push a guessed file: read the first build error, inspect the module properties and actual PRODUCT_OUT, then locate the artifact with a focused search. m hello_source builds the module; it does not guarantee product-image inclusion, installation, or runtime behavior.
Runtime verification (optional; disposable Cuttlefish only)
This section requires a device started from the matching source build and a shell policy that permits execution from /data/local/tmp. Do not use a personal phone or an unrelated emulator. Identify the exact Cuttlefish serial from adb devices -l, then set it explicitly:
SERIAL='replace-with-the-cuttlefish-serial'
adb -s "$SERIAL" get-state
adb -s "$SERIAL" shell getprop ro.build.type
adb -s "$SERIAL" shell getprop ro.build.fingerprint
Proceed only if the device is the intended disposable Cuttlefish, reports userdebug, and its fingerprint/build identity matches the source-built image. Push to the one named temporary file, set its executable bit, run it, and remove only that file:
adb -s "$SERIAL" push "$PRODUCT_OUT/system/bin/hello_source" /data/local/tmp/hello_source
adb -s "$SERIAL" shell chmod 755 /data/local/tmp/hello_source
adb -s "$SERIAL" shell /data/local/tmp/hello_source
adb -s "$SERIAL" shell rm /data/local/tmp/hello_source
Expected program output is Hello from AOSP source!. If push, chmod, or execution is denied, preserve the diagnostic, clean up only if the file was created, and report the environment’s policy limitation. Do not remount /system, use root to defeat policy, or broaden permissions. A compile-only pass remains valid for learning Soong but does not prove image integration or Android runtime behavior.
Change and inspect
Change only the message to AOSP module built successfully. Re-run m hello_source; if doing runtime verification, push/run/remove the same exact temporary path again. Compare the resulting output to the changed string. Then inspect your patch:
git -C system/core diff --check
git -C system/core diff -- hello_source/hello.cpp hello_source/Android.bp
Never use git add -A in a shared checkout. Stage only the two intended files when you are ready to make a commit.
Knowledge check#
- What is the difference between C++ source and
Android.bp? - What exactly does
m hello_sourceprove? - Why does the runtime command use both a specific serial and a temporary destination?
- What should you do if the expected artifact path is absent?
Answers#
- C++ describes program behavior; Blueprint describes a named build module and its inputs.
- It asks Soong to resolve/build that module. It does not prove the binary is in the product image, installed, or executed.
- The serial prevents acting on an unintended connected device; the temporary path keeps the demonstration isolated and removable without changing a system partition.
- Stop before pushing. Inspect build output/module placement and locate the real artifact; never substitute a guessed binary.
Checkpoint#
Compile-only pass: the module builds and the exact executable exists at the resolved path; the diff contains only the intended source and Blueprint files. Runtime pass: additionally record the selected disposable serial, matching build identity, and exact expected output before removing the temporary file.
Revise: if Soong cannot find the module, inspect whether its Android.bp is in the scanned tree, whether the name is unique, and the first configuration error. Revisit source-to-module navigation if you cannot identify the owning declaration, or C/C++ source reading if compilation fails before linking. Retry the same narrow build after addressing the first error. If runtime is unavailable, report compile-only rather than claiming the executable ran. Continue to Debugging and Contributing.
Sources#
- Android.bp file format — module syntax,
srcs, and defaults modules (stemandstatic_libsare documented in the Soong source instead, below). - Android 14 Soong
cc/cc.go(Stemproperty) andcc/linker.go(Static_libs) — property definitions at the pinned tag. - Android 14
system/core/init/Android.bp— source of the verbatim excerpt and example test module declarations. - Android 14 Soong
cc/binary.go—cc_binaryuses the system partition'sbininstall directory by default. - Android 14
system/coretree — pinned project and module path. - Build Android —
envsetup.sh,lunch, and module build behavior. Current docs are general; use the tag’s product declaration.