Building Bitbucket Pipelines for Android
TL;DR
Something weird is going on with certain versions of the Android Command Line Tools install of ndk-build
that comes with ndk-bundle
. ndk-bundle
includes a version of make
so that, you know, they can make
without worrying about whether it’s installed, BUT it appears that at least one of the binaries included in the ndk-bundle
package can’t find make
. You can use a Docker container to track down the location of the make
executable.
The Pipeline File and the Error
bitbucket-pipelines.yml:
image: java:8
pipelines:
branches:
my-branch:
# build the app
- step:
name: Build
script:
- wget --quiet --output-document=cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip
- unzip -o -qq cmdline-tools.zip -d .
- export ANDROID_HOME="/opt/atlassian/pipelines/agent/build/cmdline-tools"
- export PATH="$ANDROID_HOME/bin:$ANDROID_HOME/platform-tools:$PATH"
- yes | sdkmanager --install "ndk-bundle" --sdk_root=$ANDROID_HOME --verbose
- yes | sdkmanager --install "platform-tools" --sdk_root=$ANDROID_HOME
...<SNIP>
- ./gradlew clean --full-stacktrace --scan
And I was getting this error message:
> Task :pluginproject:ndkClean FAILED
ERROR: Cannot find 'make' program. Please install Cygwin make package
or define the GNUMAKE variable to point to it.
/opt/atlassian/pipelines/agent/build/cmdline-tools/ndk-bundle/build/ndk-build: line 151: file: command not found
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':pluginproject:ndkClean'.
> Process 'command '/opt/atlassian/pipelines/agent/build/cmdline-tools/ndk-bundle/ndk-build'' finished with non-zero exit value 1
The Solution
Okay, so first of all, that whole Cygwin error is generic and what my friend called it in a spectacular portmanteau of colloquialisms, “smoking herring.”
The solution is to set an environment variable called GNUMAKE
and point it at the make
executable that’s in your ndk-bundle
package.
- export GNUMAKE=$ANDROID_HOME/ndk-bundle/prebuilt/linux-x86_64/bin/make
The tricky part in all this was trying to remember how to use Docker - because lemme tell ya, I was tired of burning build minutes on Bitbucket doing things like “ls -la.”
Doing this locally
You basically have to create a local version of Bitbucket Pipelines’ build environment:
-
First, set up the Docker container and do a bunch of work in it:
# run your docker container in interactive, TTY mode $ docker run -it java:8 /bin/bash root@a128c0c78584:/# ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var # create the working directory that mimics BBP's env root@a128c0c78584:/# mkdir -p /opt/atlassian/pipelines/agent/build root@a128c0c78584:/# cd !$ # so now you're going to run all of the installation commands that you'd have your BBP robot do. # setting ANDROID_HOME, wgetting and unzipping, sdkmanager installs, etc. root@a128c0c78584:/opt/atlassian/pipelines/agent/build# get --quiet --output-document=cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip
…etc.
-
Now, find the
make
executable and point the GNUMAKE variable to it. In this case I’m looking inside thendk-bundle
directory because I knew that thendk-build
tool that came with it was the package that was having a hard time findingmake
.# find your `make` root@a128c0c78584:/opt/atlassian/pipelines/agent/build/cmdline-tools/ndk-bundle# find . -name make ./prebuilt/linux-x86_64/bin/make # tell Gradle how to find it root@a128c0c78584:/opt/atlassian/pipelines/agent/build/cmdline-tools/ndk-bundle# export GNUMAKE=$ANDROID_HOME/ndk-bundle/prebuilt/linux-x86_64/bin/make
-
Now, in another window, run a
docker cp
command to get your files to the “build” directory”inger.klekacz@~/Projects/ik-android $ docker cp . musing_mendel:/opt/atlassian/pipelines/agent/build
-
In the terminal window where you’re inside the Docker container, you can try the build now.
root@a128c0c78584:/opt/atlassian/pipelines/agent/build/cmdline-tools/ndk-bundle# cd $AGENT_HOME # make sure your buildfiles are there, the ones you copied over from your parent host # here you can see some files that are decidedly not native to Docker - ones you downloaded and ones you copied. root@a128c0c78584:/opt/atlassian/pipelines/agent/build/cmdline-tools# ls -l drwxr-xr-x 4 504 dialout 4096 Mar 31 14:58 app drwxr-xr-x 3 504 dialout 4096 Mar 31 14:58 bash_scripts -rw-r--r-- 1 504 dialout 2310 Apr 7 00:16 bitbucket-pipelines.yml -rw-r--r-- 1 504 dialout 1074 Mar 31 14:58 build.gradle drwxr-xr-x 14 root root 4096 Apr 7 01:01 cmdline-tools -rw-r--r-- 1 root root 87259900 Apr 7 00:50 cmdline-tools.zip drwxr-xr-x 2 504 dialout 4096 May 26 2020 failnet drwxr-xr-x 3 504 dialout 4096 May 26 2020 gradle -rw-r--r-- 1 504 dialout 21 May 26 2020 gradle.properties -rwxr-xr-x 1 504 dialout 5299 May 26 2020 gradlew -rw-r--r-- 1 504 dialout 2260 May 26 2020 gradlew.bat # run the clean! root@a128c0c78584:/opt/atlassian/pipelines/agent/build/cmdline-tools# ./gradlew clean Downloading https://services.gradle.org/distributions/gradle-6.1.1-all.zip <SNIP - downloading dots> Unzipping /root/.gradle/wrapper/dists/gradle-6.1.1-all/cfmwm155h49vnt3hynmlrsdst/gradle-6.1.1-all.zip to /root/.gradle/wrapper/dists/gradle-6.1.1-all/cfmwm155h49vnt3hynmlrsdst Set executable permissions for: /root/.gradle/wrapper/dists/gradle-6.1.1-all/cfmwm155h49vnt3hynmlrsdst/gradle-6.1.1/bin/gradle Welcome to Gradle 6.1.1! <SNIP> Starting a Gradle Daemon (subsequent builds will be faster) > Configure project :pluginproject executing ndkBuild executing ndkBuild clean > Task :pluginproject:ndkClean /opt/atlassian/pipelines/agent/build/cmdline-tools/ndk-bundle/build/ndk-build: line 151: file: command not found make: Entering directory '/opt/atlassian/pipelines/agent/build/pluginproject/src/main' Android NDK: WARNING: APP_PLATFORM android-16 is higher than android:minSdkVersion 1 in ./AndroidManifest.xml. NDK binaries will *not* be compatible with devices older than android-16. See https://android.googlesource.com/platform/ndk/+/master/docs/user/common_problems.md for more information. make: Leaving directory '/opt/atlassian/pipelines/agent/build/pluginproject/src/main' make: Entering directory '/opt/atlassian/pipelines/agent/build/pluginproject/src/main' ...<SNIP - tests running>... make: Leaving directory '/opt/atlassian/pipelines/agent/build/pluginproject/src/main' BUILD SUCCESSFUL in 1m 14s 3 actionable tasks: 1 executed, 2 up-to-date
—
MOAR TIPS
Some bonus Docker commands for people like me who only directly interact with Docker about once a quarter (though it looks like I’ll be doing it more often now…), and who ALWAYS piss away a nontrivial amount of minutes trying to remember how to start a container that doesn’t just quit immediately:
# fire up a Docker container that will keep running even once it's "done," like an Ubuntu image or some other OS
# it will stop when you `exit` it
$ docker run -it <image name>:<version> /bin/bash
# you MUST specify -it the first time you build this container. if it's already built, and you try to run it with -it, it'll get confused.
# find it again in the future - it will have all your files, but unless you put your variable declarations in your .bash_rc, you'll lose those
$ docker container ls -a
# Log into it again:
$ docker start <container id>
$ docker attach <container id>
# be advised that you may have to hit Enter/Return once you've done "docker attach" or the interface won't look like it's done anything.
# got me good a couple times.