Building a continuous-integration Android build server on FreeBSD: Part one: building APKs using Linux emulation


November 2016.
Image The FreeBSD logo

In this series of blog posts, we're going to create a Android build server for continuous integration on FreeBSD.

I'll be using a normal 10.2-RELEASE:

# uname -a
FreeBSD androidbuild 10.2-RELEASE-p11 FreeBSD 10.2-RELEASE-p11 #0: Wed Jan 27 15:56:01 CET 2016 root@androidbuild:/usr/obj/usr/src/sys/GENERIC amd64

Parts of this posts are duplicate from my old post on how to build APKs on FreeBSD.

Step 1: installing the system dependencies



We're going to install many packages, including a lot of GNU-style one. So if you care about having your systems cleans, I suggest your do all this in a jail.

Install gradle:


# make -C /usr/ports/devel/gradle/ install clean

Go drink the longest coffee you can brew. That stuff installs a lot of dependencies.

Install bash and link it to /bin/bash:

# make -C /usr/ports/shells/bash install clean
# ln -s /usr/local/bin/bash /bin/bash

Install git:

# make -C /usr/ports/devel/git install clean

Install python:

# make -C /usr/ports/lang/python install clean

Create a nice user to execute the builds:

# pw useradd androidbuild
# mkdir /home/androidbuild
# chown androidbuild:androidbuild /home/androidbuild

Load FreeBSD's Linux emulation system and install a base CentOS 6 system:

# kldload linux
# cd /usr/ports/emulators/linux_base-c6 && make install distclean

The latest version of the build tools (24.0.0) use 64bit binaries and libraries. If you want to be able to build your apks with them, you'll also need the 64bit Linux emulation. In that case, you must be running a FreeBSD version >= 10.3. In that case, you should do instead:

# kldload linux
# kldload linux64
echo "OVERRIDE_LINUX_BASE_PORT=c6_64" >> /etc/make.conf
echo "OVERRIDE_LINUX_NONBASE_PORTS=c6_64" >> /etc/make.conf
# cd /usr/ports/emulators/linux_base-c6 && make install distclean

If you alread had the 32 bit CentOS base, uninstall the port, remove the /compat/linux files and reinstall the port again after setting the two entries in make.conf.

Step 2: setting-up the Android SDK



Download the SDK



Fetch and extract the latest version of the Linux SDK:

% fetch 'https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz'
% tar xzf android-sdk_r24.4.1-linux.tgz
% setenv ANDROID_HOME /home/androidbuild/android-sdk-linux/

Patch the SDK



As it is now, the SDK will download build tools for Windows, since it obviously won't recognize our FreeBSD.
Since we're going to use the Linux emulation, we need to path the system so that it downloads Linux binaries:

Download the source of the SDK base:

% git clone https://android.googlesource.com/platform/tools/base

Apply the following patch:

diff -r -u a/common/src/main/java/com/android/SdkConstants.java b/common/src/main/java/com/android/SdkConstants.java
--- a/common/src/main/java/com/android/SdkConstants.java 2016-09-06 07:56:56.325948102 +0000
+++ b/common/src/main/java/com/android/SdkConstants.java 2016-09-06 07:58:10.721944140 +0000
@@ -635,6 +635,8 @@
return PLATFORM_WINDOWS;
} else if (os.startsWith("Linux")) { //$NON-NLS-1$
return PLATFORM_LINUX;
+ } else if (os.startsWith("FreeBSD")) { //$NON-NLS-1$
+ return PLATFORM_LINUX;
}

return PLATFORM_UNKNOWN;
diff -r -u a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java
--- a/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java 2016-09-06 07:56:56.828948347 +0000
+++ b/sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java 2016-09-06 07:58:35.160941890 +0000
@@ -216,6 +216,8 @@
hostOS = HostOs.WINDOWS;
} else if (os.startsWith("Linux")) { //$NON-NLS-1$
hostOS = HostOs.LINUX;
+ } else if (os.startsWith("FreeBSD")) { //$NON-NLS-1$
+ hostOS = HostOs.LINUX;
}

