Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/
28 changes: 13 additions & 15 deletions examples/android/tests/test-suite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,34 +138,33 @@ processes:

echo "Verifying emulator is ready..."

# Early failure detection: Check if emulator process exists
echo "Checking if emulator process started..."
# Early failure detection: Check if emulator appears in adb devices
echo "Checking if emulator is visible to adb..."
initial_wait=30
elapsed=0
emulator_process_found=false
emulator_detected=false

while [ $elapsed -lt $initial_wait ]; do
if pgrep -f "emulator.*-avd" >/dev/null 2>&1; then
emulator_process_found=true
echo "✓ Emulator process detected"
if adb devices 2>/dev/null | grep -q "emulator-"; then
emulator_detected=true
echo "✓ Emulator detected by adb"
break
fi
sleep 2
elapsed=$((elapsed + 2))
echo " Waiting for emulator process... ${elapsed}s/${initial_wait}s"
done

if [ "$emulator_process_found" = false ]; then
if [ "$emulator_detected" = false ]; then
echo ""
echo "ERROR: Emulator process not found after ${initial_wait}s" >&2
echo "ERROR: Emulator not visible to adb after ${initial_wait}s" >&2
echo ""
echo "This usually means:" >&2
echo " 1. Device filtering removed all devices (check sync-avds logs)" >&2
echo " 2. Emulator startup command failed (check android-emulator logs)" >&2
echo " 3. System images not available for selected device" >&2
echo ""
echo "Check process-compose logs above for error details" >&2
printf 'fail\nEmulator process never started - check filtering and device availability\n' > "$_step_dir/$_step.status"
printf 'fail\nEmulator not detected - check filtering and device availability\n' > "$_step_dir/$_step.status"
exit 1
fi

Expand All @@ -175,19 +174,18 @@ processes:
max_wait=300
elapsed=0
while ! android.sh emulator ready 2>/dev/null; do
# Recheck that emulator process is still running
if ! pgrep -f "emulator.*-avd" >/dev/null 2>&1; then
# Recheck that emulator is still visible to adb (crash detection)
if ! adb devices 2>/dev/null | grep -q "emulator-"; then
echo ""
echo "ERROR: Emulator process terminated unexpectedly" >&2
echo "ERROR: Emulator no longer visible to adb (crashed or terminated)" >&2
printf 'fail\nEmulator process crashed during boot\n' > "$_step_dir/$_step.status"
exit 1
fi

sleep 3
elapsed=$((elapsed + 3))
echo " Waiting for emulator... ${elapsed}s/${max_wait}s"
if [ $elapsed -ge $max_wait ]; then
printf 'fail\nTimed out after %ds waiting for emulator to boot\n' "$max_wait" > "$_step_dir/$_step.status"
printf 'fail\nTimed out after 300s waiting for emulator to boot\n' > "$_step_dir/$_step.status"
exit 1
fi
done
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"
}
84 changes: 59 additions & 25 deletions plugins/android/config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,44 @@ Example:
}
```

### `hash-overrides.json` (Optional)
Temporary workarounds for Android SDK hash mismatches caused by Google updating files on their servers.
### `android.lock`
Locked Android SDK configuration including optional hash overrides for temporary workarounds.

**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`
**Location:** `devbox.d/plugin-name/android.lock`
**Committed:** ✅ Yes
**Purpose:** Lock SDK versions and fix hash mismatches
**Generated by:** `android.sh devices sync`

