Skip to content

feat(android): Add android.lock and hash override mechanism#23

Merged
abueide merged 13 commits intomainfrom
feat/hash-mismatch-auto-fix
Apr 21, 2026
Merged

feat(android): Add android.lock and hash override mechanism#23
abueide merged 13 commits intomainfrom
feat/hash-mismatch-auto-fix

Conversation

@abueide
Copy link
Copy Markdown
Contributor

@abueide abueide commented Apr 21, 2026

Summary

Implements android.lock for reproducible Android SDK builds with optional hash override support. Consolidates flake relocation (from closed #18) with hash override mechanism.

Size: ~320 lines net (+350/-30 across 8 files)

What's Included

1. android.lock (from closed #18)

  • Moves flake.nix to devbox.d/ for version control
  • Generates android.lock with SDK configuration from env vars
  • Unified android.sh devices sync command (generates android.lock + devices.lock + syncs AVDs)
  • Flake reads from android.lock instead of android.json

2. Hash Override Mechanism

  • Optional hash_overrides field in android.lock (SHA1 hex format)
  • Hash commands: android.sh hash show/update/clear
  • Doctor validation to detect obsolete overrides
  • Auto-detection of hash mismatches with fix instructions

Changes

Flake improvements:

  • Reads android.lock (not android.json)
  • Supports SHA1 hash overrides via fetchurl overlay
  • Located in devbox.d/ for version control

New commands:

  • android.sh devices sync - Generate locks and sync AVDs
  • android.sh hash show - View current hash overrides
  • android.sh hash update <url> <sha1> - Add hash override
  • android.sh hash clear - Remove all overrides

Doctor enhancements:

  • Shows hash override status
  • Validates if overrides are still needed
  • Suggests removal when upstream is fixed

Impact

Before:

  • Hash mismatches cause cryptic errors
  • Flake in ephemeral virtenv (not version controlled)
  • No way to override broken upstream hashes

After:

  • Clear fix instructions with SHA1 hash commands
  • Flake.lock can be committed for reproducibility
  • Hash overrides stored in android.lock (temporary workarounds)

Example android.lock with override:

{
  "ANDROID_BUILD_TOOLS_VERSION": "36.1.0",
  "ANDROID_COMPILE_SDK": 36,
  "hash_overrides": {
    "https://dl.google.com/android/repository/platform-tools_r37.0.0-darwin.zip": "8c4c926d0ca192376b2a04b0318484724319e67c"
  }
}

Related PRs

Part of 6-PR split of #17:

🔗 Related nixpkgs PR: NixOS/nixpkgs#511856

@abueide abueide force-pushed the feat/hash-mismatch-auto-fix branch 2 times, most recently from d906e9c to c07b73e Compare April 21, 2026 18:13
@abueide abueide marked this pull request as ready for review April 21, 2026 18:17
@abueide abueide force-pushed the feat/hash-mismatch-auto-fix branch 4 times, most recently from 7eeccd4 to c8ca69b Compare April 21, 2026 18:48
@abueide abueide changed the title feat(android): Add automatic hash mismatch detection and fix feat(android): Add android.lock and hash override mechanism Apr 21, 2026
abueide and others added 8 commits April 21, 2026 14:07
Automatically detects and fixes nixpkgs hash mismatches when Google updates
files on their servers without changing version numbers.

Changes:
- New hash-fix.sh script for automatic hash mismatch detection
- Hash override mechanism in flake.nix
- Automatic fix on SDK evaluation failure
- hash-overrides.json for preserving reproducibility
- User-friendly error messages with fix instructions

Before: Hash mismatches cause cryptic Nix errors, require manual fixes
After: Automatically detects, fixes, and instructs user to commit overrides

Example:
  ⚠️  Android SDK hash mismatch detected
  Fixing automatically...
  ✅ Hash mismatch fixed!

  1. Run 'devbox shell' again to rebuild with the fix
  2. Commit hash-overrides.json to preserve reproducibility

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace bespoke hash-fix.sh script with integrated hash commands in devices.sh.
Hash overrides now stored in android.lock (not separate file) and managed via:
- android.sh hash show - display current overrides
- android.sh hash update <url> <hash> - add/update override
- android.sh hash clear - remove all overrides

Key changes:
- Hash overrides stored in android.lock (optional field, not set by default)
- Flake reads from android.lock instead of android.json
- android.sh devices sync preserves hash_overrides field
- Removed hash-fix.sh and android:hash-fix script
- Simplified error message in core.sh to suggest manual fix

By default, hash overrides are not set - only use as temporary fix when Google
updates files on their servers but nixpkgs hasn't caught up yet.
- Update config/README.md to reflect android.lock instead of hash-overrides.json
- Add hash override check to doctor.sh showing active overrides
- Fix misleading 'Fixing automatically...' message in core.sh
- Doctor now shows hash override status and provides clear commands

Doctor output example:
  Hash Overrides:
    ⚠ 2 hash override(s) active
      - platform-tools_r37.0.0-darwin.zip
      - build-tools_r36.1.0-darwin.zip
    View: android.sh hash show
    Clear: android.sh hash clear (when nixpkgs is updated)
Split into two jq calls to avoid complex conditional merging:
- One for standard lock file (no hash overrides)
- One for lock file with hash overrides

Fixes: jq compile error 'unexpected )' when running android:sync
Doctor now tests whether hash overrides are still needed
Android packages in nixpkgs use SHA1 hashes (via repo.json), not SHA256.
Updated hash override mechanism to match nixpkgs format:

- Flake overlay now overrides 'sha1' parameter instead of 'sha256'
- Hash commands expect SHA1 hex format (40 chars), e.g., 8c4c926d0ca192376b2a04b0318484724319e67c
- Documentation updated with correct format and examples
- Error messages show how to compute SHA1 with shasum/sha1sum

This matches the official nixpkgs update script format from #511856.

Example hash override in android.lock:
{
  "hash_overrides": {
    "https://dl.google.com/.../platform-tools_r37.0.0-darwin.zip": "8c4c926d0ca192376b2a04b0318484724319e67c"
  }
}
Moves the Android SDK flake.nix from the ephemeral .devbox/virtenv
directory to devbox.d/<plugin-name>/ so the flake and its lock file
can be committed and version controlled per-project.

**Changes:**
- Copy flake.nix to `{{ .DevboxDir }}` instead of `{{ .Virtenv }}`
- Update core.sh to look for flake in ANDROID_CONFIG_DIR first
- Update README to clarify flake.lock location and purpose
- Add react-native example devbox.d configs to demonstrate structure

**Benefits:**
- Projects can version control their flake.lock for reproducible builds
- Each project can have different Android SDK versions/configurations
- Lock file updates via `devbox run devices.sh sync` or `nix flake update`
- Consistent with where device configs live (devbox.d/)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements a two-stage configuration model for reproducible Android builds:
- **Stage 1**: Edit env vars in devbox.json (easy to change)
- **Stage 2**: Run `android:sync` to generate lock files (commit to git)

**Changes:**

1. **android.lock file** - Pins Android SDK configuration
   - Generated from env vars by `android:sync` command
   - Committed to git for team-wide reproducibility
   - Makes SDK changes reviewable in PRs

2. **Unified sync command** - `devbox run android:sync`
   - Generates android.lock from env vars
   - Regenerates devices.lock from device JSONs
   - Syncs AVDs to match device definitions
   - One command to sync all configuration

3. **Drift detection** - Warns on shell init if config is out of sync
   - Compares env vars with android.lock
   - Shows which values don't match
   - Provides clear instructions to fix

4. **Comprehensive documentation**
   - Explains env var → lock file model
   - Step-by-step update guide
   - Separates Android SDK updates from nixpkgs updates
   - Clarifies why reproducibility matters

**Benefits:**
- Reproducible: Lock files ensure identical builds across team
- Reviewable: SDK changes visible in PRs
- Explicit: Must run sync to apply changes (no accidents)
- Detectable: Warns if env vars drift from lock file

**Example workflow:**
```sh
devbox run android:sync
git add devbox.json devbox.d/ && git commit
```

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@abueide abueide force-pushed the feat/hash-mismatch-auto-fix branch from 6c2d461 to 6ccebcf Compare April 21, 2026 19:07
abueide and others added 5 commits April 21, 2026 15:00
**Root Cause:**
Device filtering logic appended newlines after each filtered device,
causing an empty line to be processed when only one device was selected.
This triggered "missing required fields" errors and caused emulator
startup to fail in strict mode.

**Example:**
```bash
ANDROID_DEVICES=max  # Filter to single device
# Result: {"name":"max",...}\n
# Loop processes: 1) valid JSON ✓  2) empty string ✗
```

**Fixes:**
1. Strip trailing newline after device filtering (line 395)
2. Add empty-line guard in processing loop (lines 436-442)
3. Add debug logging to show raw device JSON (lines 447-450)
4. Add device count logging before loop (lines 426-427)

**Testing:**
Added comprehensive unit tests in test-device-filtering.sh:
- Single device filter (the bug case)
- Multiple device filter
- Empty filter (all devices)
- Invalid filter (no matches)
- Device count logging
- Empty line guard behavior

All tests pass ✓

**Impact:**
- Fixes Android E2E CI failures on PR #23
- Fixes React Native Android E2E CI failures
- Improves debugging with device count and raw JSON logging

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
**Problem:**
The hash override mechanism was using `pkgs.appendOverlays` which doesn't
work correctly - it caused this error when hash_overrides were present:
```
error: expected a set but found a function: «lambda extendsWithExclusion @
«github:NixOS/nixpkgs/b86751b»/lib/customisation.nix:864:30»
```

**Root Cause:**
`pkgs.appendOverlays` doesn't properly apply overlays to an existing pkgs
instance. The correct approach is to re-import nixpkgs with overlays when
hash overrides are needed.

**Fix:**
Changed flake.nix to re-import nixpkgs with the fetchurl overlay when
hash_overrides are present in android.lock:

```nix
pkgsWithOverrides = if (builtins.length (builtins.attrNames hashOverrides)) > 0
  then import nixpkgs {
    inherit system;
    config = { allowUnfree = true; android_sdk.accept_license = true; };
    overlays = [(final: prev: {
      fetchurl = args:
        if builtins.isAttrs args && builtins.hasAttr "url" args && builtins.hasAttr args.url hashOverrides
        then prev.fetchurl (args // { sha1 = hashOverrides.${args.url}; })
        else prev.fetchurl args;
    })];
  }
  else pkgs;
```

**Testing:**
✅ Tested locally with hash override for platform-tools_r37.0.0-darwin.zip
✅ SDK built successfully with correct hash
✅ `devbox run doctor` confirms SDK working
✅ `devbox run android:sync` creates AVDs successfully

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The verify-emulator-ready step was using pgrep to detect if the emulator
process started, but pgrep doesn't work reliably across process-compose
boundaries in pure/isolated environments.

This changes the verification to use 'android.sh emulator ready' which
uses adb to check the emulator's boot status via the sys.boot_completed
property. This is:
- POSIX compliant (uses adb, not pgrep)
- Works across process boundaries
- Platform-independent (macOS, Linux, BSD, etc.)
- More reliable (checks actual boot status, not just process existence)

Bug introduced in: c5b0ce7 'fix(tests): Fix test timeouts and add early failure detection'

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace the second pgrep usage (crash detection during boot) with adb-based
detection. This uses 'adb devices | grep emulator-' to check if the emulator
is still visible to adb.

This is:
- POSIX compliant (uses adb + grep, no pgrep)
- Uses native Android tooling (adb)
- Platform-independent (macOS, Linux, BSD)
- More reliable (checks adb connection, not process existence)

Now all emulator verification uses Android-native tools (adb) instead of
generic CLI tools (pgrep), improving portability and reliability.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…ress messages

Changes:
1. Split emulator verification into two stages:
   - Stage 1 (30s): Check if emulator appears in 'adb devices' (early failure detection)
   - Stage 2 (300s): Wait for emulator to be fully booted via 'android.sh emulator ready'

2. Remove broken progress message variables that weren't expanding correctly
   in process-compose (showed 's/s' instead of actual elapsed time)

This fixes CI failures where the emulator was taking longer than 30s to fully
boot. The first stage now just checks if adb can see the emulator (fast),
then the second stage waits for full boot completion.

Fixes #23

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@abueide abueide merged commit 161d6de into main Apr 21, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant