April 19, 2026
8 min read

Building ArjunaOS #5: Your First Build

ArjunaOS
LineageOS
AOSP
Android Build
lunch
Emulator
PPrasad Manoj Parulkar
Prasad Manoj Parulkar

AOSP Engineer

The source tree is finally local, so this is where we ask it to prove itself. We source the build environment, use the correct modern lunch target, run the first real LineageOS build with mka, inspect the generated emulator images, and boot them for the first time.

In the last article, we finally got the tree onto disk.

That was a big milestone, but still a quiet one. A synced source tree looks impressive in du -sh, but it does not prove much by itself. The tree has to survive envsetup.sh, take a real lunch target, compile, produce images, and then boot into something that does not immediately embarrass us.

This article is that moment.

We are going to source the environment, pick the emulator target for this series, run the first real build, inspect the images that come out, and then boot them in the emulator. No customization yet. No boot animation work. No ArjunaOS branding. First we make sure the tree can stand up on its own legs.

Figure 1: Article 5 is the first full build loop. Source the environment, choose the target, build the images, inspect the output, then boot the result in the emulator.

Source the build environment first

Every Android build article eventually says this, but a lot of them say it too quickly.

Before lunch, before mka, before anything interesting, source the build environment:

cd <path-to-your-workspace>
. build/envsetup.sh

For this series run, the workspace path was:

/home/prasad/Desktop/Work/arjunaos

Why bother with envsetup.sh?

Because this is what wires the tree into your shell. It adds helper functions like lunch, m, mka, and it also points tools like the emulator at the right prebuilts inside the tree. Without it, you are basically poking at the checkout with a half-configured shell and hoping Android build magic reads your mind. It does not.

One nice side effect is that the emulator binary from prebuilts/android-emulator/ also lands on PATH. That becomes important later in this same article.

Pick the right lunch combo

This is where one small but important detail changed from a lot of older Android tutorials.

For this tree, use the full product, release, and variant form:

lunch lineage_sdk_phone_x86_64-trunk_staging-userdebug

Older Android tutorials often show shorter lunch combos. For this setup, the full modern form is the right one to use.

That target resolved cleanly and produced the summary we actually wanted:

  • TARGET_PRODUCT=lineage_sdk_phone_x86_64
  • TARGET_BUILD_VARIANT=userdebug
  • TARGET_ARCH=x86_64
  • LINEAGE_VERSION=23.2-20260418-UNOFFICIAL-sdk_phone_x86_64

This is the first real checkpoint in the article. If lunch does not settle correctly, there is no point charging ahead into a build and pretending the machine will sort it out later.

Figure 2: The first build target used in this series. The important part is that the tree accepted the full modern lunch combo and produced a clean target summary.

One odd line did appear during this run:

Trying dependencies-only mode on a non-existing device tree?

It looked suspicious, yes. But in this case it did not stop lunch, and the target summary still came out correctly. So treat that kind of message carefully. Weird does not always mean fatal.

Starting the first build

This part is worth being deliberate about.

For the first build in this series, I used:

mka

Not bacon. Not a packaging target. Not a flashable zip path.

That is on purpose.

The first thing we need to prove is not that the tree can package a distribution artifact. The first thing we need to prove is that the tree can actually compile and produce working emulator images. mka is a good fit for that milestone. It is the blunt, practical "build the tree" command, and in this LineageOS setup it simply forwards into the normal build wrapper.

One honest note from this run: the first long build attempt did not finish cleanly.

It failed late because a Chromium WebView prebuilt was still sitting there as an LFS pointer instead of the real APK. That is the kind of annoying detour people really do hit on platform builds. After the missing LFS-backed content was fetched, the next build completed successfully in about 26 minutes:

#### build completed successfully (26:12 (mm:ss)) ####

There were still warnings in the log. That is normal. Android builds are noisy. The line that matters is the successful completion footer.

Figure 3: The successful rebuild result for this series run. Warnings are still visible in the tail, but the important part is the successful build footer at the end.

Do not jump straight to bacon just because it sounds more ROM-like. Packaging comes later in the series. Right now we only want to prove that the source tree can turn into real bootable images.

Inspect what the build actually produced

If the build finishes and you do not inspect the output, you are missing half the lesson.

For this run, the important files landed under:

out/target/product/emu64x

I checked the main output images directly:

