Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f61790b
feat(android): Move flake.nix to devbox.d for version control
abueide Apr 20, 2026
b7468ce
feat(android): Add android.lock and unified sync command
abueide Apr 20, 2026
e591c81
feat: Add automatic doctor checks on shell init
abueide Apr 20, 2026
d7f597e
refactor: Move doctor init checks to init layer
abueide Apr 20, 2026
0db92e9
fix(android): address PR review feedback - security, deduplication, a…
abueide Apr 20, 2026
036cbb1
fix(react-native): disable Android NDK and CMake by default
abueide Apr 20, 2026
18a2f95
feat(android): detect and explain nixpkgs hash mismatch errors
abueide Apr 20, 2026
b609e63
feat(android): add automatic hash mismatch detection and fix
abueide Apr 20, 2026
56c1f97
feat(android): make hash mismatch fix fully automatic
abueide Apr 20, 2026
69308d0
fix(android): preserve reproducibility by committing hash overrides
abueide Apr 20, 2026
7ac0871
fix: address code review issues in Android plugin scripts
abueide Apr 20, 2026
07c92bb
fix(react-native): re-enable NDK to fix E2E build failures
abueide Apr 20, 2026
4834133
fix(android): use RETURN trap for function-scope cleanup
abueide Apr 20, 2026
a1f92b6
fix(android): update min device from API 21 to API 24
abueide Apr 20, 2026
b7ffde6
fix(android): implement hash override mechanism in flake
abueide Apr 21, 2026
89ba3fc
fix(android): fix case typo in includeCMake check
abueide Apr 21, 2026
3c88b92
fix(android): respect ANDROID_DEVICES filter in device sync
abueide Apr 21, 2026
133bdc6
fix: filter devices in AVD setup and fix iOS log noise
abueide Apr 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ examples/*/reports/
examples/*/test-results/
plugins/*/tests/reports/
plugins/*/tests/test-results/
notes/
11 changes: 6 additions & 5 deletions examples/ios/tests/test-suite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ processes:
if ! ios.sh app status; then
echo "FAIL: App is not running"
if [ -n "$UDID" ]; then
xcrun simctl spawn "$UDID" log show --predicate 'eventMessage contains "crash" OR eventMessage contains "fatal" OR eventMessage contains "terminate"' --last 5m --style compact > "$_logs_dir/simulator-crash.log" 2>&1 || true
xcrun simctl spawn "$UDID" log show --predicate 'process == "ios" AND (eventMessage contains "crash" OR eventMessage contains "fatal" OR eventMessage contains "terminate")' --last 5m --style compact > "$_logs_dir/simulator-crash.log" 2>&1 || true
echo "Simulator crash logs: $_logs_dir/simulator-crash.log"
fi
printf 'fail\nApp not found in running processes\n' > "$_step_dir/$_step.status"
Expand All @@ -190,7 +190,7 @@ processes:
if ! ios.sh app status; then
echo "FAIL: App crashed during soak period"
if [ -n "$UDID" ]; then
xcrun simctl spawn "$UDID" log show --predicate 'eventMessage contains "crash" OR eventMessage contains "fatal" OR eventMessage contains "terminate"' --last 5m --style compact > "$_logs_dir/simulator-crash.log" 2>&1 || true
xcrun simctl spawn "$UDID" log show --predicate 'process == "ios" AND (eventMessage contains "crash" OR eventMessage contains "fatal" OR eventMessage contains "terminate")' --last 5m --style compact > "$_logs_dir/simulator-crash.log" 2>&1 || true
echo "Simulator crash logs: $_logs_dir/simulator-crash.log"
fi
printf 'fail\nApp crashed during soak period\n' > "$_step_dir/$_step.status"
Expand All @@ -199,9 +199,10 @@ processes:

# Capture simulator logs for analysis
if [ -n "$UDID" ]; then
xcrun simctl spawn "$UDID" log show --last 2m --style compact > "$_logs_dir/simulator-app.log" 2>&1 || true
# Filter logs to only our test app process to avoid false positives from system services
xcrun simctl spawn "$UDID" log show --last 2m --predicate 'process == "ios"' --style compact > "$_logs_dir/simulator-app.log" 2>&1 || true

