Build Android in the Cloud
In this example, you are given a task to build Android app on a 64-bit AWS Linux build agent running in Amazon Cloud (AWS).
Install Android SDK
You have to start with installing Android SDK first.
Download
Start with downloading latest Linux version, found on this page. You can find direct link to tarball in “DOWNLOAD FOR OTHER PLATFORMS” section.
wget http://dl.google.com/android/android-sdk_r22.6.2-linux.tgz
Extract and Move
Extract it and put in preferred location, in this example it’s /usr/local/opt/android-sdk
# Extract.
tar xzf android-sdk_r22.6.2-linux.tgz
# Move.
mv android-sdk-linux /usr/local/opt/android-sdk
Configure PATH
Now configure ANDROID_HOME
and update the path. That is done by modifying ~/.bash_profile
, create the file if you don’t have it yet.
export ANDROID_HOME=/usr/local/opt/android-sdk
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/platform-tools
if [[ -d $ANDROID_HOME/build-tools ]]; then
ANDROID_BUILD_TOOLS_HOME=$(dirname $(find $ANDROID_HOME/build-tools -name aapt | tail -1))
echo "Android Built Tools located: $ANDROID_BUILD_TOOLS_HOME"
export PATH=$ANDROID_BUILD_TOOLS_HOME:$PATH
fi
You noticed that adding Android Build Tools to the path is optional. That’s because we didn’t install Build Tools yet.
Update Android SDK
This is where you update Android SDK, install all the tools and APIs, including Build Tools and Android Support Repository and Library. Remember, this Linux instance is running in the AWS Cloud meaning it’s headless (no GUI) and you only can work with the shell.
If you happened to read older version of this article, you’d remember seeing a lot of shell scripts using grep
.
This is a revised version, with much simpler and cleaner code.
The command that does the job is android update sdk
. You need to use --filter
option to specify list of packages you need to update or install. To get readable identifiers of all available packages, run the following command
android list sdk --all --extended
--extended
flag is used to display extended information about each package, including a human readable identifier. --all
is needed to include extra packages, like build tools, by default they won’t be listed. Here’s an example output.
----------
id: 3 or "build-tools-20.0.0"
Type: BuildTool
Desc: Android SDK Build-tools, revision 20
----------
id: 4 or "build-tools-19.1.0"
Type: BuildTool
Desc: Android SDK Build-tools, revision 19.1
----------
You can now compose filter as a comma-separated list of all package identifiers you want to install. Since it’s a headless build box, you will have to use --no-ui
option, and --all
is needed to include extra packages.
FILTER=tool,platform,android-20,build-tools-20.0.0,android-19,android-19.0.1
android update sdk --no-ui --all --filter $FILTER
This example installs Android SDK Tools, Platform tools, Build tools versions 20.0.0 and 19.0.1, as well as SDK Platform 19 and 20. Customize the list to your needs using proper identifiers.
Answering the Prompts
But it’s not done yet. When installing or updating, you will have to accept license prompts. Each package can have it’s own license requiring you to answer a prompt with “y”.
You’d probably think of yes
command line utility designed for this particular task. However it will not work. yes
outputs “y” to stdout too often with no options to put delays in between. Android SDK update tool expects not just “y”, but a “y” followed by return key, in other words, it expects “y\n” string as a whole. I don’t know the exact mechanics of yes
command, but if you try something like this
FILTER=tool,platform,android-20,build-tools-20.0.0,android-19,android-19.0.1
yes | android update sdk --no-ui --all --filter $FILTER
you will see that the license prompt will complain about incorrect input and fail after a number of attempts.
The solution is to put certain delay between “y” outputs to stdout. This code I found on the web, it’s reliable and does the job.
FILTER=tool,platform,android-20,build-tools-20.0.0,android-19,android-19.0.1
( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) \
| android update sdk --no-ui --all \
--filter ${FILTER}
Project Specific SDK Update
If you have various Android projects, each with it’s own requirements for Android packages, it would be reasonable to add Android SDK update task to the project’s build configuration. In this example I will use Gradle, so here’s an example of Gradle task
task updateSDK(type: Exec) {
ext.filter = "tool,platform-tool,android-20,build-tools-20.0.0,android-19,build-tools-19.1.0,build-tools-19.0.1,extra-android-support,extra-android-m2repository,extra-google-m2repository,extra-google-google_play_services,extra-google-google_play_services_froyo"
commandLine "sh", "-c", "( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) \
| android update sdk --no-ui --all \
--filter ${filter}"
}
Caveats
The last and very annoying bit, is that this script seems to update packages even if they are already installed. At the moment I have no clue what’s causing this behavior and what’s the best workaround. When it comes to running this script on one of the Bamboo agents in the cloud it doesn’t really matter, but when the script runs on one and only Mac build agent you have - this really slows down each build.
Install 32-bit Libraries
Before you try to build anything, you have to do one more thing.
Remember, the host OS is 64-bit Linux system. Android requires a bunch of 32-bit libraries and you have to install them. The Linux system in question is RPM-based so this example uses yum
command, change it to apt-get
or another package manager specific for your OS.
# Install 32-bit libraries.
sudo yum install glibc.i686, zlib.i686, libstdc++.so.6 libz.so.1
Now you’re good to go!
Summary
This is a TLDR or a summary section, that simply lists the solution “as is”.
# Install 32-bit libraries.
sudo yum install glibc.i686, zlib.i686, libstdc++.so.6 libz.so.1
# Update Android SDK on headless server.
FILTER=tool,platform,android-20,build-tools-20.0.0,android-19,android-19.0.1
( sleep 5 && while [ 1 ]; do sleep 1; echo y; done ) \
| android update sdk --no-ui --all \
--filter ${FILTER}