Example:
```json
{
"dl.google.com-android-repository-platform-tools_r37.0.0-darwin.zip": "8c4c926d0ca192376b2a04b0318484724319e67c"
"ANDROID_BUILD_TOOLS_VERSION": "36.1.0",
"ANDROID_CMDLINE_TOOLS_VERSION": "19.0",
"ANDROID_COMPILE_SDK": 36,
"ANDROID_TARGET_SDK": 36,
"ANDROID_SYSTEM_IMAGE_TAG": "google_apis",
"ANDROID_INCLUDE_NDK": false,
"ANDROID_NDK_VERSION": "27.0.12077973",
"ANDROID_INCLUDE_CMAKE": false,
"ANDROID_CMAKE_VERSION": "3.22.1",
"hash_overrides": {
"https://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
**Hash overrides field (optional):**
- By default, this field is **not present** - uses upstream hashes from nixpkgs
- Only set via `android.sh hash update` when Google updates files without version changes
- Uses **SHA1 hex format** (40 characters) matching nixpkgs Android repo.json
- Temporary workaround until nixpkgs catches up
- Safe to remove once nixpkgs is updated

This prevents the scenario where one developer fixes a hash mismatch but others keep hitting the same error.
**When to commit:**
- ✅ **Always commit** - ensures reproducible builds for the team
- ✅ **After sync** - when SDK versions change
- ✅ **After hash fix** - when adding hash overrides
- 🗑️ **Can clear overrides** - via `android.sh hash clear` when no longer needed

## Hash Mismatch Issue

Expand All @@ -59,15 +71,37 @@ error: hash mismatch in fixed-output derivation
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
**Manual fix:**
When `devbox shell` fails with a hash mismatch:
1. Extract the URL from the error message
2. Download the file and compute its SHA1 hash
3. Add the override: `android.sh hash update <url> <sha1-hex>`
4. Commit the fix: `git add devbox.d/*/android.lock && git commit -m "fix(android): add hash override"`
5. Retry: `devbox shell`

**Then commit the file:**
**Example:**
```bash
git add devbox.d/*/hash-overrides.json
git commit -m "fix(android): add SDK hash override"
# Download file and compute SHA1 hash
curl -O https://dl.google.com/android/repository/platform-tools_r37.0.0-darwin.zip
shasum platform-tools_r37.0.0-darwin.zip # or sha1sum on Linux

# Add override with SHA1 hex (40 characters)
android.sh hash update https://dl.google.com/android/repository/platform-tools_r37.0.0-darwin.zip 8c4c926d0ca192376b2a04b0318484724319e67c