# Scan for native crash patterns
# Scan for native crash patterns (only in our app's logs)
error_patterns="Terminating app|SIGABRT|EXC_BAD_ACCESS|EXC_CRASH|Assertion failure"
if grep -qiE "$error_patterns" "$_logs_dir/simulator-app.log" 2>/dev/null; then
echo ""
Expand Down Expand Up @@ -233,7 +234,7 @@ processes:
# Capture final simulator logs before teardown
UDID=$(cat "${IOS_RUNTIME_DIR:-/tmp}/ios-e2e/simulator-udid.txt" 2>/dev/null || cat "${IOS_RUNTIME_DIR:-/tmp}/${SUITE_NAME:-ios-e2e}/simulator-udid.txt" 2>/dev/null || true)
if [ -n "$UDID" ]; then
xcrun simctl spawn "$UDID" log show --last 5m --style compact > "$_logs_dir/simulator-final.log" 2>&1 || true
xcrun simctl spawn "$UDID" log show --last 5m --predicate 'process == "ios"' --style compact > "$_logs_dir/simulator-final.log" 2>&1 || true
echo "Final simulator logs: $_logs_dir/simulator-final.log"
fi

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"devices": [
{
"name": "medium_phone_api36",
"api": 36,
"device": "medium_phone",
"tag": "google_apis"
},
{
"name": "pixel_api21",
"api": 21,
"device": "pixel",
"tag": "google_apis"
}
],
"checksum": "8df4d3393b61fbbb08e45cf8762f95c521316938e514527916e4fce88a849d57"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "medium_phone_api36",
"api": 36,
"device": "medium_phone",
"tag": "google_apis"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "pixel_api21",
"api": 21,
"device": "pixel",
"tag": "google_apis"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"devices": [
{
"name": "iPhone 17",
"runtime": "26.2"
},
{
"name": "iPhone 13",
"runtime": "15.4"
}
],
"checksum": "4d5276f203d7ad62860bfc067f76194df53be449d4aa8a3b2d069855ec1f3232"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "iPhone 17",
"runtime": "26.2"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "iPhone 13",
"runtime": "15.4"
}
147 changes: 131 additions & 16 deletions plugins/android/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,39 @@
# Android Devbox Plugin

This plugin pins Android user data (AVDs, emulator configs, adb keys) to the project virtenv so
shells are pure and do not touch global `~/.android` state.
This plugin provides reproducible Android development environments by:
- Pinning Android user data (AVDs, emulator configs, adb keys) to the project virtenv
- Managing Android SDK versions through Nix
- Version controlling Android configuration via lock files

Runtime scripts live in the virtenv (`.devbox/virtenv/android/scripts`) and are added to PATH when
the plugin activates.
## Architecture: Env Vars → Lock Files → Reproducible Builds

Configuration is managed via environment variables in `plugin.json`. The plugin automatically generates
a JSON file in the virtenv for Nix flake evaluation. Set env vars to configure SDK versions, default
device selection, or enable `ANDROID_LOCAL_SDK`.
The plugin uses a **two-stage configuration model**:

The Android SDK flake lives under `devbox.d/android/` and exposes `android-sdk*` outputs.
1. **Configuration (env vars in `devbox.json`)** - Easy to edit, defines desired state
2. **Lock files (in `devbox.d/`)** - Committed to git, ensures team-wide reproducibility

### Configuration Files

```
devbox.d/segment-integrations.mobile-devtools.android/
├── flake.nix # Nix template (from plugin, committed)
├── flake.lock # Pins nixpkgs version (committed)
├── android.lock # Pins Android SDK config (committed)
└── devices/
├── devices.lock # Pins device definitions (committed)
├── min.json # Device configs (committed)
└── max.json
```

