Android
The Android build environment provides three major features and a number of supporting features.
Using androidenv with Android Studio
Use the android-studio-full
attribute for a very complete Android SDK, including system images:
buildInputs = [ android-studio-full ];
This is identical to:
buildInputs = [ androidStudioPackages.stable.full ];
Alternatively, you can pass composeAndroidPackages to the withSdk
passthru:
buildInputs = [
(android-studio.withSdk (androidenv.composeAndroidPackages {
includeNDK = true;
}).androidsdk)
];
These will export ANDROID_SDK_ROOT and ANDROID_NDK_ROOT to the SDK and NDK directories in the specified Android build environment.
Deploying an Android SDK installation with plugins
Alternatively, you can deploy the SDK separately with a desired set of plugins, or subsets of an SDK.
with import <nixpkgs> {};
let
androidComposition = androidenv.composeAndroidPackages {
cmdLineToolsVersion = "8.0";
toolsVersion = "26.1.1";
platformToolsVersion = "30.0.5";
buildToolsVersions = [ "30.0.3" ];
includeEmulator = false;
emulatorVersion = "30.3.4";
platformVersions = [ "28" "29" "30" ];
includeSources = false;
includeSystemImages = false;
systemImageTypes = [ "google_apis_playstore" ];
abiVersions = [ "armeabi-v7a" "arm64-v8a" ];
cmakeVersions = [ "3.10.2" ];
includeNDK = true;
ndkVersions = ["22.0.7026061"];
useGoogleAPIs = false;
useGoogleTVAddOns = false;
includeExtras = [
"extras;google;gcm"
];
};
in
androidComposition.androidsdk
The above function invocation states that we want an Android SDK with the above specified plugin versions. By default, most plugins are disabled. Notable exceptions are the tools, platform-tools and build-tools sub packages.
The following parameters are supported:
cmdLineToolsVersion
, specifies the version of thecmdline-tools
package to usetoolsVersion
, specifies the version of thetools
package. Noticetools
is obsolete, and currently only26.1.1
is available, so there's not a lot of options here, however, you can set it asnull
if you don't want it.platformsToolsVersion
specifies the version of theplatform-tools
pluginbuildToolsVersions
specifies the versions of thebuild-tools
plugins to use.includeEmulator
specifies whether to deploy the emulator package (false
by default). When enabled, the version of the emulator to deploy can be specified by setting theemulatorVersion
parameter.cmakeVersions
specifies which CMake versions should be deployed.includeNDK
specifies that the Android NDK bundle should be included. Defaults to:false
.ndkVersions
specifies the NDK versions that we want to use. These are linked under thendk
directory of the SDK root, and the first is linked under thendk-bundle
directory.ndkVersion
is equivalent to specifying one entry inndkVersions
, andndkVersions
overrides this parameter if provided.includeExtras
is an array of identifier strings referring to arbitrary add-on packages that should be installed.platformVersions
specifies which platform SDK versions should be included.
For each platform version that has been specified, we can apply the following options:
includeSystemImages
specifies whether a system image for each platform SDK should be included.includeSources
specifies whether the sources for each SDK version should be included.useGoogleAPIs
specifies that for each selected platform version the Google API should be included.useGoogleTVAddOns
specifies that for each selected platform version the Google TV add-on should be included.
For each requested system image we can specify the following options:
systemImageTypes
specifies what kind of system images should be included. Defaults to:default
.abiVersions
specifies what kind of ABI version of each system image should be included. Defaults to:armeabi-v7a
.
Most of the function arguments have reasonable default settings.
You can specify license names:
extraLicenses
is a list of license names. You can get these names from repo.json orquerypackages.sh licenses
. The SDK license (android-sdk-license
) is accepted for you if you set accept_license to true. If you are doing something like working with preview SDKs, you will want to addandroid-sdk-preview-license
or whichever license applies here.
Additionally, you can override the repositories that composeAndroidPackages will pull from:
repoJson
specifies a path to a generated repo.json file. You can generate this by runninggenerate.sh
, which in turn will call intomkrepo.rb
.repoXmls
is an attribute set containing paths to repo XML files. If specified, it takes priority overrepoJson
, and will trigger a local build writing out a repo.json to the Nix store based on the given repository XMLs.
{
repoXmls = {
packages = [ ./xml/repository2-1.xml ];
images = [
./xml/android-sys-img2-1.xml
./xml/android-tv-sys-img2-1.xml
./xml/android-wear-sys-img2-1.xml
./xml/android-wear-cn-sys-img2-1.xml
./xml/google_apis-sys-img2-1.xml
./xml/google_apis_playstore-sys-img2-1.xml
];
addons = [ ./xml/addon2-1.xml ];
};
}
When building the above expression with:
$ nix-build
The Android SDK gets deployed with all desired plugin versions.
We can also deploy subsets of the Android SDK. For example, to only the
platform-tools
package, you can evaluate the following expression:
with import <nixpkgs> {};
let
androidComposition = androidenv.composeAndroidPackages {
# ...
};
in
androidComposition.platform-tools
Using predefined Android package compositions
In addition to composing an Android package set manually, it is also possible to use a predefined composition that contains a fairly complete set of Android packages:
The following Nix expression can be used to deploy the entire SDK:
with import <nixpkgs> {};
androidenv.androidPkgs.androidsdk
It is also possible to use one plugin only:
with import <nixpkgs> {};
androidenv.androidPkgs.platform-tools
Spawning emulator instances
For testing purposes, it can also be quite convenient to automatically generate scripts that spawn emulator instances with all desired configuration settings.
An emulator spawn script can be configured by invoking the emulateApp {}
function:
with import <nixpkgs> {};
androidenv.emulateApp {
name = "emulate-MyAndroidApp";
platformVersion = "28";
abiVersion = "x86"; # armeabi-v7a, mips, x86_64
systemImageType = "google_apis_playstore";
}
Additional flags may be applied to the Android SDK's emulator through the runtime environment variable $NIX_ANDROID_EMULATOR_FLAGS
.
It is also possible to specify an APK to deploy inside the emulator and the package and activity names to launch it:
with import <nixpkgs> {};
androidenv.emulateApp {
name = "emulate-MyAndroidApp";
platformVersion = "24";
abiVersion = "armeabi-v7a"; # mips, x86, x86_64
systemImageType = "default";
app = ./MyApp.apk;
package = "MyApp";
activity = "MainActivity";
}
In addition to prebuilt APKs, you can also bind the APK parameter to a
buildApp {}
function invocation shown in the previous example.
Notes on environment variables in Android projects
ANDROID_HOME
should point to the Android SDK. In your Nix expressions, this should be${androidComposition.androidsdk}/libexec/android-sdk
. Note thatANDROID_SDK_ROOT
is deprecated, but if you rely on tools that need it, you can export it too.ANDROID_NDK_ROOT
should point to the Android NDK, if you're doing NDK development. In your Nix expressions, this should be${ANDROID_HOME}/ndk-bundle
.
If you are running the Android Gradle plugin, you need to export GRADLE_OPTS to override aapt2 to point to the aapt2 binary in the Nix store as well, or use a FHS environment so the packaged aapt2 can run. If you don't want to use a FHS environment, something like this should work:
let
buildToolsVersion = "30.0.3";
# Use buildToolsVersion when you define androidComposition
androidComposition = <...>;
in
pkgs.mkShell rec {
ANDROID_HOME = "${androidComposition.androidsdk}/libexec/android-sdk";
ANDROID_NDK_ROOT = "${ANDROID_HOME}/ndk-bundle";
# Use the same buildToolsVersion here
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_HOME}/build-tools/${buildToolsVersion}/aapt2";
}
If you are using cmake, you need to add it to PATH in a shell hook or FHS env profile. The path is suffixed with a build number, but properly prefixed with the version. So, something like this should suffice:
let
cmakeVersion = "3.10.2";
# Use cmakeVersion when you define androidComposition
androidComposition = <...>;
in
pkgs.mkShell rec {
ANDROID_HOME = "${androidComposition.androidsdk}/libexec/android-sdk";
ANDROID_NDK_ROOT = "${ANDROID_HOME}/ndk-bundle";
# Use the same cmakeVersion here
shellHook = ''
export PATH="$(echo "$ANDROID_HOME/cmake/${cmakeVersion}".*/bin):$PATH"
'';
}
Note that running Android Studio with ANDROID_HOME set will automatically write a
local.properties
file with sdk.dir
set to $ANDROID_HOME if one does not already
exist. If you are using the NDK as well, you may have to add ndk.dir
to this file.
An example shell.nix that does all this for you is provided in examples/shell.nix. This shell.nix includes a shell hook that overwrites local.properties with the correct sdk.dir and ndk.dir values. This will ensure that the SDK and NDK directories will both be correct when you run Android Studio inside nix-shell.
Notes on improving build.gradle compatibility
Ensure that your buildToolsVersion and ndkVersion match what is declared in androidenv. If you are using cmake, make sure its declared version is correct too.
Otherwise, you may get cryptic errors from aapt2 and the Android Gradle plugin warning that it cannot install the build tools because the SDK directory is not writeable.
android {
buildToolsVersion "30.0.3"
ndkVersion = "22.0.7026061"
externalNativeBuild {
cmake {
version "3.10.2"
}
}
}
Querying the available versions of each plugin
repo.json provides all the options in one file now.
A shell script in the pkgs/development/mobile/androidenv/
subdirectory can be used to retrieve all
possible options:
./querypackages.sh packages
The above command-line instruction queries all package versions in repo.json.
Updating the generated expressions
repo.json is generated from XML files that the Android Studio package manager uses.
To update the expressions run the generate.sh
script that is stored in the
pkgs/development/mobile/androidenv/
subdirectory:
./generate.sh
Building an Android application with Ant
In addition to the SDK, it is also possible to build an Ant-based Android project and automatically deploy all the Android plugins that a project requires. Most newer Android projects use Gradle, and this is included for historical purposes.
with import <nixpkgs> {};
androidenv.buildApp {
name = "MyAndroidApp";
src = ./myappsources;
release = true;
# If release is set to true, you need to specify the following parameters
keyStore = ./keystore;
keyAlias = "myfirstapp";
keyStorePassword = "mykeystore";
keyAliasPassword = "myfirstapp";
# Any Android SDK parameters that install all the relevant plugins that a
# build requires
platformVersions = [ "24" ];
# When we include the NDK, then ndk-build is invoked before Ant gets invoked
includeNDK = true;
}
Aside from the app-specific build parameters (name
, src
, release
and
keystore parameters), the buildApp {}
function supports all the function
parameters that the SDK composition function (the function shown in the
previous section) supports.
This build function is particularly useful when it is desired to use Hydra: the Nix-based continuous integration solution to build Android apps. An Android APK gets exposed as a build product and can be installed on any Android device with a web browser by navigating to the build result page.