Preface

Let me begin by telling you that this is a horrible idea.

Linux has first-class support in the AOSP source code base. Compilation will break all the time because developers will only consider Linux and not Darwin. So if you want to go down a set of rabbit holes, then suit yourself. For what it’s worth, this article is a failure article. I never managed to successfully finish an AOSP build on macOS.

Plus, pair all the intensive workload with a thermally-limited laptop such as the MacBook Pro and you have a nice bomb on your desk.

Developers beware.

Caveats

  • Builds will break all the time (see Preface)
  • ART is not supported on Mac - you will get shitty performance with your builds
  • Macs are not designed for heavy workloads (again, see Preface)
  • Apple’s own tools are often incompatible with the build process. You’ll need to switch them out at times or even outright remove them, which is tedious and might break your system/build environment.

You still want to do it?

Getting the source ready

This part really was simple. I already had Homebrew installed on my Mac, so it was as simple as following the build environment setup instructions on the Android source website. I created a case-sensitive, journaled, HFS+ sparse bundle (read why here) of roughly 300 GB on my external hard drive.

Once the initial repository tools were set up, it was as simple as repo syncing the initial source tree. Problems began to arise after that, however..

Why sparse bundle?

  1. Sparse bundles are not fixed. They are dynamically created, which saves size on your external hard drive. Say your drive is about 500 GB and you have a 300 GB “image” on there. Let’s say you want to clone a second source and want to make another 300 GB “image”. With a disk image, you’re screwed, because the image always takes up 300 GB no matter what. With a sparse bundle, you can create a second 300 GB “image”, provided that the first “image” has left enough space on the drive for the second one (as in, you only used about, say, 150 GB on the first “image”).
  2. Sparse bundles are faster and more recent than sparse images. The latter was for Mac OS X 10.5 and before, while the former is for 10.6 and up. So unless you need compatibility you’re better off with a sparse bundle.

Actually building the damn thing

Error #1 : Wrong SDK

After starting up the build I was greeted by this message:

ninja: no work to do.
[1/1] out/soong/.bootstrap/bin/soong_build out/soong/build.ninja
FAILED: out/soong/build.ninja
out/soong/.bootstrap/bin/soong_build -t -l out/.module_paths/Android.bp.list -b out/soong -n out -d out/soong/build.ninja.d -o out/soong/build.ninja Android.bp

internal error: Could not find a supported mac sdk: ["10.10" "10.11" "10.12" "10.13"]

ninja: build stopped: subcommand failed.
20:17:47 soong bootstrap failed with: exit status 1

#### failed to build some targets (13 seconds) ####

Ouch. Fortunately, the solution was really simple. Just add your SDK version to the soong build file! Just in case the link goes dead, the procedure is to modify the build/soong/cc/config/x86_darwin_host.go file and add your SDK version in there. It should look something like this:

