{ description = "NilsMG4 — Android Automotive build environment"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; }; outputs = { self, nixpkgs }: let systems = [ "x86_64-linux" "aarch64-linux" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f (import nixpkgs { inherit system; config = { allowUnfree = true; android_sdk.accept_license = true; }; })); # Must match app/build.gradle (compileSdk 36.1) and EVHardware/lib (compileSdk 36). buildToolsVersion = "36.0.0"; in { devShells = forAllSystems (pkgs: let android = pkgs.androidenv.composeAndroidPackages { platformVersions = [ "36" "36.1" ]; buildToolsVersions = [ buildToolsVersion ]; includeEmulator = false; includeNDK = false; includeSources = false; includeSystemImages = false; }; sdk = "${android.androidsdk}/libexec/android-sdk"; # gradle/gradle-daemon-jvm.properties pins the daemon to JDK 21. jdk = pkgs.jdk21; buildTools = "${sdk}/build-tools/${buildToolsVersion}"; # AGP otherwise downloads an aapt2 binary from Maven, which is dynamically linked # against a generic FHS and does not run on NixOS. Use the SDK's patched one. # Gradle is also pointed at the Nix JDK so the daemon toolchain criteria are met # locally instead of downloading a (non-NixOS) JDK through foojay. gradleOpts = builtins.concatStringsSep " " [ "-Dorg.gradle.project.android.aapt2FromMavenOverride=${buildTools}/aapt2" "-Dorg.gradle.java.installations.paths=${jdk.home}" "-Dorg.gradle.java.installations.auto-download=false" ]; # The MG4 head unit is signed with the public AOSP test platform key, so that is # the key a system-UID (unstable) build must carry. See tools/README.md and OTA.md. aospKeys = "https://raw.githubusercontent.com/aosp-mirror/platform_build/android-9.0.0_r1/target/product/security"; platformCertSha256 = "c8a2e9bccf597c2fb6dc66bee293fc13f2fc47ec77bc6b2b0d52c11f51192ab8"; buildCar = pkgs.writeShellApplication { name = "build-car"; runtimeInputs = [ jdk pkgs.curl pkgs.openssl pkgs.coreutils pkgs.gnugrep pkgs.gawk ]; text = '' root=$(git rev-parse --show-toplevel 2>/dev/null || pwd) cd "$root" if [ ! -f app/build.gradle ]; then echo "build-car: run this from the NilsMG4 checkout" >&2 exit 1 fi export ANDROID_HOME=${sdk} ANDROID_SDK_ROOT=${sdk} JAVA_HOME=${jdk.home} export GRADLE_OPTS="${gradleOpts}" echo "sdk.dir=$ANDROID_HOME" > local.properties # Keys live in tools/, which .gitignore keeps out of the repository. for f in platform.pk8 platform.x509.pem; do if [ ! -f "tools/$f" ]; then echo "Fetching AOSP test key tools/$f" curl -sSfL -o "tools/$f" "${aospKeys}/$f" fi done cert=$(openssl x509 -in tools/platform.x509.pem -outform der | sha256sum | cut -d' ' -f1) if [ "$cert" != "${platformCertSha256}" ]; then echo "build-car: tools/platform.x509.pem has SHA-256 $cert, expected ${platformCertSha256}" >&2 exit 1 fi ./gradlew assembleUnstableDebug "$@" version=$(./gradlew -q printUnstableVersion "$@" | tail -1) in=app/build/outputs/apk/unstable/debug/NilsMG4-unstable-debug.apk mkdir -p build/car aligned=build/car/aligned.apk out="build/car/NilsMG4-unstable-$version.apk" "${buildTools}/zipalign" -f -p 4 "$in" "$aligned" # Options come before the input APK; apksigner rejects the other order. "${buildTools}/apksigner" sign \ --key tools/platform.pk8 --cert tools/platform.x509.pem \ --out "$out" "$aligned" rm -f "$aligned" "$out.idsig" signer=$("${buildTools}/apksigner" verify --print-certs "$out" \ | awk -F': ' '/Signer #1 certificate SHA-256 digest/ { print $2 }') if [ "$signer" != "${platformCertSha256}" ]; then echo "build-car: $out is signed with $signer, expected ${platformCertSha256}" >&2 rm -f "$out" exit 1 fi sha256sum "$out" | cut -d' ' -f1 > "$out.sha256" echo echo "Signed with the AOSP platform key: $out" echo "Install on the head unit:" echo " adb push $out /data/local/tmp/update.apk" echo " adb shell \"pm install -r -f -d -t /data/local/tmp/update.apk\"" ''; }; in { default = pkgs.mkShell { packages = [ jdk android.androidsdk buildCar pkgs.nodejs_22 pkgs.influxdb2-server pkgs.influxdb2-cli ]; ANDROID_HOME = sdk; ANDROID_SDK_ROOT = sdk; JAVA_HOME = jdk.home; GRADLE_OPTS = gradleOpts; shellHook = '' echo "sdk.dir=$ANDROID_HOME" > local.properties echo "NilsMG4 dev shell — JDK $(java -version 2>&1 | head -1 | cut -d'"' -f2), SDK at $ANDROID_HOME" echo " ./gradlew assembleStableDebug # debug APK" echo " ./gradlew assembleUnstableDebug # build that can update itself (OTA)" echo " build-car # unstable build signed for the car" ''; }; }); }; }