mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 14:22:51 +00:00
Add flake
This commit is contained in:
229
CONTRIBUTING.md
Normal file
229
CONTRIBUTING.md
Normal file
@@ -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.
|
||||||
34
apps/mobile/justfile
Normal file
34
apps/mobile/justfile
Normal file
@@ -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
|
||||||
104
flake.lock
generated
Normal file
104
flake.lock
generated
Normal file
@@ -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
|
||||||
|
}
|
||||||
134
flake.nix
Normal file
134
flake.nix
Normal file
@@ -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"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user