Build & run Android

Prepare a Linux workstation and boot a Cuttlefish build.

11 min readChapter 11 of 50Guided platform development
In this chapter

This lab describes a reproducible Android 14 baseline and the steps from host preparation to a locally built Cuttlefish device. It targets Ubuntu 22.04, x86_64 as a reference host (AOSP's requirements page says Android 11 or higher needs Ubuntu 18.04 or later; 22.04 is this lesson's choice), not a claim that this lesson has been executed on that host. AOSP sync and a full platform build are large, potentially hours-long operations; do not begin unless you have the resources and time. The tag is android-14.0.0_r1; do not substitute current-release target names.

Before you start#

Use a dedicated Ubuntu 22.04 x86_64 Linux machine or VM. AOSP’s current workstation guidance recommends 400 GB free (250 GB source plus 150 GB output) and 64 GB RAM; these are planning thresholds, not guarantees that every Android 14 build fits or succeeds. Use fast local storage, not a removable or network filesystem. For Cuttlefish, the host must expose KVM. A VM/cloud machine without nested virtualization cannot complete the run portion; stop at the build-only checkpoint instead of trying to bypass that requirement.

Preflight before sync:

bash
uname -m
lsb_release -ds
df -h "$HOME"
free -h
grep -m1 -E 'vmx|svm' /proc/cpuinfo
test -e /dev/kvm && ls -l /dev/kvm

Expect x86_64, Ubuntu 22.04, adequate free disk/RAM, a CPU virtualization flag, and accessible /dev/kvm for the run lab. If KVM is absent, host firmware, VM configuration, permissions, or cloud policy must be corrected by the host administrator; do not use sudo on arbitrary build commands.

Learning goals#

  • Prepare a pinned checkout without mixing it with current-release instructions.
  • Identify the product from the tag’s own product declaration.
  • Distinguish host tools, Android target artifacts, a successful build, and a running device.
  • Verify a running image by its fingerprint and shut down only the Cuttlefish instance you started.

Practice#

Install host prerequisites and Repo

For the reference Ubuntu host, install the packages listed by AOSP’s requirements page, which also documents apt-get install repo:

bash
sudo apt-get update
sudo apt-get install git-core gnupg flex bison build-essential zip curl \
  zlib1g-dev libc6-dev-i386 x11proto-core-dev libx11-dev lib32z1-dev \
  libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig
sudo apt-get install repo
repo version

The package-manager Repo launcher is the simple path; repo version must complete. If Ubuntu’s package is unavailable or too old, follow the official manual Repo installation: download the launcher to a file, verify its detached signature with the published Repo signing key, then install it into ~/bin and ensure that directory is on PATH. Do not pipe a download into a shell or run an unverified script. Keep the downloaded file and verification output until the signature succeeds.

Java/toolchain distinction: Android’s build uses checked-in/prebuilt toolchains for many compiler and runtime tools; the host java -version is not proof of the JDK the build will use. AOSP's requirements page says the latest release branch ships prebuilt OpenJDK, Make, and Python 3, and the Android 14 manifest maps prebuilts/jdk/jdk17 and other JDK projects. Do not install an arbitrary JDK or override JAVA_HOME to “fix” a build. After checkout, inspect prebuilts/jdk/ and build/soong/java/config/ for the pinned tree’s declared toolchain; source build/envsetup.sh configures the build environment. If the build reports a Java/tool error, capture the first diagnostic and follow the tag’s build scripts rather than guessing a version.

Sync exactly the pinned source

The canonical checkout path is ~/src/aosp-android14 because later lessons use it. Do not use mkdir -p on that final directory: mkdir -p would silently reuse a possibly unrelated Repo client. First inspect it without changing anything:

bash
ls -ld ~/src ~/src/aosp-android14 2>/dev/null