# Commit and retry
git add devbox.d/*/android.lock
git commit -m "fix(android): add hash override for platform-tools"
devbox shell
```

See: [HASH_MISMATCH_ISSUE.md](../../../notes/HASH_MISMATCH_ISSUE.md) for full details.
**View current overrides:**
```bash
android.sh hash show
```

**Remove overrides (when nixpkgs is updated):**
```bash
android.sh hash clear
git add devbox.d/*/android.lock
git commit -m "fix(android): remove hash overrides (nixpkgs updated)"
```
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"
}
42 changes: 34 additions & 8 deletions plugins/android/virtenv/flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
"aarch64-darwin"
];

# Read generated android.json (created from env vars by android-init.sh)
# On first initialization, android.json may not exist yet, so provide defaults
configFileExists = builtins.pathExists ./android.json;
versionData = if configFileExists
then builtins.fromJSON (builtins.readFile ./android.json)
# Read android.lock (generated by android.sh devices sync)
# On first initialization, android.lock may not exist yet, so provide defaults
androidLockExists = builtins.pathExists ./android.lock;
androidLockData = if androidLockExists
then builtins.fromJSON (builtins.readFile ./android.lock)
else {
# Default values for initial flake evaluation before android-init.sh runs
# Default values for initial flake evaluation before sync runs
ANDROID_BUILD_TOOLS_VERSION = "36.1.0";
ANDROID_CMDLINE_TOOLS_VERSION = "19.0";
ANDROID_SYSTEM_IMAGE_TAG = "google_apis";
Expand All @@ -28,7 +28,7 @@
ANDROID_INCLUDE_CMAKE = false;
ANDROID_CMAKE_VERSION = "3.22.1";
};
defaultsData = if builtins.hasAttr "defaults" versionData then versionData.defaults else versionData;
defaultsData = androidLockData;
getVar =
name:
if builtins.hasAttr name defaultsData then toString (builtins.getAttr name defaultsData)
Expand Down Expand Up @@ -71,6 +71,13 @@
cmakeVersion = getVar "ANDROID_CMAKE_VERSION";
};

# Hash overrides for when Google updates files on their servers
# These can be set in android.lock to work around nixpkgs hash mismatches
# By default this field is not set - only set via `android.sh hash update` when upstream is broken
hashOverrides = if builtins.hasAttr "hash_overrides" androidLockData
then androidLockData.hash_overrides
else {};

forAllSystems =
f:
builtins.listToAttrs (
Expand All @@ -94,9 +101,28 @@

abiVersions = if builtins.match "aarch64-.*" system != null then [ "arm64-v8a" ] else [ "x86_64" ];

# Apply hash overrides to nixpkgs if any are specified
# Android packages use SHA1 hashes, not SHA256
# We need to re-import nixpkgs with overlays when overrides are present
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;

androidPkgs =
config:
pkgs.androidenv.composeAndroidPackages {
pkgsWithOverrides.androidenv.composeAndroidPackages {
platformVersions = config.platformVersions;
buildToolsVersions = [ config.buildToolsVersion ];
cmdLineToolsVersion = config.cmdLineToolsVersion;
Expand Down
26 changes: 25 additions & 1 deletion plugins/android/virtenv/scripts/domain/avd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,8 @@ android_setup_avds() {
fi
done

devices_json="$filtered_json"
# Strip trailing newline to prevent empty iterations
devices_json="${filtered_json%$'\n'}"

if [ -z "$devices_json" ]; then
echo "ERROR: No devices match ANDROID_DEVICES filter: ${ANDROID_DEVICES}" >&2
Expand Down Expand Up @@ -422,10 +423,33 @@ android_setup_avds() {
# Get default system image tag
default_image_tag="${ANDROID_SYSTEM_IMAGE_TAG:-google_apis}"

# Count devices to process
device_count=$(echo "$devices_json" | grep -c '{' || echo "0")
echo "Processing $device_count device(s) from lock file"

# Debug: show devices_json content
if android_debug_enabled; then
echo "DEBUG: devices_json content:" >&2
echo "$devices_json" | cat -A >&2
fi

echo "$devices_json" | while IFS= read -r device_json; do
# Skip empty lines (defensive guard)
if [ -z "$device_json" ]; then
if android_debug_enabled; then
echo "DEBUG: Skipping empty device line" >&2
fi
continue
fi

echo ""
echo "Processing device from lock file..."

# Debug: show raw JSON being parsed
if android_debug_enabled; then
echo "DEBUG: Raw device JSON: $device_json" >&2
fi

# Parse device definition from lock file
device_name="$(echo "$device_json" | jq -r '.name // empty')"
api_level="$(echo "$device_json" | jq -r '.api // empty')"
Expand Down
7 changes: 5 additions & 2 deletions plugins/android/virtenv/scripts/platform/core.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,13 @@ resolve_flake_sdk_root() {

root="${ANDROID_SDK_FLAKE_PATH:-}"
if [ -z "$root" ]; then
if [ -n "${ANDROID_RUNTIME_DIR:-}" ] && [ -d "${ANDROID_RUNTIME_DIR}" ]; then
# Flake is in the config directory (devbox.d/) where device configs live
if [ -n "${ANDROID_CONFIG_DIR:-}" ] && [ -d "${ANDROID_CONFIG_DIR}" ]; then
root="${ANDROID_CONFIG_DIR}"
elif [ -n "${ANDROID_RUNTIME_DIR:-}" ] && [ -d "${ANDROID_RUNTIME_DIR}" ]; then
root="${ANDROID_RUNTIME_DIR}"
elif [ -n "${ANDROID_SCRIPTS_DIR:-}" ] && [ -d "${ANDROID_SCRIPTS_DIR}" ]; then
# Flake is in same directory as scripts (virtenv)
# Fallback: flake in same directory as scripts (virtenv) - deprecated
root="$(dirname "${ANDROID_SCRIPTS_DIR}")"
elif [ -n "${DEVBOX_PROJECT_ROOT:-}" ] && [ -d "${DEVBOX_PROJECT_ROOT}/.devbox/virtenv/android" ]; then
root="${DEVBOX_PROJECT_ROOT}/.devbox/virtenv/android"
Expand Down
Loading
Loading