From 2965b52993727fbe5d4f27ebdd596c81d70fbc08 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Tue, 21 Apr 2026 16:16:02 -0500 Subject: [PATCH 1/4] chore: bump plugin versions by 0.1 - android: 0.0.3 -> 0.0.4 - ios: 0.0.3 -> 0.0.4 - react-native: 0.0.4 -> 0.0.5 Version bump to reflect recent improvements: - Device filtering fixes - Hash override mechanism - Improved emulator verification with adb - POSIX-compliant process detection Co-Authored-By: Claude Sonnet 4.5 --- plugins/android/plugin.json | 2 +- plugins/ios/plugin.json | 2 +- plugins/react-native/plugin.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/android/plugin.json b/plugins/android/plugin.json index f6e78d93..bafb6f48 100644 --- a/plugins/android/plugin.json +++ b/plugins/android/plugin.json @@ -1,6 +1,6 @@ { "name": "android", - "version": "0.0.3", + "version": "0.0.4", "description": "Sets Android home/AVD paths inside the Devbox virtenv for reproducible, project-local Android tooling.", "env": { "ANDROID_USER_HOME": "{{ .Virtenv }}/android", diff --git a/plugins/ios/plugin.json b/plugins/ios/plugin.json index 7db7cbaf..6637b5ba 100644 --- a/plugins/ios/plugin.json +++ b/plugins/ios/plugin.json @@ -1,6 +1,6 @@ { "name": "ios", - "version": "0.0.3", + "version": "0.0.4", "description": "Configures iOS tooling inside Devbox and ensures Xcode toolchain usage.", "env": { "IOS_CONFIG_DIR": "{{ .DevboxDir }}", diff --git a/plugins/react-native/plugin.json b/plugins/react-native/plugin.json index 9161eb04..cadae37d 100644 --- a/plugins/react-native/plugin.json +++ b/plugins/react-native/plugin.json @@ -1,6 +1,6 @@ { "name": "react-native", - "version": "0.0.4", + "version": "0.0.5", "description": "Aggregates the Android and iOS Devbox plugins for React Native projects.", "include": [ "github:segment-integrations/mobile-devtools?dir=plugins/android&ref=main", From 5f734d61f1ae32f0cbf4206c153cdcfef21988d6 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Tue, 21 Apr 2026 16:54:37 -0500 Subject: [PATCH 2/4] fix(android): set ANDROID_NDK_ROOT when NDK is included When ANDROID_INCLUDE_NDK is true, export ANDROID_NDK_ROOT pointing to the NDK location within the SDK. This prevents Gradle from trying to download/install the NDK at build time, which fails because the Nix store is read-only. The NDK is already included in the Nix SDK when ANDROID_INCLUDE_NDK=true, but Gradle doesn't know where to find it without ANDROID_NDK_ROOT being set. This fixes React Native builds which require the NDK. Fixes #25 Co-Authored-By: Claude Sonnet 4.5 --- plugins/android/virtenv/scripts/user/setup.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/android/virtenv/scripts/user/setup.sh b/plugins/android/virtenv/scripts/user/setup.sh index 4115d8bc..0994a262 100755 --- a/plugins/android/virtenv/scripts/user/setup.sh +++ b/plugins/android/virtenv/scripts/user/setup.sh @@ -90,6 +90,21 @@ else echo "✅ [OK] Android SDK: ${ANDROID_SDK_ROOT}" fi +# Set ANDROID_NDK_ROOT if NDK is included +if [ "${ANDROID_INCLUDE_NDK:-false}" = "true" ] || [ "${ANDROID_INCLUDE_NDK:-0}" = "1" ]; then + if [ -n "${ANDROID_NDK_VERSION:-}" ]; then + ndk_path="${ANDROID_SDK_ROOT}/ndk/${ANDROID_NDK_VERSION}" + if [ -d "$ndk_path" ]; then + export ANDROID_NDK_ROOT="$ndk_path" + else + # Fallback: check for ndk-bundle (older SDK layout) + if [ -d "${ANDROID_SDK_ROOT}/ndk-bundle" ]; then + export ANDROID_NDK_ROOT="${ANDROID_SDK_ROOT}/ndk-bundle" + fi + fi + fi +fi + # Write SDK root to shared state file (for process-compose sibling processes) if [ -n "${ANDROID_RUNTIME_DIR:-}" ]; then mkdir -p "${ANDROID_RUNTIME_DIR}/.state" From 7f3a558e6f041ca48f23fcfdac30e659127ada45 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Tue, 21 Apr 2026 17:12:42 -0500 Subject: [PATCH 3/4] feat: add plugin URL rewriting system for local testing - Add scripts/dev/rewrite-plugin-urls.sh to convert between github: and path: URLs - Update devbox sync to automatically rewrite URLs to local paths - Add restore-plugin-urls command to restore GitHub format - Update CI workflows (pr-checks.yml, e2e-full.yml) to rewrite URLs before tests - Allows proper plugin format (github: URLs) while testing local changes - Add generated flake.nix/lock and android.lock files to examples This solves the problem where React Native plugin includes android/ios from GitHub, preventing local testing of changes. CI and local sync now rewrite to local paths automatically. Co-Authored-By: Claude Sonnet 4.5 --- .github/workflows/e2e-full.yml | 9 ++ .github/workflows/pr-checks.yml | 9 ++ devbox.json | 8 + devbox.lock | 49 ++++++ .../android/devbox.d/android/android.lock | 14 ++ examples/android/devbox.d/android/flake.lock | 27 ++++ examples/android/devbox.d/android/flake.nix | 147 ++++++++++++++++++ examples/android/devbox.json | 2 +- examples/ios/devbox.json | 2 +- .../devbox.d/android/android.lock | 11 ++ .../devbox.d/android/devices/devices.lock | 6 +- .../react-native/devbox.d/android/flake.lock | 27 ++++ .../react-native/devbox.d/android/flake.nix | 147 ++++++++++++++++++ .../flake.nix | 147 ++++++++++++++++++ examples/react-native/devbox.json | 2 +- scripts/dev/rewrite-plugin-urls.sh | 69 ++++++++ 16 files changed, 671 insertions(+), 5 deletions(-) create mode 100644 examples/android/devbox.d/android/android.lock create mode 100644 examples/android/devbox.d/android/flake.lock create mode 100644 examples/android/devbox.d/android/flake.nix create mode 100644 examples/react-native/devbox.d/android/android.lock create mode 100644 examples/react-native/devbox.d/android/flake.lock create mode 100644 examples/react-native/devbox.d/android/flake.nix create mode 100644 examples/react-native/devbox.d/segment-integrations.mobile-devtools.android/flake.nix create mode 100755 scripts/dev/rewrite-plugin-urls.sh diff --git a/.github/workflows/e2e-full.yml b/.github/workflows/e2e-full.yml index 090df6a6..33e33424 100644 --- a/.github/workflows/e2e-full.yml +++ b/.github/workflows/e2e-full.yml @@ -58,6 +58,9 @@ jobs: with: enable-cache: true + - name: Rewrite plugin URLs to local + run: bash scripts/dev/rewrite-plugin-urls.sh --to-local examples/ + - name: Run Android E2E test working-directory: examples/android env: @@ -131,6 +134,9 @@ jobs: with: enable-cache: true + - name: Rewrite plugin URLs to local + run: bash scripts/dev/rewrite-plugin-urls.sh --to-local examples/ + - name: Run iOS E2E test working-directory: examples/ios env: @@ -245,6 +251,9 @@ jobs: with: enable-cache: true + - name: Rewrite plugin URLs to local + run: bash scripts/dev/rewrite-plugin-urls.sh --to-local examples/ + - name: Run React Native E2E test working-directory: examples/react-native run: | diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 03f88c93..87b2d864 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -74,6 +74,9 @@ jobs: with: enable-cache: true + - name: Rewrite plugin URLs to local + run: bash scripts/dev/rewrite-plugin-urls.sh --to-local examples/ + - name: Run Android E2E test working-directory: examples/android env: @@ -139,6 +142,9 @@ jobs: with: enable-cache: true + - name: Rewrite plugin URLs to local + run: bash scripts/dev/rewrite-plugin-urls.sh --to-local examples/ + - name: Run iOS E2E test working-directory: examples/ios env: @@ -253,6 +259,9 @@ jobs: with: enable-cache: true + - name: Rewrite plugin URLs to local + run: bash scripts/dev/rewrite-plugin-urls.sh --to-local examples/ + - name: Run React Native E2E test working-directory: examples/react-native run: | diff --git a/devbox.json b/devbox.json index 385d8ce0..77ff9260 100644 --- a/devbox.json +++ b/devbox.json @@ -74,6 +74,9 @@ "sync": [ "echo 'Syncing example projects with latest plugins...'", "echo ''", + "echo 'Rewriting plugin URLs to local paths...'", + "bash scripts/dev/rewrite-plugin-urls.sh --to-local examples/", + "echo ''", "echo '1/3 Android...'", "(cd examples/android && rm -rf .devbox devbox.d devbox.lock && devbox install)", "echo ' Generating device lock file...'", @@ -194,6 +197,11 @@ "bump": [ "bash scripts/bump.sh \"${@}\"" ], + "restore-plugin-urls": [ + "echo 'Restoring plugin URLs to GitHub format...'", + "bash scripts/dev/rewrite-plugin-urls.sh --to-github examples/", + "echo '✓ Plugin URLs restored to GitHub format'" + ], "test:fast": [ "echo 'Running fast tests (lint + unit + integration in parallel)...'", "devbox run lint", diff --git a/devbox.lock b/devbox.lock index d566cb93..a04e81ce 100644 --- a/devbox.lock +++ b/devbox.lock @@ -278,6 +278,55 @@ } } }, + "rustup@latest": { + "last_modified": "2026-04-01T01:09:41Z", + "plugin_version": "0.0.1", + "resolved": "github:NixOS/nixpkgs/62e3050a29278c985725a86704faa1e99236b51a#rustup", + "source": "devbox-search", + "version": "1.29.0", + "systems": { + "aarch64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/vf4ymfv40jsq1r2bapps3ll8xxqng794-rustup-1.29.0", + "default": true + } + ], + "store_path": "/nix/store/vf4ymfv40jsq1r2bapps3ll8xxqng794-rustup-1.29.0" + }, + "aarch64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/q335jx3dgagxcj79bgxaxa027bq3vf7a-rustup-1.29.0", + "default": true + } + ], + "store_path": "/nix/store/q335jx3dgagxcj79bgxaxa027bq3vf7a-rustup-1.29.0" + }, + "x86_64-darwin": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/psqiilmbpc0d5c12qpswamkhdgzq5dcv-rustup-1.29.0", + "default": true + } + ], + "store_path": "/nix/store/psqiilmbpc0d5c12qpswamkhdgzq5dcv-rustup-1.29.0" + }, + "x86_64-linux": { + "outputs": [ + { + "name": "out", + "path": "/nix/store/3jmhpvfwqag9jcxi21l2lrv79ha7h4p5-rustup-1.29.0", + "default": true + } + ], + "store_path": "/nix/store/3jmhpvfwqag9jcxi21l2lrv79ha7h4p5-rustup-1.29.0" + } + } + }, "shellcheck@latest": { "last_modified": "2026-01-23T17:20:52Z", "resolved": "github:NixOS/nixpkgs/a1bab9e494f5f4939442a57a58d0449a109593fe#shellcheck", diff --git a/examples/android/devbox.d/android/android.lock b/examples/android/devbox.d/android/android.lock new file mode 100644 index 00000000..b7c23576 --- /dev/null +++ b/examples/android/devbox.d/android/android.lock @@ -0,0 +1,14 @@ +{ + "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" + } +} diff --git a/examples/android/devbox.d/android/flake.lock b/examples/android/devbox.d/android/flake.lock new file mode 100644 index 00000000..4b8e88ed --- /dev/null +++ b/examples/android/devbox.d/android/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1776329215, + "narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b86751bc4085f48661017fa226dee99fab6c651b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/examples/android/devbox.d/android/flake.nix b/examples/android/devbox.d/android/flake.nix new file mode 100644 index 00000000..110b24cf --- /dev/null +++ b/examples/android/devbox.d/android/flake.nix @@ -0,0 +1,147 @@ +{ + description = "Android SDK tools for Devbox (plugin local flake)"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + outputs = + { self, nixpkgs }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + # 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 sync runs + ANDROID_BUILD_TOOLS_VERSION = "36.1.0"; + ANDROID_CMDLINE_TOOLS_VERSION = "19.0"; + 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"; + }; + defaultsData = androidLockData; + getVar = + name: + if builtins.hasAttr name defaultsData then toString (builtins.getAttr name defaultsData) + else builtins.throw "Missing required value in android.json: ${name}"; + + unique = + list: + builtins.foldl' ( + acc: item: if builtins.elem item acc then acc else acc ++ [ item ] + ) [ ] list; + + lockData = + if builtins.pathExists ./devices.lock + then builtins.fromJSON (builtins.readFile ./devices.lock) + else { devices = [ ]; }; + + # Extract API versions from lock file devices array, default to latest if empty + deviceApis = + if builtins.hasAttr "devices" lockData && (builtins.length lockData.devices) > 0 + then map (device: device.api) lockData.devices + else [ 36 ]; # Default to latest stable API + + # Include ANDROID_COMPILE_SDK in platform versions if set (for projects + # that compile against a different API than the emulator/device targets) + compileSdkApis = + if builtins.hasAttr "ANDROID_COMPILE_SDK" defaultsData + then [ (toString defaultsData.ANDROID_COMPILE_SDK) ] + else []; + + androidSdkConfig = { + platformVersions = unique ((map toString deviceApis) ++ compileSdkApis); + buildToolsVersion = getVar "ANDROID_BUILD_TOOLS_VERSION"; + cmdLineToolsVersion = getVar "ANDROID_CMDLINE_TOOLS_VERSION"; + systemImageTypes = [ (getVar "ANDROID_SYSTEM_IMAGE_TAG") ]; + includeNDK = + if builtins.hasAttr "ANDROID_INCLUDE_NDK" defaultsData then defaultsData.ANDROID_INCLUDE_NDK else false; + ndkVersion = getVar "ANDROID_NDK_VERSION"; + includeCMake = + if builtins.hasAttr "ANDROID_INCLUDE_CMAKE" defaultsData then defaultsData.ANDROID_INCLUDE_CMAKE else false; + 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 ( + map (system: { + name = system; + value = f system; + }) systems + ); + in + { + packages = forAllSystems ( + system: + let + pkgs = import nixpkgs { + inherit system; + config = { + allowUnfree = true; + android_sdk.accept_license = true; + }; + }; + + 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: + pkgsWithOverrides.androidenv.composeAndroidPackages { + platformVersions = config.platformVersions; + buildToolsVersions = [ config.buildToolsVersion ]; + cmdLineToolsVersion = config.cmdLineToolsVersion; + includeEmulator = true; + includeSystemImages = true; + includeNDK = config.includeNDK; + ndkVersions = if config.includeNDK && config.ndkVersion != "" then [ config.ndkVersion ] else [ ]; + includeCmake = config.includeCMake; + cmakeVersions = if config.includeCMake && config.cmakeVersion != "" then [ config.cmakeVersion ] else [ ]; + abiVersions = abiVersions; + systemImageTypes = config.systemImageTypes; + }; + in + { + android-sdk = (androidPkgs androidSdkConfig).androidsdk; + default = (androidPkgs androidSdkConfig).androidsdk; + } + ); + + androidSdkConfig = androidSdkConfig; + }; +} diff --git a/examples/android/devbox.json b/examples/android/devbox.json index 3ee3a9b2..6c3efcdd 100644 --- a/examples/android/devbox.json +++ b/examples/android/devbox.json @@ -1,5 +1,5 @@ { - "include": ["path:../../plugins/android/plugin.json"], + "include": ["github:segment-integrations/mobile-devtools?dir=plugins/android&ref=main"], "packages": { "jdk17": "latest", "gradle": "latest" diff --git a/examples/ios/devbox.json b/examples/ios/devbox.json index c6b19027..f0d839a6 100644 --- a/examples/ios/devbox.json +++ b/examples/ios/devbox.json @@ -1,5 +1,5 @@ { - "include": ["path:../../plugins/ios/plugin.json"], + "include": ["github:segment-integrations/mobile-devtools?dir=plugins/ios&ref=main"], "packages": { "process-compose": "latest" }, diff --git a/examples/react-native/devbox.d/android/android.lock b/examples/react-native/devbox.d/android/android.lock new file mode 100644 index 00000000..acd0bea2 --- /dev/null +++ b/examples/react-native/devbox.d/android/android.lock @@ -0,0 +1,11 @@ +{ + "ANDROID_BUILD_TOOLS_VERSION": "35.0.0", + "ANDROID_CMDLINE_TOOLS_VERSION": "19.0", + "ANDROID_COMPILE_SDK": 35, + "ANDROID_TARGET_SDK": 35, + "ANDROID_SYSTEM_IMAGE_TAG": "google_apis", + "ANDROID_INCLUDE_NDK": true, + "ANDROID_NDK_VERSION": "29.0.14206865", + "ANDROID_INCLUDE_CMAKE": true, + "ANDROID_CMAKE_VERSION": "4.1.2" +} diff --git a/examples/react-native/devbox.d/android/devices/devices.lock b/examples/react-native/devbox.d/android/devices/devices.lock index 070a0bd6..62b95ea1 100644 --- a/examples/react-native/devbox.d/android/devices/devices.lock +++ b/examples/react-native/devbox.d/android/devices/devices.lock @@ -4,13 +4,15 @@ "name": "medium_phone_api35", "api": 35, "device": "medium_phone", - "tag": "google_apis" + "tag": "google_apis", + "filename": "max" }, { "name": "pixel_api21", "api": 21, "device": "pixel", - "tag": "google_apis" + "tag": "google_apis", + "filename": "min" } ], "checksum": "f5bfab3fdcbe8a23858954c18b1fa86d28a3316e801523aa6d4aa72ca9cf5ab7" diff --git a/examples/react-native/devbox.d/android/flake.lock b/examples/react-native/devbox.d/android/flake.lock new file mode 100644 index 00000000..4b8e88ed --- /dev/null +++ b/examples/react-native/devbox.d/android/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1776329215, + "narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b86751bc4085f48661017fa226dee99fab6c651b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/examples/react-native/devbox.d/android/flake.nix b/examples/react-native/devbox.d/android/flake.nix new file mode 100644 index 00000000..110b24cf --- /dev/null +++ b/examples/react-native/devbox.d/android/flake.nix @@ -0,0 +1,147 @@ +{ + description = "Android SDK tools for Devbox (plugin local flake)"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + outputs = + { self, nixpkgs }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + # 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 sync runs + ANDROID_BUILD_TOOLS_VERSION = "36.1.0"; + ANDROID_CMDLINE_TOOLS_VERSION = "19.0"; + 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"; + }; + defaultsData = androidLockData; + getVar = + name: + if builtins.hasAttr name defaultsData then toString (builtins.getAttr name defaultsData) + else builtins.throw "Missing required value in android.json: ${name}"; + + unique = + list: + builtins.foldl' ( + acc: item: if builtins.elem item acc then acc else acc ++ [ item ] + ) [ ] list; + + lockData = + if builtins.pathExists ./devices.lock + then builtins.fromJSON (builtins.readFile ./devices.lock) + else { devices = [ ]; }; + + # Extract API versions from lock file devices array, default to latest if empty + deviceApis = + if builtins.hasAttr "devices" lockData && (builtins.length lockData.devices) > 0 + then map (device: device.api) lockData.devices + else [ 36 ]; # Default to latest stable API + + # Include ANDROID_COMPILE_SDK in platform versions if set (for projects + # that compile against a different API than the emulator/device targets) + compileSdkApis = + if builtins.hasAttr "ANDROID_COMPILE_SDK" defaultsData + then [ (toString defaultsData.ANDROID_COMPILE_SDK) ] + else []; + + androidSdkConfig = { + platformVersions = unique ((map toString deviceApis) ++ compileSdkApis); + buildToolsVersion = getVar "ANDROID_BUILD_TOOLS_VERSION"; + cmdLineToolsVersion = getVar "ANDROID_CMDLINE_TOOLS_VERSION"; + systemImageTypes = [ (getVar "ANDROID_SYSTEM_IMAGE_TAG") ]; + includeNDK = + if builtins.hasAttr "ANDROID_INCLUDE_NDK" defaultsData then defaultsData.ANDROID_INCLUDE_NDK else false; + ndkVersion = getVar "ANDROID_NDK_VERSION"; + includeCMake = + if builtins.hasAttr "ANDROID_INCLUDE_CMAKE" defaultsData then defaultsData.ANDROID_INCLUDE_CMAKE else false; + 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 ( + map (system: { + name = system; + value = f system; + }) systems + ); + in + { + packages = forAllSystems ( + system: + let + pkgs = import nixpkgs { + inherit system; + config = { + allowUnfree = true; + android_sdk.accept_license = true; + }; + }; + + 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: + pkgsWithOverrides.androidenv.composeAndroidPackages { + platformVersions = config.platformVersions; + buildToolsVersions = [ config.buildToolsVersion ]; + cmdLineToolsVersion = config.cmdLineToolsVersion; + includeEmulator = true; + includeSystemImages = true; + includeNDK = config.includeNDK; + ndkVersions = if config.includeNDK && config.ndkVersion != "" then [ config.ndkVersion ] else [ ]; + includeCmake = config.includeCMake; + cmakeVersions = if config.includeCMake && config.cmakeVersion != "" then [ config.cmakeVersion ] else [ ]; + abiVersions = abiVersions; + systemImageTypes = config.systemImageTypes; + }; + in + { + android-sdk = (androidPkgs androidSdkConfig).androidsdk; + default = (androidPkgs androidSdkConfig).androidsdk; + } + ); + + androidSdkConfig = androidSdkConfig; + }; +} diff --git a/examples/react-native/devbox.d/segment-integrations.mobile-devtools.android/flake.nix b/examples/react-native/devbox.d/segment-integrations.mobile-devtools.android/flake.nix new file mode 100644 index 00000000..110b24cf --- /dev/null +++ b/examples/react-native/devbox.d/segment-integrations.mobile-devtools.android/flake.nix @@ -0,0 +1,147 @@ +{ + description = "Android SDK tools for Devbox (plugin local flake)"; + + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + outputs = + { self, nixpkgs }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + # 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 sync runs + ANDROID_BUILD_TOOLS_VERSION = "36.1.0"; + ANDROID_CMDLINE_TOOLS_VERSION = "19.0"; + 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"; + }; + defaultsData = androidLockData; + getVar = + name: + if builtins.hasAttr name defaultsData then toString (builtins.getAttr name defaultsData) + else builtins.throw "Missing required value in android.json: ${name}"; + + unique = + list: + builtins.foldl' ( + acc: item: if builtins.elem item acc then acc else acc ++ [ item ] + ) [ ] list; + + lockData = + if builtins.pathExists ./devices.lock + then builtins.fromJSON (builtins.readFile ./devices.lock) + else { devices = [ ]; }; + + # Extract API versions from lock file devices array, default to latest if empty + deviceApis = + if builtins.hasAttr "devices" lockData && (builtins.length lockData.devices) > 0 + then map (device: device.api) lockData.devices + else [ 36 ]; # Default to latest stable API + + # Include ANDROID_COMPILE_SDK in platform versions if set (for projects + # that compile against a different API than the emulator/device targets) + compileSdkApis = + if builtins.hasAttr "ANDROID_COMPILE_SDK" defaultsData + then [ (toString defaultsData.ANDROID_COMPILE_SDK) ] + else []; + + androidSdkConfig = { + platformVersions = unique ((map toString deviceApis) ++ compileSdkApis); + buildToolsVersion = getVar "ANDROID_BUILD_TOOLS_VERSION"; + cmdLineToolsVersion = getVar "ANDROID_CMDLINE_TOOLS_VERSION"; + systemImageTypes = [ (getVar "ANDROID_SYSTEM_IMAGE_TAG") ]; + includeNDK = + if builtins.hasAttr "ANDROID_INCLUDE_NDK" defaultsData then defaultsData.ANDROID_INCLUDE_NDK else false; + ndkVersion = getVar "ANDROID_NDK_VERSION"; + includeCMake = + if builtins.hasAttr "ANDROID_INCLUDE_CMAKE" defaultsData then defaultsData.ANDROID_INCLUDE_CMAKE else false; + 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 ( + map (system: { + name = system; + value = f system; + }) systems + ); + in + { + packages = forAllSystems ( + system: + let + pkgs = import nixpkgs { + inherit system; + config = { + allowUnfree = true; + android_sdk.accept_license = true; + }; + }; + + 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: + pkgsWithOverrides.androidenv.composeAndroidPackages { + platformVersions = config.platformVersions; + buildToolsVersions = [ config.buildToolsVersion ]; + cmdLineToolsVersion = config.cmdLineToolsVersion; + includeEmulator = true; + includeSystemImages = true; + includeNDK = config.includeNDK; + ndkVersions = if config.includeNDK && config.ndkVersion != "" then [ config.ndkVersion ] else [ ]; + includeCmake = config.includeCMake; + cmakeVersions = if config.includeCMake && config.cmakeVersion != "" then [ config.cmakeVersion ] else [ ]; + abiVersions = abiVersions; + systemImageTypes = config.systemImageTypes; + }; + in + { + android-sdk = (androidPkgs androidSdkConfig).androidsdk; + default = (androidPkgs androidSdkConfig).androidsdk; + } + ); + + androidSdkConfig = androidSdkConfig; + }; +} diff --git a/examples/react-native/devbox.json b/examples/react-native/devbox.json index 54d1b252..043cfe87 100644 --- a/examples/react-native/devbox.json +++ b/examples/react-native/devbox.json @@ -1,5 +1,5 @@ { - "include": ["path:../../plugins/react-native/plugin.json"], + "include": ["github:segment-integrations/mobile-devtools?dir=plugins/react-native&ref=main"], "packages": [ "nodejs@20", "watchman@latest", diff --git a/scripts/dev/rewrite-plugin-urls.sh b/scripts/dev/rewrite-plugin-urls.sh new file mode 100755 index 00000000..9b2ca13c --- /dev/null +++ b/scripts/dev/rewrite-plugin-urls.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Rewrite plugin URLs in devbox.json files +# Usage: rewrite-plugin-urls.sh [--to-local|--to-github] [directory] +# +# --to-local: Rewrite github: URLs to path: (for local testing) +# --to-github: Rewrite path: URLs to github: (restore public format) +# directory: Directory to search (default: examples/) + +set -euo pipefail + +mode="${1:---to-local}" +search_dir="${2:-examples}" + +if [ ! -d "$search_dir" ]; then + echo "ERROR: Directory not found: $search_dir" >&2 + exit 1 +fi + +case "$mode" in + --to-local) + echo "Rewriting plugin URLs to local paths in $search_dir..." + # Find all devbox.json files and rewrite github: to path: + find "$search_dir" -name "devbox.json" -type f | while read -r file; do + if grep -q "github:segment-integrations/mobile-devtools" "$file"; then + echo " Rewriting: $file" + + # Backup original + cp "$file" "$file.bak" + + # Rewrite URLs using sed (macOS compatible) + sed -i '' \ + -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/android&ref=main"|"path:../../plugins/android/plugin.json"|g' \ + -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/ios&ref=main"|"path:../../plugins/ios/plugin.json"|g' \ + -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/react-native&ref=main"|"path:../../plugins/react-native/plugin.json"|g' \ + "$file" + fi + done + echo "✓ Rewrote plugin URLs to local paths" + ;; + + --to-github) + echo "Restoring plugin URLs to GitHub format in $search_dir..." + # Find all devbox.json files and restore from backup or rewrite path: to github: + find "$search_dir" -name "devbox.json" -type f | while read -r file; do + if [ -f "$file.bak" ]; then + echo " Restoring from backup: $file" + mv "$file.bak" "$file" + elif grep -q "path:.*plugins/.*/plugin.json" "$file"; then + echo " Rewriting: $file" + + # Rewrite URLs using sed (macOS compatible) + sed -i '' \ + -e 's|"path:../../plugins/android/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/android\&ref=main"|g' \ + -e 's|"path:../../plugins/ios/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/ios\&ref=main"|g' \ + -e 's|"path:../../plugins/react-native/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/react-native\&ref=main"|g' \ + "$file" + fi + done + # Clean up any remaining backups + find "$search_dir" -name "devbox.json.bak" -type f -delete + echo "✓ Restored plugin URLs to GitHub format" + ;; + + *) + echo "ERROR: Unknown mode: $mode" >&2 + echo "Usage: $0 [--to-local|--to-github] [directory]" >&2 + exit 1 + ;; +esac From 60e40b2ac771e0375463f1db8970fc7ee32fbe73 Mon Sep 17 00:00:00 2001 From: Andrea Bueide Date: Tue, 21 Apr 2026 17:23:54 -0500 Subject: [PATCH 4/4] fix: add hash override for platform-tools in React Native example React Native android.lock was missing the hash override for platform-tools_r37.0.0-darwin.zip, causing Nix build failures. Added hash_overrides section with correct SHA1 hash (8c4c926d0ca192376b2a04b0318484724319e67c) to match the android example. Also updated rewrite-plugin-urls.sh to handle the react-native plugin's nested includes (android/ios). Co-Authored-By: Claude Sonnet 4.5 --- .../devbox.d/android/android.lock | 5 +- .../devbox.d/android/devices/devices.lock | 6 +- scripts/dev/rewrite-plugin-urls.sh | 105 ++++++++++++------ 3 files changed, 80 insertions(+), 36 deletions(-) diff --git a/examples/react-native/devbox.d/android/android.lock b/examples/react-native/devbox.d/android/android.lock index acd0bea2..04848db9 100644 --- a/examples/react-native/devbox.d/android/android.lock +++ b/examples/react-native/devbox.d/android/android.lock @@ -7,5 +7,8 @@ "ANDROID_INCLUDE_NDK": true, "ANDROID_NDK_VERSION": "29.0.14206865", "ANDROID_INCLUDE_CMAKE": true, - "ANDROID_CMAKE_VERSION": "4.1.2" + "ANDROID_CMAKE_VERSION": "4.1.2", + "hash_overrides": { + "https://dl.google.com/android/repository/platform-tools_r37.0.0-darwin.zip": "8c4c926d0ca192376b2a04b0318484724319e67c" + } } diff --git a/examples/react-native/devbox.d/android/devices/devices.lock b/examples/react-native/devbox.d/android/devices/devices.lock index 62b95ea1..070a0bd6 100644 --- a/examples/react-native/devbox.d/android/devices/devices.lock +++ b/examples/react-native/devbox.d/android/devices/devices.lock @@ -4,15 +4,13 @@ "name": "medium_phone_api35", "api": 35, "device": "medium_phone", - "tag": "google_apis", - "filename": "max" + "tag": "google_apis" }, { "name": "pixel_api21", "api": 21, "device": "pixel", - "tag": "google_apis", - "filename": "min" + "tag": "google_apis" } ], "checksum": "f5bfab3fdcbe8a23858954c18b1fa86d28a3316e801523aa6d4aa72ca9cf5ab7" diff --git a/scripts/dev/rewrite-plugin-urls.sh b/scripts/dev/rewrite-plugin-urls.sh index 9b2ca13c..49dcc22d 100755 --- a/scripts/dev/rewrite-plugin-urls.sh +++ b/scripts/dev/rewrite-plugin-urls.sh @@ -18,46 +18,89 @@ fi case "$mode" in --to-local) - echo "Rewriting plugin URLs to local paths in $search_dir..." - # Find all devbox.json files and rewrite github: to path: - find "$search_dir" -name "devbox.json" -type f | while read -r file; do - if grep -q "github:segment-integrations/mobile-devtools" "$file"; then - echo " Rewriting: $file" + echo "Rewriting plugin URLs to local paths..." + + # Rewrite examples/ devbox.json files + if [ -d "$search_dir" ]; then + echo " Processing examples in $search_dir..." + find "$search_dir" -name "devbox.json" -type f | while read -r file; do + if grep -q "github:segment-integrations/mobile-devtools" "$file"; then + echo " Rewriting: $file" + + # Backup original + cp "$file" "$file.bak" + + # Rewrite URLs using sed (macOS compatible) + sed -i '' \ + -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/android&ref=main"|"path:../../plugins/android/plugin.json"|g' \ + -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/ios&ref=main"|"path:../../plugins/ios/plugin.json"|g' \ + -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/react-native&ref=main"|"path:../../plugins/react-native/plugin.json"|g' \ + "$file" + fi + done + fi + + # Rewrite plugins/ plugin.json files (react-native includes android/ios) + if [ -f "plugins/react-native/plugin.json" ]; then + echo " Processing react-native plugin..." + if grep -q "github:segment-integrations/mobile-devtools" "plugins/react-native/plugin.json"; then + echo " Rewriting: plugins/react-native/plugin.json" # Backup original - cp "$file" "$file.bak" + cp "plugins/react-native/plugin.json" "plugins/react-native/plugin.json.bak" - # Rewrite URLs using sed (macOS compatible) + # Rewrite URLs to relative paths from react-native plugin sed -i '' \ - -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/android&ref=main"|"path:../../plugins/android/plugin.json"|g' \ - -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/ios&ref=main"|"path:../../plugins/ios/plugin.json"|g' \ - -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/react-native&ref=main"|"path:../../plugins/react-native/plugin.json"|g' \ - "$file" + -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/android&ref=main"|"path:../android/plugin.json"|g' \ + -e 's|"github:segment-integrations/mobile-devtools?dir=plugins/ios&ref=main"|"path:../ios/plugin.json"|g' \ + "plugins/react-native/plugin.json" fi - done + fi + echo "✓ Rewrote plugin URLs to local paths" ;; --to-github) - echo "Restoring plugin URLs to GitHub format in $search_dir..." - # Find all devbox.json files and restore from backup or rewrite path: to github: - find "$search_dir" -name "devbox.json" -type f | while read -r file; do - if [ -f "$file.bak" ]; then - echo " Restoring from backup: $file" - mv "$file.bak" "$file" - elif grep -q "path:.*plugins/.*/plugin.json" "$file"; then - echo " Rewriting: $file" - - # Rewrite URLs using sed (macOS compatible) - sed -i '' \ - -e 's|"path:../../plugins/android/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/android\&ref=main"|g' \ - -e 's|"path:../../plugins/ios/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/ios\&ref=main"|g' \ - -e 's|"path:../../plugins/react-native/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/react-native\&ref=main"|g' \ - "$file" - fi - done - # Clean up any remaining backups - find "$search_dir" -name "devbox.json.bak" -type f -delete + echo "Restoring plugin URLs to GitHub format..." + + # Restore examples/ devbox.json files + if [ -d "$search_dir" ]; then + echo " Processing examples in $search_dir..." + find "$search_dir" -name "devbox.json" -type f | while read -r file; do + if [ -f "$file.bak" ]; then + echo " Restoring from backup: $file" + mv "$file.bak" "$file" + elif grep -q "path:.*plugins/.*/plugin.json" "$file"; then + echo " Rewriting: $file" + + # Rewrite URLs using sed (macOS compatible) + sed -i '' \ + -e 's|"path:../../plugins/android/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/android\&ref=main"|g' \ + -e 's|"path:../../plugins/ios/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/ios\&ref=main"|g' \ + -e 's|"path:../../plugins/react-native/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/react-native\&ref=main"|g' \ + "$file" + fi + done + # Clean up any remaining backups + find "$search_dir" -name "devbox.json.bak" -type f -delete + fi + + # Restore plugins/ plugin.json files + if [ -f "plugins/react-native/plugin.json.bak" ]; then + echo " Processing react-native plugin..." + echo " Restoring from backup: plugins/react-native/plugin.json" + mv "plugins/react-native/plugin.json.bak" "plugins/react-native/plugin.json" + elif [ -f "plugins/react-native/plugin.json" ] && grep -q "path:../android/plugin.json\|path:../ios/plugin.json" "plugins/react-native/plugin.json"; then + echo " Processing react-native plugin..." + echo " Rewriting: plugins/react-native/plugin.json" + + # Rewrite URLs back to GitHub format + sed -i '' \ + -e 's|"path:../android/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/android\&ref=main"|g' \ + -e 's|"path:../ios/plugin.json"|"github:segment-integrations/mobile-devtools?dir=plugins/ios\&ref=main"|g' \ + "plugins/react-native/plugin.json" + fi + echo "✓ Restored plugin URLs to GitHub format" ;;