Choose exactly one case:

  • New checkout: use this only when ~/src/aosp-android14 does not exist. mkdir -p is allowed for its parent ~/src; the test ! -e gate makes the chain stop if the checkout path already exists. Every mutating step is joined with &&, so a failed creation or cd prevents repo init and sync from running.

    bash
    mkdir -p ~/src &&
    test ! -e ~/src/aosp-android14 &&
    mkdir ~/src/aosp-android14 &&
    cd ~/src/aosp-android14 &&
    repo init -u https://android.googlesource.com/platform/manifest -b android-14.0.0_r1 &&
    repo sync -c -j8
    
  • Your own existing checkout: only if you recognize it as your AOSP worktree and know its state. Verify it before resuming; never run repo init again on an existing checkout:

    bash
    cd ~/src/aosp-android14 && test -d .repo && repo status && repo manifest -r | head
    

    Read the status and confirm the manifest revisions are the intended Android 14 baseline. If there are unfamiliar local changes or you cannot establish ownership/tag, stop and ask the owner; do not reset, delete, overwrite, or sync it blindly. If it is your known checkout, its pinned baseline is correct, and you have reviewed the status, resume an interrupted fetch with:

    bash
    cd ~/src/aosp-android14 && repo sync -c -j8
    

&& runs the next command only when the previous one succeeds; do not replace these chains with a script that merely prints “STOP” and then continues. -b selects the manifest revision and -c fetches only the current manifest branch from the server; -j8 splits the sync across eight threads (Repo command reference). Network, storage, and server limits vary. An interrupted sync is normally resumed with repo sync; inspect its first failure before retrying. Do not delete .repo or reclone as a first response.