darwinSupportedSdkVersions = []string{
        "10.10",
        "10.11",
        "10.12",
        "10.13",

In my case, I added 10.14 there since I am on Mojave. You can find what SDK you have by running this command:

find /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs -iname "*.sdk"

In the end, the file should be like this:

darwinSupportedSdkVersions = []string{
        "10.10",
        "10.11",
        "10.12",
        "10.13",
        "10.14",
}

Yes, the extra comma there isn’t a mistake.

Onto the next error!

Error #2: flex

Weird flex but OK. Actually, I think this was a ROM issue.

The solution was to add a executable flag to flex. Go into prebuilts/build-tools/darwin-x86/bin and then run chmod +x flex to add the flag.

Next error, please.

Error #3: PATH_MAX not found

bootable/recovery/otautil/DirUtil.cpp:146:17: error: use of undeclared identifier 'PATH_MAX'
        char dn[PATH_MAX];
                ^
1 error generated.
[  0% 645/84824] //bootable/recovery/otautil:libotautil clang++ rangeset.cpp [darwin]
ninja: build stopped: subcommand failed.
17:58:21 ninja failed with: exit status 1

#### failed to build some targets (02:05 (mm:ss)) ####

Well, well, well. This is a really interesting bug.

So on macOS, the identifier PATH_MAX doesn’t really exist. Instead, there’s the identifier called MAX_PATH (I know, what the fuck?!) Everything the Apple way, I guess.

You have two options:

  1. Lazily edit that to say MAX_PATH and make future programmers suffer
  2. Or actually do your fucking job and use preprocessor directives to force the compiler to do something different when it detects itself running on Darwin.

Being the lazy guy I am I chose option 1 but I will post a writeup on how to do option 2 soon here.

I’m kidding. Let’s write those directives.

Go to the affected file, bootable/recovery/otautil/DirUtil.cpp and locate the problem portion:

    /* recurse over components */
    errno = 0;
    while ((de = readdir(dir)) != NULL) {
        //TODO: don't blow the stack
        char dn[MAX_PATH];
        if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
            continue;
        }

You can already see my lazily edited portion. So what do we do now?

Well, we need to make use of preprocessor directives. Just as a brief reminder, preprocessor directives run before the compiler. They specify which part of the code should be compiled depending on a number of factors defined in the directives. One of the checks could be the kernel the preprocessor is running on. To do that, we use #ifdefs and identifiers unique to the kernel.

Wait. You still don’t understand? You want to know all the identifiers? Well, you’re in luck. Here is the list of all operating system identifiers.

So how do we tell the preprocessor to use the correct identifier on the correct platform? We use #ifdefs:

#ifdef __unix__
  // Do something for Linux/UNIX
#elif define __APPLE__
  // Do something for macOS
#endif

Let’s try fixing that file, then!

    /* recurse over components */
    errno = 0;
    while ((de = readdir(dir)) != NULL) {
        //TODO: don't blow the stack
        #ifdef __unix__
            char dn[PATH_MAX];
        #elif define __APPLE__
            char dn[MAX_PATH];
        #endif
        if (!strcmp(de->d_name, "..") || !strcmp(de->d_name, ".")) {
            continue;
        }

Cool, that wasn’t so hard, right? Here is the commit if you are interested.

Note: I am not actually sure if this is the correct way to do this. If you have a better idea or the correct way, please get in touch and I’ll update the section.

Onto the next problem!

Error #4: sed problems

sed: illegal option -- z
usage: sed script [-Ealn] [-i extension] [file ...]
       sed [-Ealn] [-i extension] [-e script] ... [-f script_file] ... [file ...]
[  5% 4572/84144] build /Volumes/AOSP/bliss/p9.0/out/target/common/obj/PACKAGING/hiddenapi-light-greylist.txt
uniq: illegal option -- D
usage: uniq [-c | -d | -u] [-i] [-f fields] [-s chars] [input [output]]
[  5% 4573/84144] Building Kernel Config

This seems like a classic example of macOS trying to do things “the right way” and releasing a completely different version of sed that is different from everything else. Sigh. Let’s fix that up right now.

brew install gnu-sed

After installing gsed, we want to use it instead of sed. So include it in the PATH!

In ~/.bash_profile, add:

# Export GNU sed
export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"

Good to go! Next!

Error #5: elf not found

Oh great. Now what the fuck is elf?

Turns out elf is specific to Linux systems. But wait, there are a couple of good solutions. Solution number one comes from this GitHub Gist created in 2012. Solution number two comes from an XDA thread in 2016.

..you can already guess which one I picked. 2016 it is, baby! Install libelf:

brew install libelf

Then we need to add some additional definitions (thanks to the original XDA thread poster, 3martini!) in /usr/local/include/elf.h.

#include "../opt/libelf/include/libelf/gelf.h"
#define R_386_NONE 0
#define R_386_32 1
#define R_386_PC32 2
#define R_ARM_NONE 0
#define R_ARM_PC24 1
#define R_ARM_ABS32 2
#define R_MIPS_NONE 0
#define R_MIPS_16 1
#define R_MIPS_32 2
#define R_MIPS_REL32 3
#define R_MIPS_26 4
#define R_MIPS_HI16 5
#define R_MIPS_LO16 6
#define R_IA64_IMM64 0x23 /* symbol + addend, mov imm64 */
#define R_PPC_ADDR32 1 /* 32bit absolute address */
#define R_PPC64_ADDR64 38 /* doubleword64 S + A */
#define R_SH_DIR32 1
#define R_SPARC_64 32 /* Direct 64 bit */
#define R_X86_64_64 1 /* Direct 64 bit */
#define R_390_32 4 /* Direct 32 bit. */
#define R_390_64 22 /* Direct 64 bit. */
#define R_MIPS_64 18

Done? Cool!

In case that didn’t work, I will put option 2 here as well for archival purposes (in case the Gist disappears). Download the elf.h file here and place it in /usr/local/include/.

What’s up for the next trick?

Error #6: i386 - the straw that broke the camel’s back

This is the part where I gave up.

The build seemed to be progressing well, until it failed on a portion of the kernel build process. Looking at the logs, it seemed like some of the definitions for i386 were undefined.

Doing my due research, it seems like a recent breakage. Unfortunately, Apple deprecated the x86 libraries (i386) starting from macOS Mojave. Therefore, Xcode 10 and the counterpart Command Line Tools 10 do not support i386 and now has no more x86 libraries.

So I gave up. I did not want to install Xcode 9 on my macOS just to compile Android.

Update

If you want to try progressing after this step, there are a few possible solutions. I found this thread from the Android building group on Google Groups where the guy managed to fix his build by downloading an older macOS SDK version. This way, you get to keep Xcode 10 and still build Android.

Download the SDK here, and then copy it to Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs to resolve the issue.

You’ll also need a couple ROM-side patches. Apply them before building.

AOSP Gerrit: Ship a 64-bit libc++.dylib in platform-tools

AOSP Gerrit: Disable 32-bit Darwin builds

AOSP Gerrit: Support Mac 10.14 SDK

The last commit is actually similar to what we had on Error #1 so you don’t really have to cherry-pick that. It seems like these commits were merged after Pie was released, so if all goes well, we’ll get macOS Mojave compatibility back when Q comes out. (And then Catalina will come and fuck it all up again, but oh well.)

Conclusion

Will I revisit this topic again in the future? Perhaps.

Is Apple’s macOS not developer-friendly for people who primarily compile Android and Linux kernels? That really is questionable. Valve’s Proton team actually dropped support for macOS after a couple revisions because they didn’t want to deal with all the hassles the Darwin kernel presents.

This kind of sucks, because diversity is important, and being able to compile your codebase regardless of platform really is a huge boon to all developers out there. But until we get there, it seems like I will have to develop Android ROMs on Linux.