ls -lh \
  out/target/product/emu64x/system.img \
  out/target/product/emu64x/vendor.img \
  out/target/product/emu64x/super.img \
  out/target/product/emu64x/vbmeta.img \
  out/target/product/emu64x/userdata.img \
  out/target/product/emu64x/kernel-ranchu

On this build, those came out like this:

  • system.img at about 940M
  • vendor.img at about 125M
  • super.img at about 8.1G
  • vbmeta.img at about 64K
  • userdata.img at about 550M
  • kernel-ranchu at about 22M

That tells you something very useful immediately: the tree did not just compile a few jars and call it a day. It produced the core pieces the emulator needs to boot.

Figure 4: This is the output proof that matters after the successful rebuild. The exact sizes will vary a bit, but the key point is that real emulator images now exist under out/target/product/emu64x.

Figure 5: The product output directory is not random clutter. The main build artifacts sit there in a very teachable shape: kernel, partition images, verification metadata, and the writable runtime overlays the emulator creates later. This diagram uses a generic output-directory placeholder. In this series run, that directory was out/target/product/emu64x.

Also notice the product output directory name. The lunch target was lineage_sdk_phone_x86_64, but the build artifacts landed under emu64x. That is normal in this setup because the product inherits from the goldfish emulator configuration where PRODUCT_DEVICE := emu64x.

This is exactly why checking the output directory is useful. It teaches you what the target really maps to, not just what the lunch string looked like.

Boot it in the emulator

Now the satisfying part.

In the same tree, with the environment sourced and the lunch target selected, I launched the emulator like this:

emulator

That works here because after envsetup.sh and lunch, the shell already knows where the product output lives. The emulator can pick up the right images from ANDROID_PRODUCT_OUT instead of forcing us to hand-type every path manually.

For the article, I actually like this better than leading with extra flags. It keeps the first boot step closer to the general emulator flow instead of making it look like we always need a special incantation just to launch the build.

If you later need a completely fresh start, -wipe-data is useful. If snapshot behavior gets weird, -no-snapshot is also a handy flag to remember. But those are good troubleshooting tools. They do not need to be the first thing a reader sees.

On first launch, the emulator also created the writable runtime images you would expect:

  • system-qemu.img
  • vendor-qemu.img
  • userdata-qemu.img
  • cache.img

That is another quiet proof point. It means the emulator is no longer only looking at the static build artifacts. It is actually spinning up a writable runtime environment on top of them.

And yes, the boot completed. That is the milestone this whole article was working toward.

Figure 6: First boot from the images built in this article. This is where the source tree finally turns into a running system instead of just a pile of output files.

If your first boot feels slow, do not panic immediately. Cold boots in the emulator are not graceful little race cars. Let it breathe for a minute before you decide something is broken.

What this milestone should give you

If your run reached this point, you now have:

  • a sourced Android build shell
  • a valid lunch target for the emulator path used in this series
  • a successful build with mka
  • real output images in out/target/product/emu64x
  • a working emulator boot from those images

That is the milestone here. The tree is no longer just synced source sitting on disk. It has turned into a booting system on your machine.

Notice what we still have not done yet:

  • no ArjunaOS branding changes
  • no custom boot animation
  • no settings tweaks
  • no overlays
  • no system app work
  • no packaging or distribution flow

That is fine.

The important thing here is simple: the synced LineageOS tree on your machine now compiles, produces real images, and boots in the emulator. We can start changing ArjunaOS only after that foundation is proven.

That is already a real win.

Coming up next

In the next article, we slow down and unpack what just happened.

We used envsetup.sh, lunch, and mka, but we still have not really explained the build system yet. That is the right next step now. Before we start changing ArjunaOS itself, we should understand how Soong, product definitions, inherited makefiles, and output targets are shaping this build behind the scenes.

That way, when we start modifying things later, it feels intentional instead of like trial and error.

Article 5 of 30

Building ArjunaOS: Create Your Own Custom Android OS

Learn to build your own custom Android OS from scratch. This series walks you through creating ArjunaOS, a custom ROM based on LineageOS, starting from setting up your build environment and compiling your first build, through branding and system customization, to adding custom system services and advanced features. Covers the complete journey across three parts: building the OS, adding features, and deep customization.

Series Progress17%

Comments (0)

Sign in to join the conversation

Loading comments...