Only in a checkout whose ownership/state you have established, capture the resolved manifest to a new file (-r is --revision-as-HEAD in the Repo tool's manifest subcommand, a moving branch; "save revisions as current HEAD"). The test prevents replacing an existing artifact:

bash
cd ~/src/aosp-android14 && test ! -e manifest-pinned.xml && repo manifest -r > manifest-pinned.xml

If that file already exists, choose another new filename or read the existing one; do not overwrite it. The manifest output is evidence, not an AOSP project change.

Prove the product name from Android 14 source

At the tag, device/google/cuttlefish/AndroidProducts.mk lists aosp_cf_x86_64_phone, and its COMMON_LUNCH_CHOICES includes aosp_cf_x86_64_phone-userdebug. This is the tag-specific product+variant (not the current -aosp_current release-config form shown on the current Build Android page, e.g. aosp_cf_x86_64_only_phone-aosp_current-userdebug). Inspect it locally rather than relying on memory:

bash
cd ~/src/aosp-android14
rg -n 'aosp_cf_x86_64_phone|COMMON_LUNCH_CHOICES' device/google/cuttlefish/AndroidProducts.mk

Then configure:

bash
source build/envsetup.sh
lunch aosp_cf_x86_64_phone-userdebug
echo "$TARGET_PRODUCT $TARGET_BUILD_VARIANT $(get_build_var TARGET_ARCH)"

The expected configuration is product aosp_cf_x86_64_phone, variant userdebug, architecture x86_64. If the local product declaration or lunch output differs, stop and check repo manifest -r and the actual tag. Do not replace it with a modern target copied from current docs.

Build and inspect artifacts

A build is not a running image
  1. Pinned source and configured target

    Manifest → envsetup → verified Android 14 product and userdebug variant.

  2. Build produces two output classes

    ANDROID_HOST_OUT: host tools. ANDROID_PRODUCT_OUT: target artifacts.

  3. Launch matching Cuttlefish

    Host integration, KVM, compatible launcher flags, and the correct images are separate requirements.

  4. Verify the running image

    Selected ADB serial and matching fingerprint/build evidence; then stop only your instance.

Conceptual model. Android 14 teaching path; no build or boot is asserted. Host tools and target images must match. Stop at an unmet resource, KVM, or identity prerequisite.

The full build is optional for a read-only learner but necessary to create the image you will boot. It can take hours and uses substantial storage/CPU. After sourcing the environment and selecting the target:

bash
m
printf 'PRODUCT_OUT=%s\nANDROID_HOST_OUT=%s\n' "$ANDROID_PRODUCT_OUT" "$ANDROID_HOST_OUT"
ls -ld "$ANDROID_PRODUCT_OUT" "$ANDROID_HOST_OUT"

The build’s target artifacts live under $ANDROID_PRODUCT_OUT; host executables live under $ANDROID_HOST_OUT. They are not interchangeable. A success summary means the configured graph built; it does not mean the image booted. If building fails, save the first meaningful compiler/configuration diagnostic and command; the last ninja failed line is usually only a summary. Do not clear all output on speculation.

Launch only the source-matched Cuttlefish build

This run path is conditional on a completed build, host KVM, and Cuttlefish host integration compatible with the checkout. Android 14’s tagged Cuttlefish README documents launch_cvd/stop_cvd, KVM, and same-build host/image pairing. The installed system host packages and launcher should come from this same source build—not a random downloaded image or unrelated prebuilt host package. First verify that the environment exposes the built host launcher and this target’s output:

bash
source build/envsetup.sh
lunch aosp_cf_x86_64_phone-userdebug
test -x "$ANDROID_HOST_OUT/bin/launch_cvd"
test -d "$ANDROID_PRODUCT_OUT"
command -v adb

If a check fails, this tree/host combination has not established the local-build launch path; stop and use the Android 14 Cuttlefish README/build instructions to determine the matching host package. Do not silently substitute the current Cuttlefish artifact workflow. If all checks pass, try the built host tool against the configured product output. Caution: the tagged README launches from a directory holding the extracted host package and images with HOME=$PWD ./bin/launch_cvd; it does not document $ANDROID_HOST_OUT or --daemon. The --daemon form appears in current Cuttlefish docs. Confirm the flags with "$ANDROID_HOST_OUT/bin/launch_cvd" --help before relying on this:

bash
"$ANDROID_HOST_OUT/bin/launch_cvd" --daemon
adb devices -l

If the host tool does not automatically select $ANDROID_PRODUCT_OUT, stop and inspect that exact launcher’s --help output for its image-directory option (the tagged README does not document one); do not point it at unrelated images. The device must appear in adb devices -l. Select its exact serial from that output and verify the image identity:

bash
adb -s SERIAL shell getprop ro.build.fingerprint
adb -s SERIAL shell getprop ro.build.type

Replace SERIAL with the specific Cuttlefish serial, not the literal word. Compare the fingerprint/build properties with your configured build output. userdebug is expected. A connected device with a fingerprint alone does not prove it is your build; if identity cannot be matched, the runtime check has not passed.

Stop the Cuttlefish instance using the matching host tool from the same environment. The tagged README stops with HOME=$PWD ./bin/stop_cvd from the directory used to launch, so keep the same directory and environment:

bash
"$ANDROID_HOST_OUT/bin/stop_cvd"

Use stop_cvd only for the instance you started; do not kill processes or remove Cuttlefish state by guessing. The exact product and source host tools above are tag-scoped. A successful local compile, a launch, and a verified fingerprint are three separate claims.

No-checkout/no-build alternative: use the browser-only Finding Your Way in AOSP Gitiles worksheet. Open the exact Android 14 r1 manifest and tagged Cuttlefish product declaration; record the tag and the product/lunch-choice lines as source facts. This does not require running repo manifest, creating a local file, or syncing source. It completes source orientation only; it does not certify that an image builds, launches, or runs.

Knowledge check#

  1. Why is java -version insufficient to identify the JDK used by an Android 14 build?
  2. What source file verifies the exact lunch product, and what values should echo report?
  3. What do ANDROID_HOST_OUT and ANDROID_PRODUCT_OUT contain?
  4. What evidence is needed before claiming that your local image is running?

Answers#

  1. The tree may configure checked-in prebuilts; the host’s default Java executable is not necessarily selected by the build.
  2. device/google/cuttlefish/AndroidProducts.mk; product aosp_cf_x86_64_phone, variant userdebug, architecture x86_64.
  3. Host executables/tools versus target-product artifacts. A matching host launcher and matching product image are both required.
  4. A connected Cuttlefish serial plus build identity properties that match the local configured output. Build success alone is not runtime proof.

Checkpoint#

Source-only pass: record the pinned manifest revision and show the tag’s product declaration and lunch choice. Build pass: also capture successful m summary and resolved product/host output paths. Runtime pass: also capture the specific ADB serial, matching fingerprint/build type, and successful stop_cvd completion.

If resource/KVM gates fail, stop at source-only or build-only; do not mark runtime complete. If lunch fails, compare manifest and product declarations before changing the target. If launch fails, capture its first error and verify host tool/image pairing. Continue to Soong and your first change for a smaller targeted build.

Sources#

Next: Soong and your first change.

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.