From 7eda9b1295c8c063577acc18358f58440e54f480 Mon Sep 17 00:00:00 2001 From: EthanShoeDev <13422990+EthanShoeDev@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:14:52 -0400 Subject: [PATCH] Add flake --- CONTRIBUTING.md | 229 +++++++++++++++++++++++++++++++++++++++++++ apps/mobile/justfile | 34 +++++++ flake.lock | 104 ++++++++++++++++++++ flake.nix | 134 +++++++++++++++++++++++++ 4 files changed, 501 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 apps/mobile/justfile create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..8bf9a31 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,229 @@ +# Development with Nix (Android/Expo) + +This repo ships a Nix **flake** that provides reproducible dev shells for React +Native + Expo and Android workflows. You don’t need global installs of +Node/Watchman/Android SDK—the shell provides everything. + +## Prereqs + +- Nix with flakes enabled (`nix --version` should work) +- (Optional, recommended) [`direnv`](https://direnv.net/) + + [`nix-direnv`](https://github.com/nix-community/nix-direnv) to auto-enter + shells + +## Shell variants + +We publish three dev shells: + +- **`default`** – minimal JS toolchain you always want (Node, pnpm, watchman, + git, jq, just) +- **`android-local`** – adds a full **Android SDK** + **Emulator** + **API 36 + Google Play x86_64** system image Good when you run the emulator **on your + machine**. +- **`android-remote`** – no emulator/image; adds **adb** + **scrcpy** Good when + you run an emulator **on a remote server** and mirror/control it locally. + +Pick one per your setup. + +## Quick start + +### A) One-off use (no direnv) + +```bash +# Minimal JS shell +nix develop .#default + +# Local emulator workflow (SDK + emulator + API 36 image) +nix develop .#android-local + +# Remote emulator workflow (adb + scrcpy only) +nix develop .#android-remote +``` + +### B) Auto-enter with direnv (recommended) + +Create `.envrc` at the project root: + +```bash +# choose one: +use flake .#android-local +# use flake .#android-remote +# use flake .#default +``` + +Then: + +```bash +direnv allow +``` + +Any new shell in this folder will enter the selected dev shell automatically. + +## What the shell sets up + +- **Node/PNPM/Watchman/Git/JQ/Just** (all shells) +- **ANDROID_SDK_ROOT / ANDROID_HOME** (in `android-local`; points to the + immutable SDK built by Nix) +- **adb / emulator / sdkmanager / avdmanager** (in `android-local`) +- **adb / scrcpy** (in `android-remote`) + +> Tip: we keep the Android SDK fully **immutable** (declarative). You don’t +> “install packages” via Android Studio; the flake lists exactly which +> components are present. + +## Local emulator workflow (`android-local`) + +1. Enter the shell: + +```bash +nix develop .#android-local +``` + +2. (First time) Create an AVD for API 36 (Google Play, x86_64): + +```bash +avdmanager create avd -n a36-play-x86_64 \ + -k "system-images;android-36;google_apis_playstore;x86_64" +``` + +3. Run the emulator: + +```bash +# GUI window (desktop) +emulator @a36-play-x86_64 + +# Headless (CI/servers): +emulator @a36-play-x86_64 -no-window -no-audio +# If no KVM: add -gpu swiftshader_indirect +``` + +4. Verify `adb` sees it: + +```bash +adb devices +``` + +5. Run your typical Expo/RN commands (Metro, build, etc.) inside the shell. + +> **macOS users**: You can still build Android in this shell. The +> `android-local` shell provides `platform-tools` + SDK commands; the GUI +> Android Studio app is optional. If you prefer to use the macOS GUI emulator +> instead of the Nix one, that’s fine—use `default` or `android-remote` and keep +> your local Android Studio install. + +## Remote emulator workflow (`android-remote`) + +Use this when your emulator runs on a remote Linux box (often headless/KVM). + +1. Enter the shell: + +```bash +nix develop .#android-remote +``` + +2. SSH-tunnel the **remote adb server** back to your machine: + +```bash +ssh -N -L 5037:127.0.0.1:5037 user@remote-host +``` + +3. Point `adb` at the forwarded server and verify: + +```bash +adb -H 127.0.0.1 -P 5037 devices +``` + +4. Mirror/control the remote emulator window locally: + +```bash +scrcpy +``` + +That’s it—everything flows through SSH, and you don’t need any extra ports. + +## Common tasks + +- Check versions: + + ```bash + adb version + sdkmanager --version + avdmanager --help + ``` + +- Upgrade/change Android components Edit the system image or + build-tools/platforms listed in `flake.nix` under the `androidSdk36` + definition, then re-enter the shell. + +- Clean emulators/AVDs AVDs live in `~/.android/avd` by default. You can remove + an AVD with: + + ```bash + avdmanager delete avd -n a36-play-x86_64 + ``` + +## Troubleshooting + +- **Emulator is very slow / won’t start** (Linux): Ensure `/dev/kvm` exists and + your user has permission (`kvm` group). Headless servers without KVM can still + run, but add `-gpu swiftshader_indirect` and expect reduced performance. + +- **`adb` doesn’t see the emulator**: Kill any stray local adb server and retry: + + ```bash + adb kill-server + adb start-server + adb devices + ``` + +- **Gradle/Java mismatch**: If your Android Gradle Plugin complains about Java, + pin the JDK you need in the dev shell and set `JAVA_HOME`. (You can add a JDK + to `defaultPkgs` in the flake if your project requires a specific version.) + +- **Expo/Metro can’t find Android SDK**: Confirm `echo $ANDROID_SDK_ROOT` prints + a path in the `android-local` shell. + +## CI usage + +You can build/test in CI with: + +```bash +nix develop --command bash -lc 'pnpm install && pnpm test' +``` + +or pick a specific shell: + +```bash +nix develop .#android-local --command bash -lc 'just android-build' +``` + +--- + +If you want, I can add a tiny `Justfile` with `just avd-create`, `just avd-run`, +and `just adb-tunnel-remote` helpers so the common commands are one-liners. + +## Enable Nix flakes globally + +If you see errors like: + +``` +error: experimental Nix feature 'nix-command' is disabled; add '--extra-experimental-features nix-command' to enable it +``` + +…it means flakes are not enabled in your Nix configuration yet. + +You can enable them permanently with a one-liner: + +```bash +sudo mkdir -p /etc/nix && echo 'experimental-features = nix-command flakes' | sudo tee /etc/nix/nix.conf +``` + +Then restart your shell (or `nix-daemon` on macOS), and the error goes away. + +From now on you can just run: + +```bash +nix develop .#android-local +``` + +without passing any extra flags. diff --git a/apps/mobile/justfile b/apps/mobile/justfile new file mode 100644 index 0000000..22aff75 --- /dev/null +++ b/apps/mobile/justfile @@ -0,0 +1,34 @@ + +avd_name := "ExpoRnA36" +avd_system_image := "system-images;android-36.0-Baklava;google_apis_playstore;x86_64" + + +default: + @just --list + +avd-list: + avdmanager list avd + +avd-ensure: + #! /bin/bash + set -ex + # avdmanager create avd -n {{ avd_name }} -k "{{ avd_system_image }}" + if ! avdmanager list avd | grep -q "Name: {{avd_name}}"; then + echo "Creating AVD {{avd_name}}..." + # yes | + avdmanager create avd -n "{{avd_name}}" \ + -k "{{avd_system_image}}" \ + --abi "x86_64" + # --device "pixel_7" \ + fi + echo "AVD {{avd_name}} created" + +avd-start: + emulator -avd {{ avd_name }} + +avd-start-headless: + emulator -avd {{ avd_name }} -no-window -no-audio + +avd-mirror-remote ssh_target: + ssh -N -L 5037:127.0.0.1:5037 {{ ssh_target }} + scrcpy \ No newline at end of file diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c6050b5 --- /dev/null +++ b/flake.lock @@ -0,0 +1,104 @@ +{ + "nodes": { + "android-nixpkgs": { + "inputs": { + "devshell": "devshell", + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1757621816, + "narHash": "sha256-r1cZQcvqcF7YUsw70Gxb2eGzuHl/hQHs01WNNeWklIc=", + "owner": "tadfisher", + "repo": "android-nixpkgs", + "rev": "feba9bc6e12243ba9ee9bdb236866e1209c55a78", + "type": "github" + }, + "original": { + "owner": "tadfisher", + "repo": "android-nixpkgs", + "type": "github" + } + }, + "devshell": { + "inputs": { + "nixpkgs": [ + "android-nixpkgs", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1741473158, + "narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=", + "owner": "numtide", + "repo": "devshell", + "rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1757487488, + "narHash": "sha256-zwE/e7CuPJUWKdvvTCB7iunV4E/+G0lKfv4kk/5Izdg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ab0f3607a6c7486ea22229b92ed2d355f1482ee0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "android-nixpkgs": "android-nixpkgs", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..1b6cb4d --- /dev/null +++ b/flake.nix @@ -0,0 +1,134 @@ +{ + description = "Expo RN devshells (local emulator / remote AVD)"; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + # Android SDK as packages + android-nixpkgs = { + url = "github:tadfisher/android-nixpkgs"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, android-nixpkgs, ... }: + let + systems = [ "x86_64-linux" "aarch64-darwin" "x86_64-darwin" ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: + f { + pkgs = import nixpkgs { + inherit system; + overlays = [ android-nixpkgs.overlays.default ]; + config.allowUnfree = true; # emulator is unfree + }; + } + ); + in + { + devShells = forAllSystems ({ pkgs }: let + makeAndroidSdk = mode: + let + androidSdk = pkgs.androidSdk (sdk: + if mode == "full" then + (with sdk; [ + cmdline-tools-latest + platform-tools + emulator + build-tools-36-0-0 + platforms-android-36 + system-images-android-36-0-Baklava-google-apis-playstore-x86-64 + ]) + else if mode == "remote" then + (with sdk; [ + cmdline-tools-latest # ← required for a valid SDK + platform-tools # adb/fastboot + ]) + else + throw "makeAndroidSdk: unknown mode '${mode}'. Use \"full\" or \"remote\"." + ); + + # Standard path from nixpkgs' androidSdk wrapper + # https://ryantm.github.io/nixpkgs/languages-frameworks/android/#notes-on-environment-variables-in-android-projects + sdkRoot = "${androidSdk}/libexec/android-sdk"; + in + { + inherit androidSdk sdkRoot; + }; + + fullAndroidSdk = makeAndroidSdk "full"; + remoteAndroidSdk = makeAndroidSdk "remote"; + + defaultPkgs = with pkgs; [ + nodejs_22 + nodePackages.pnpm + git + just + jq + watchman + jdk17 + gradle_8 + ]; + in { + + # Minimal: only universal dev tools you always want + default = pkgs.mkShell { + packages = defaultPkgs; + }; + + # Local emulator: full SDK + AVD bits for API 36 + android-local = pkgs.mkShell { + packages = defaultPkgs ++ [ fullAndroidSdk.androidSdk ]; + shellHook = '' + # Resolve SDK root robustly (libexec first, then share) + _CANDS=( + "${fullAndroidSdk.sdkRoot}" + "${fullAndroidSdk.androidSdk}/libexec/android-sdk" + "${fullAndroidSdk.androidSdk}/share/android-sdk" + ) + for p in "''${_CANDS[@]}"; do + if [ -d "$p" ]; then + export ANDROID_SDK_ROOT="$p" + export ANDROID_HOME="$p" + break + fi + done + + if [ -z "$ANDROID_SDK_ROOT" ]; then + echo "❌ Could not locate ANDROID_SDK_ROOT in Nix store. Check androidSdk composition." + return 1 + fi + + # Ensure Nix adb/emulator/cmdline-tools win over system tools + export PATH="$ANDROID_SDK_ROOT/platform-tools:$ANDROID_SDK_ROOT/emulator:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin:$PATH" + hash -r + + echo "ANDROID_SDK_ROOT=$ANDROID_SDK_ROOT" + which -a adb || true + which -a emulator || true + which -a avdmanager || true + + # quick sanity + adb version || true + emulator -version || true + avdmanager --help >/dev/null || true + ''; + }; + + # Remote AVD workflow: no emulator/image; add scrcpy + adb only + android-remote = pkgs.mkShell { + packages = defaultPkgs ++ [ + remoteAndroidSdk.androidSdk # provides adb/fastboot only + pkgs.scrcpy + ]; + shellHook = '' + export ANDROID_SDK_ROOT="${remoteAndroidSdk.sdkRoot}" + export ANDROID_HOME="${remoteAndroidSdk.sdkRoot}" + export PATH="${remoteAndroidSdk.sdkRoot}/platform-tools:$PATH" + hash -r + echo "Using Nix adb from: $ANDROID_SDK_ROOT" + which -a adb + adb version || true + echo "Tip: ssh -N -L 5037:127.0.0.1:5037 user@remote && scrcpy" + ''; + }; + }); + }; +}