**Why lock files?**
- `flake.lock` → Ensures everyone uses the same nixpkgs (same Android package versions)
- `android.lock` → Makes Android SDK changes reviewable in PRs
- `devices.lock` → Pins which devices/APIs are used for testing

**Why not just env vars?**
- Env vars are easy to change but invisible in diffs
- Lock files make configuration changes explicit and reviewable
- Prevents "works on my machine" when team members have different configs

## Quickstart

Expand Down Expand Up @@ -72,11 +95,104 @@ Set in your `devbox.json`:
}
```

Then regenerate the device lock file:
Then sync the configuration:
```bash
devbox run android.sh devices eval
devbox run android:sync
```

## How to Update Android SDK Versions

The Android SDK configuration uses a **two-stage model**: env vars → lock files.

### Step 1: Edit Environment Variables

Change Android SDK settings in your `devbox.json`:

```json
{
"env": {
"ANDROID_BUILD_TOOLS_VERSION": "36.1.0",
"ANDROID_COMPILE_SDK": "35",
"ANDROID_TARGET_SDK": "35",
"ANDROID_SYSTEM_IMAGE_TAG": "google_apis"
}
}
```

At this point, **the changes are NOT applied yet**. The old `android.lock` is still in effect.

### Step 2: Sync Configuration

Run the sync command to generate lock files:

```sh
devbox run android:sync
```

This command:
1. Generates `android.lock` from your env vars (pins Android SDK config)
2. Regenerates `devices.lock` from device JSON files (pins device APIs)
3. Syncs AVDs to match device definitions

### Step 3: Review and Commit

```sh
git diff devbox.d/ # Review what changed in lock files
git add devbox.json devbox.d/
git commit -m "chore: update Android SDK to API 35"
```

### Why This Two-Stage Model?

**Reproducibility**: Lock files ensure everyone on the team uses identical Android SDK versions, even if plugin versions differ.

**Reviewability**: Android SDK changes are visible in PRs. Reviewers can see:
- Which SDK versions changed
- Which device APIs were added/removed
- Whether nixpkgs was updated

**Explicit Updates**: Changing env vars doesn't immediately affect builds. You must explicitly sync, preventing accidental misconfigurations.

### Drift Detection

If env vars don't match the lock file, you'll see a warning on `devbox shell`:

```
⚠️ WARNING: Android configuration has changed but lock file is outdated.

Environment variables don't match android.lock:
ANDROID_BUILD_TOOLS_VERSION: "36.1.0" (env) vs "35.0.0" (lock)

To apply changes:
devbox run android:sync

To revert changes:
Edit devbox.json to match the lock file
```

This prevents deploying with mismatched configurations.

## Updating nixpkgs

The `flake.lock` pins which version of nixpkgs provides Android packages. Update it separately from Android SDK versions:

```sh
cd devbox.d/segment-integrations.mobile-devtools.android/
nix flake update
```

This updates nixpkgs to the latest, which may provide:
- Newer Android SDK package versions
- Bug fixes in Nix Android packaging
- Security updates

**When to update nixpkgs:**
- Android SDK packages fail to build
- You need a newer package version not available in current nixpkgs
- Regular maintenance (e.g., monthly)

**Don't conflate**: Updating Android SDK config (env vars) vs updating nixpkgs (flake.lock) are separate concerns.

### Troubleshooting SDK Version Mismatches

If your `android/build.gradle` has hardcoded SDK versions that don't match the plugin, you'll see build failures like:
Expand Down Expand Up @@ -124,9 +240,7 @@ The flake evaluates all device APIs by default. To restrict it, set `ANDROID_DEV
```json
{"env": {"ANDROID_DEVICES": "max"}}
```
Use `devbox run android.sh devices select max` to update this value.

**Note:** The Android flake lock is automatically updated when device definitions change, ensuring system images stay in sync.
Use `devbox run android.sh devices select max` to update this value, then run `devbox run android:sync` to apply.

## Commands

Expand All @@ -140,13 +254,14 @@ devbox run reset-emu-device max # Reset a specific device

