Development
Robotnix modules use the same Nix-based module system used in NixOS. To understand the NixOS module system, please read this.
Robotnix does not primarily aim to significantly decrease the complexity of Android development, but rather (once a developer has a working build) makes it easier to share that work with others.
As such, robotnix does not replace the existing Android build system, but provides a convenient Nix-based wrapper around the build system. (See blueprint2nix and soongnix for an experimental attempt at reimplementing part of the Android build system natively in Nix.)
Feel free to ask robotnix development questions in #robotnix:nixos.org
on Matrix.
Git mirrors
Robotnix can be configured to use local git mirrors of Android source code. The AOSP documentation includes instructions to create a local mirror of the Android source code. Maintaining a local mirror can save bandwidth in the long-run when repeatedly updating a flavor over time which contains incremental updates.
This functionality is enabled by setting the ROBOTNIX_GIT_MIRRORS
environment variable.
The value of ROBOTNIX_GIT_MIRRORS
contains a number of mappings, each separated by a |
character.
Each mapping is of the format <remote_url>=<local_url>
.
For example:
ROBOTNIX_GIT_MIRRORS=https://android.googlesource.com=/mnt/cache/mirror|https://github.com/LineageOS=/mnt/cache/lineageos/LineageOS
Both the robotnix update scripts as well as robotnix's overridden fetchgit
derivation use ROBOTNIX_GIT_MIRRORS
.
This environment variable is passed to fetchgit
via impureEnvVars
(search for impureEnvVars
in the Nix manual).
If the Nix daemon is being used, it needs to have this ROBOTNIX_GIT_MIRRORS
in its environment, not just in the user's environment when running nix-build
or nix build
.
The following NixOS configuration can be used to easily set this environment variable for the Nix daemon:
let
mirrors = {
"https://android.googlesource.com" = "/mnt/cache/mirror";
"https://github.com/LineageOS" = "/mnt/cache/lineageos/LineageOS";
};
in
{
nix.envVars.ROBOTNIX_GIT_MIRRORS = lib.concatStringsSep "|" (lib.mapAttrsToList (local: remote: "${local}=${remote}") mirrors);
# Also add local mirrors to nix sandbox exceptions
nix.sandboxPaths = lib.attrValues mirrors;
}
Helper scripts
Robotnix can produce a few helper scripts that can make Android development easier in some circumstances.
Running nix-build --arg configuration <cfg> -A <output>
for the outputs below will produce the corresponding helper script, using the provided robotnix configuration.
config.build.debugEnterEnv
produces a script which enters an FHS environment with the required dependencies, as well as the Android source files bind-mounted under the current directory. Useful in conjunction withcd $(mktemp -d)
to enter a temporary directory. Files are bind-mounted readonly, so files cannot be edited ad-hoc using this script.
The following outputs can be useful with an existing Android source checkout made using repo
.
config.build.env
produces arobotnix-build
script underbin/
which enters an FHS environment that contains all required dependencies to build Android.config.build.debugUnpackScript
produces a script which will copy the robotnix-specific source directories into./robotnix/
.config.build.debugPatchScript
produces a script which will patch all Android source directories under the current directory in a similar way they would be patched during a normal robotnix build.
External modules
Robotnix is welcome to contributions of well-written modules that can be maintained in an ongoing fashion. Modules can provide support for new flavors, additional devices with an existing flavor, included system/privileged applications, and others.
If the proposed module is not suitable for inclusion as an upstream robotnix module,
it can still be developed and maintained externally and easily included by a user.
This can be done in a similar way as is done with NixOS modules.
For instance, if the module is provided by default.nix
in the owner/repo
repository on GitHub:
{
imports = [ (builtins.fetchTarball {
url = "https://github.com/owner/repo/archive/9b034054166e1f01b3bdb6a1948daa3bdafe039a.tar.gz";
sha256 = "0000000000000000000000000000000000000000000000000000000000000000";
}) ];
}
The above imports
statement will include the provided module in the robotnix build, pinned by the provided revision and sha256
.
Any options or configuration set by the specified module will be included in the build.
Developing a new flavor
To create a new flavor, the developer should create a robotnix module that conditions on config.flavor
.
The flavor configuration defaults should be set conditionally using (for example) mkIf (config.flavor = "...") { ... }
.
Those configuration defaults should include:
- Setting
source.dirs
using a repo JSON file produced bymk_repo_file.py
. - Setting the default
androidVersion
. - Setting the default
buildDateTime
based on (for example) the time that the flavor was last updated. - Providing a warning if the user has not selected a valid device for this flavor.
Additionally, flavors should provide update scripts that can (at least) automatically produce an updated repo JSON file.
It is recommended to take a look at the Nix expressions implementing the current flavors under flavors/
.
Emulator
Robotnix can also build a script which will start the Android emulator using an attached robotnix-built system image.
This can be accomplished with the emulator
Nix output.
To build and run an emulator with an attached vanilla system image, use (for example):
$ nix-build ./default.nix --arg configuration '{device="x86_64"; flavor="vanilla";}' -A emulator
$ ./result
This currently only works well when using the generic x86_64
device.
Testing / CI / Reproducibility
All devices (Pixel 3-5(a) (XL)) have very basic checks to ensure that the android build process will at least start properly.
See release.nix
for the set of configurations with this minimal build testing.
This check is run using nix-build ./release.nix -A check
.
As each build takes approximately 4 hours--I only build marlin and crosshatch builds for myself.
At some point, I would love to set up a build farm and publish build products on s3 or cachix.
This would allow an end-user to simply sign releases using their own keys without building the entire AOSP themselves.
As of 2020-05-17, target_files
, signed_target_files
, img
, and ota
files have all been verified to be bit-for-bit reproducible for crosshatch
and marlin
using the vanilla
flavor.
Automated periodic testing of this is still desired.
One option being investigated is to have multiple independent remote builders produce unsigned target files for a number of device and flavor combinations.
An end-user could then verify that the builders produced the same unsigned target files, and finish the process by signing the target files and producing their own img
and ota
files.
This eliminates the requirement for an end-user to spend hours building android.
There are, however, a few places where user-specific public keys are included in the build for key pinning. This unfortunately decreases the possibility of sharing build products between users. The F-Droid privileged extension and Trichrome (disabled for now) are two components which have this issue. Fixes for this are still under investigation.
Building individual android components
For convenience, individual android components may be easily built using the components.nix
at the root of the repo.
For example, to build adb
, run
$ nix-build ./components.nix -A adb
The set of components available may be found in components.json
.
Only what Android considers the "installed output" of the components is copied into the resuting derivation, and not "intermediate" results.
Sometimes these intermediate results are what is desired, in which case the user should manually set the installPhase
for a mkAndroid
invokation.
For more detailed information about what exactly these components produce as "installed output", see the config.build.moduleInfo
output for a build.
Additional Notes
Robotnix bind mounts the source directories from /nix/store
.
These files/directories have their "user write" (u-w
) permission removed.
Sometimes, Android Makefiles which copy files from the source directories may assume the files have the write permission enabled, which can then break later steps.
To work around these issues, it is usually sufficient to add a chmod
command or add --no-preserve=owner,mode
to the cp
command in the Makefile.