BitSize jvmBits;

Rebuild the patched files.

% javac common/src/main/java/com/android/SdkConstants.java
% javac sdklib/src/main/java/com/android/sdklib/internal/repository/archives/ArchFilter.java -cp "sdklib/src/main/java:common/src/main/java:annotations/src/main/java"

Replace the files inside the jar.

% cd sdklib/src/main/java/ && jar uf ${ANDROID_HOME}/tools/lib/sdklib.jar com/android/sdklib/internal/repository/archives/ArchFilter.class
% cd common/src/main/java && jar uf ${ANDROID_HOME}/tools/lib/common.jar com/android/SdkConstants.class

See this patch on the Android website: https://android-review.googlesource.com/#/c/100271/

Download the SDK packages and set-up the build tools



Go to the tool directory:

% cd ${ANDROID_HOME}/tools/

In the example, we're going to build an APK thats uses API version 23.
Let's download the right packages:

% ./android list sdk -u -a
[...]
7- Android SDK Build-tools, revision 23.0.3
31- SDK Platform Android 6.0, API 23, revision 3
119- Google APIs, Android API 23, revision 1
% ./android update sdk -u -a -t tools,platform-tools,7,31,119

Let's find the Linux binaries in the build tools, and brand them as such:

% find build-tools/23.0.3/ -maxdepth 1 -type f -print0 | xargs -0 -n 10 file | grep "ELF"
build-tools/23.0.3/mipsel-linux-android-ld: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.8, stripped
build-tools/23.0.3/arm-linux-androideabi-ld: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.8, stripped
build-tools/23.0.3/llvm-rs-cc: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=8a4ffbc0e197147c4e968722e995605e1d06ea88, not stripped
build-tools/23.0.3/i686-linux-android-ld: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.8, stripped
build-tools/23.0.3/bcc_compat: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=d565d03b7bafd03d335bdd246832bb31c7cca527, not stripped
build-tools/23.0.3/aapt: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=cfb63b4ad11d0c2d59f10329f0116706e99bf72e, not stripped
build-tools/23.0.3/aidl: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=3cbd3d742040d61877a3c6544778bf4701b2f62d, not stripped
build-tools/23.0.3/aarch64-linux-android-ld: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.8, stripped
build-tools/23.0.3/split-select: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=b3bfb153d0ffaef6ed84c316ff682381ba8d75b2, not stripped
build-tools/23.0.3/dexdump: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=a678a8163a2483107d285ffdc94fdde0d4fb2178, not stripped
build-tools/23.0.3/zipalign: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=7d64216663df9fd3b4048952d095fbd07cb4284f, not stripped

% find build-tools/24.0.3/ -maxdepth 1 -type f -print0 | xargs -0 -n 10 file | grep "ELF" | awk 'BEGIN { FS = ":" } ; {print $1}' | xargs brandelf -t Linux
% find build-tools/24.0.3/ -maxdepth 1 -type f -print0 | xargs -0 -n 10 file | grep "ELF" | awk 'BEGIN { FS = ":" } ; {print $1}' | xargs chmod +x

The SDK is now configured and we should be able to build our apps there.

Step 3: building the APK



Fetch the sources of your app:

% git clone 'https://git.example.org/android-app'
% cd android-app

Be sure to have set environment variable ANDROID_HOME to the SDK location.
Start gradle and see if it complains.

% gradle tasks
------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Android tasks
-------------
androidDependencies - Displays the Android dependencies of the project.
signingReport - Displays the signing info for each variant.
sourceSets - Prints out all the source sets defined in this project.
[...]
To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

BUILD SUCCESSFUL

Total time: 14.839 secs

Gradle is happy, let's try a build:

% gradle assembleRelease
[...]
BUILD SUCCESSFUL

Total time: 2 mins 24.784 secs

Let's look at the build output:

% ls Truc/build/outputs/apk/
truc-app-release-unaligned.apk truc-app-release.apk

Success!