Device management:
```sh
devbox run android:sync # Sync all config (android.lock + devices.lock + AVDs)
devbox run android.sh devices list
devbox run android.sh devices create pixel_api28 --api 28 --device pixel --tag google_apis
devbox run android.sh devices update pixel_api28 --api 29
devbox run android.sh devices delete pixel_api28
devbox run android.sh devices select max min # Select specific devices
devbox run android.sh devices reset # Reset to all devices
devbox run android.sh devices eval # Generate devices.lock
devbox run android.sh devices select max min # Select specific devices (then run android:sync)
devbox run android.sh devices reset # Reset to all devices (then run android:sync)
devbox run android.sh devices eval # Generate devices.lock only (use android:sync instead)
```

Build commands:
Expand Down
73 changes: 73 additions & 0 deletions plugins/android/config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Android Plugin Configuration

This directory contains configuration files for the Android Devbox plugin.

## Files

### `devices/*.json`
Device definitions for Android emulators. These define the AVD configurations that will be created.

**Location:** `devbox.d/plugin-name/devices/`
**Committed:** ✅ Yes
**Purpose:** Define emulator configurations for the team

Example:
```json
{
"name": "max",
"api": 36,
"abi": "arm64-v8a",
"tag": "google_apis"
}
```

### `hash-overrides.json` (Optional)
Temporary workarounds for Android SDK hash mismatches caused by Google updating files on their servers.

**Location:** `devbox.d/plugin-name/hash-overrides.json`
**Committed:** ✅ Yes (for reproducibility)
**Purpose:** Fix hash mismatches until nixpkgs is updated
**Auto-generated:** By `devbox run android:hash-fix`

Example:
```json
{
"dl.google.com-android-repository-platform-tools_r37.0.0-darwin.zip": "8c4c926d0ca192376b2a04b0318484724319e67c"
}
```

**When to commit:**
- ✅ **Always commit** when auto-generated - ensures everyone on the team gets the fix
- 🗑️ **Remove when obsolete** - once nixpkgs is updated, the override is no longer needed
- ✓ **Safe to keep** - having stale overrides is harmless (they're just not used if nixpkgs already has the correct hash)

**Why commit it:**
- **Reproducibility**: Everyone on the team uses the same fixed hash
- **CI/CD**: Automated builds get the fix automatically
- **Onboarding**: New team members don't hit the same error

This prevents the scenario where one developer fixes a hash mismatch but others keep hitting the same error.

## Hash Mismatch Issue

This is a **recurring problem** with Android SDK where Google updates files at stable URLs without changing version numbers, breaking Nix's content-addressable builds.

**Symptoms:**
```
error: hash mismatch in fixed-output derivation
specified: sha1-XXXXXXX
got: sha1-YYYYYYY
```

**Automatic fix:**
The plugin automatically detects and fixes this during `devbox shell`. Just run `devbox shell` twice:
1. First run: Detects error + auto-fixes + saves to hash-overrides.json
2. Second run: Uses fixed hash + builds successfully

**Then commit the file:**
```bash
git add devbox.d/*/hash-overrides.json
git commit -m "fix(android): add SDK hash override"
```

See: [HASH_MISMATCH_ISSUE.md](../../../notes/HASH_MISMATCH_ISSUE.md) for full details.
4 changes: 2 additions & 2 deletions plugins/android/config/devices/min.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pixel_api21",
"api": 21,
"name": "pixel_api24",
"api": 24,
"device": "pixel",
"tag": "google_apis"
}
3 changes: 3 additions & 0 deletions plugins/android/config/hash-overrides.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"https://dl.google.com/android/repository/platform-tools_r37.0.0-darwin.zip": "094a1395683c509fd4d48667da0d8b5ef4d42b2abfcd29f2e8149e2f989357c7"
}
3 changes: 3 additions & 0 deletions plugins/android/config/hash-overrides.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dl.google.com-android-repository-platform-tools_r37.0.0-darwin.zip": "8c4c926d0ca192376b2a04b0318484724319e67c"
}
Loading
Loading