From 6e4cdb9b5a000e18ffb5a2c2591e0ba8b2dce1b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 29 Apr 2026 18:20:24 +0200 Subject: [PATCH 01/10] ci: add per-app build matrix with precise path triggers Replaces the LLM-only Android/iOS build workflows with one matrix-based workflow per platform that gates each app's build on `dorny/paths-filter`. Adds coverage for computer-vision, speech, text-embeddings, and bare-rn demos, plus a Jest smoke test for bare-rn. Skips builds on draft PRs and fires on ready_for_review so flipping a draft to ready triggers CI. Closes #963. --- .github/actions/build-android-app/action.yml | 78 +++++++++ .github/actions/build-ios-app/action.yml | 67 +++++++ .../workflows/build-android-llm-example.yml | 79 --------- .github/workflows/build-android.yml | 164 +++++++++++++++++ .github/workflows/build-ios-llm-example.yml | 66 ------- .github/workflows/build-ios.yml | 165 ++++++++++++++++++ .github/workflows/ci.yml | 11 ++ .github/workflows/docs-build-check.yml | 3 +- .github/workflows/test-bare-rn.yml | 43 +++++ 9 files changed, 530 insertions(+), 146 deletions(-) create mode 100644 .github/actions/build-android-app/action.yml create mode 100644 .github/actions/build-ios-app/action.yml delete mode 100644 .github/workflows/build-android-llm-example.yml create mode 100644 .github/workflows/build-android.yml delete mode 100644 .github/workflows/build-ios-llm-example.yml create mode 100644 .github/workflows/build-ios.yml create mode 100644 .github/workflows/test-bare-rn.yml diff --git a/.github/actions/build-android-app/action.yml b/.github/actions/build-android-app/action.yml new file mode 100644 index 000000000..c1b15a9d6 --- /dev/null +++ b/.github/actions/build-android-app/action.yml @@ -0,0 +1,78 @@ +name: Build Android app +description: Build an Android demo app from this monorepo (with optional Expo prebuild) + +inputs: + app-path: + description: Path to the app workspace, relative to the repo root (e.g. apps/llm) + required: true + expo-prebuild: + description: Whether to run `expo prebuild --platform android` before the Gradle build + required: false + default: "true" + +runs: + using: composite + steps: + - name: Free disk space + shell: bash + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo docker system prune -af + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: "yarn" + + - name: Setup Java 17 + uses: actions/setup-java@v5 + with: + distribution: "zulu" + java-version: 17 + cache: "gradle" + + - name: Install root dependencies + shell: bash + run: yarn install --immutable + + - name: Install Expo CLI + if: inputs.expo-prebuild == 'true' + shell: bash + run: | + npm install -g @expo/cli + echo "$(npm prefix -g)/bin" >> $GITHUB_PATH + + - name: Generate native Android project + if: inputs.expo-prebuild == 'true' + working-directory: ${{ inputs.app-path }} + shell: bash + run: | + rm -rf android + npx expo prebuild --platform android --no-install + + - name: Cache Gradle + uses: actions/cache@v5 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + ${{ inputs.app-path }}/android/.gradle + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Build app + working-directory: ${{ inputs.app-path }}/android + shell: bash + run: | + ./gradlew assembleDebug \ + --build-cache \ + --parallel \ + --daemon \ + --configure-on-demand \ + -PreactNativeArchitectures=arm64-v8a \ + -Dorg.gradle.jvmargs="-Xmx4g -XX:+HeapDumpOnOutOfMemoryError" \ + -Dorg.gradle.workers.max=4 diff --git a/.github/actions/build-ios-app/action.yml b/.github/actions/build-ios-app/action.yml new file mode 100644 index 000000000..9e579b486 --- /dev/null +++ b/.github/actions/build-ios-app/action.yml @@ -0,0 +1,67 @@ +name: Build iOS app +description: Build an iOS demo app from this monorepo (with optional Expo prebuild) + +inputs: + app-path: + description: Path to the app workspace, relative to the repo root (e.g. apps/llm) + required: true + expo-prebuild: + description: Whether to run `expo prebuild --platform ios` before pod install + required: false + default: "true" + +runs: + using: composite + steps: + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: "yarn" + + - name: Install root dependencies + shell: bash + run: yarn install --immutable + + - name: Install Expo CLI + if: inputs.expo-prebuild == 'true' + shell: bash + run: | + npm install -g @expo/cli + echo "$(npm prefix -g)/bin" >> $GITHUB_PATH + + - name: Generate native iOS project + if: inputs.expo-prebuild == 'true' + working-directory: ${{ inputs.app-path }} + shell: bash + run: | + rm -rf ios + npx expo prebuild --platform ios --no-install + + - name: Install CocoaPods dependencies + working-directory: ${{ inputs.app-path }}/ios + shell: bash + run: pod install + + - name: Build app + working-directory: ${{ inputs.app-path }}/ios + shell: bash + run: | + WORKSPACE=$(ls -d *.xcworkspace | head -n 1) + SCHEME="${WORKSPACE%.xcworkspace}" + set -o pipefail && xcodebuild \ + -workspace "$WORKSPACE" \ + -scheme "$SCHEME" \ + -sdk iphonesimulator \ + -configuration Debug \ + -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ + build \ + CODE_SIGNING_ALLOWED=NO \ + -jobs $(sysctl -n hw.ncpu) \ + COMPILER_INDEX_STORE_ENABLE=NO \ + ONLY_ACTIVE_ARCH=YES | xcbeautify diff --git a/.github/workflows/build-android-llm-example.yml b/.github/workflows/build-android-llm-example.yml deleted file mode 100644 index 3b4c95bf5..000000000 --- a/.github/workflows/build-android-llm-example.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: LLM Example app Android build check -on: - pull_request: - paths: - - .github/workflows/build-android-llm-example.yml - - apps/llm/** - - packages/react-native-executorch/** - push: - branches: - - main - paths: - - .github/workflows/build-android-llm-example.yml - - apps/llm/** - - packages/react-native-executorch/** - workflow_dispatch: -jobs: - build: - if: github.repository == 'software-mansion/react-native-executorch' - runs-on: ubuntu-latest - env: - WORKING_DIRECTORY: apps/llm - concurrency: - group: android-${{ github.ref }} - cancel-in-progress: true - steps: - - name: Free Disk Space (Manual) - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /opt/hostedtoolcache/CodeQL - sudo docker system prune -af - - - name: Check out Git repository - uses: actions/checkout@v6 - with: - submodules: recursive - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: "24" - cache: "yarn" - - name: Setup Java 17 - uses: actions/setup-java@v5 - with: - distribution: "zulu" - java-version: 17 - cache: "gradle" - - name: Install root dependencies - run: yarn install --immutable - - name: Install Expo CLI - run: | - npm install -g @expo/cli - echo "$(npm prefix -g)/bin" >> $GITHUB_PATH - - name: Generate native Android project - working-directory: ${{ env.WORKING_DIRECTORY }} - run: | - rm -rf android - npx expo prebuild --platform android --no-install - - name: Cache Gradle - uses: actions/cache@v5 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - ${{ env.WORKING_DIRECTORY }}/android/.gradle - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Build app - working-directory: ${{ env.WORKING_DIRECTORY }}/android - run: | - ./gradlew assembleDebug \ - --build-cache \ - --parallel \ - --daemon \ - --configure-on-demand \ - -PreactNativeArchitectures=arm64-v8a \ - -Dorg.gradle.jvmargs="-Xmx4g -XX:+HeapDumpOnOutOfMemoryError" \ - -Dorg.gradle.workers.max=4 diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml new file mode 100644 index 000000000..aebd76709 --- /dev/null +++ b/.github/workflows/build-android.yml @@ -0,0 +1,164 @@ +name: Android example apps build check +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: + +jobs: + detect-changes: + if: github.repository == 'software-mansion/react-native-executorch' && github.event.pull_request.draft != true + runs-on: ubuntu-latest + outputs: + apps: ${{ steps.matrix.outputs.apps }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Detect changed apps + id: filter + if: github.event_name != 'workflow_dispatch' + uses: dorny/paths-filter@v3 + with: + filters: | + workflow: &workflow + - .github/workflows/build-android.yml + - .github/actions/build-android-app/** + shared: &shared + - *workflow + - packages/react-native-executorch/android/** + - packages/react-native-executorch/scripts/** + - packages/react-native-executorch/third-party/** + - packages/react-native-executorch/package.json + - packages/react-native-executorch/tsconfig.json + - packages/react-native-executorch/common/ada/** + - packages/react-native-executorch/common/runner/** + - packages/react-native-executorch/common/rnexecutorch/Error.h + - packages/react-native-executorch/common/rnexecutorch/ErrorCodes.h + - packages/react-native-executorch/common/rnexecutorch/Log.h + - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp + - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.h + - packages/react-native-executorch/common/rnexecutorch/data_processing/** + - packages/react-native-executorch/common/rnexecutorch/host_objects/** + - packages/react-native-executorch/common/rnexecutorch/jsi/** + - packages/react-native-executorch/common/rnexecutorch/metaprogramming/** + - packages/react-native-executorch/common/rnexecutorch/tests/** + - packages/react-native-executorch/common/rnexecutorch/threads/** + - packages/react-native-executorch/common/rnexecutorch/utils/** + - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.cpp + - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.h + - packages/react-native-executorch/src/index.ts + - packages/react-native-executorch/src/common/** + - packages/react-native-executorch/src/constants/** + - packages/react-native-executorch/src/errors/** + - packages/react-native-executorch/src/native/** + - packages/react-native-executorch/src/types/** + - packages/react-native-executorch/src/utils/** + - packages/react-native-executorch/src/modules/BaseModule.ts + - packages/react-native-executorch/src/modules/general/** + - packages/react-native-executorch/src/hooks/general/** + - packages/react-native-executorch/src/hooks/useModule.ts + - packages/react-native-executorch/src/hooks/useModuleFactory.ts + - package.json + - yarn.lock + expo-shared: &expo-shared + - *shared + - packages/expo-resource-fetcher/** + llm-pkg: &llm-pkg + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h + - packages/react-native-executorch/common/rnexecutorch/models/llm/** + - packages/react-native-executorch/src/modules/natural_language_processing/LLMModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useLLM.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - packages/react-native-executorch/src/controllers/LLMController.ts + llm: + - *expo-shared + - *llm-pkg + - apps/llm/** + computer-vision: + - *expo-shared + - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.cpp + - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.h + - packages/react-native-executorch/common/rnexecutorch/models/classification/** + - packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/** + - packages/react-native-executorch/common/rnexecutorch/models/object_detection/** + - packages/react-native-executorch/common/rnexecutorch/models/ocr/** + - packages/react-native-executorch/common/rnexecutorch/models/semantic_segmentation/** + - packages/react-native-executorch/common/rnexecutorch/models/style_transfer/** + - packages/react-native-executorch/common/rnexecutorch/models/text_to_image/** + - packages/react-native-executorch/common/rnexecutorch/models/vertical_ocr/** + - packages/react-native-executorch/src/modules/computer_vision/** + - packages/react-native-executorch/src/hooks/computer_vision/** + - packages/react-native-executorch/src/controllers/BaseOCRController.ts + - packages/react-native-executorch/src/controllers/OCRController.ts + - packages/react-native-executorch/src/controllers/VerticalOCRController.ts + - apps/computer-vision/** + speech: + - *expo-shared + - packages/react-native-executorch/common/pfft/** + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h + - packages/react-native-executorch/common/rnexecutorch/models/speech_to_text/** + - packages/react-native-executorch/common/rnexecutorch/models/text_to_speech/** + - packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/** + - packages/react-native-executorch/src/modules/natural_language_processing/SpeechToTextModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/TextToSpeechModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/VADModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useSpeechToText.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useTextToSpeech.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useVAD.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - apps/speech/** + text-embeddings: + - *expo-shared + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h + - packages/react-native-executorch/common/rnexecutorch/models/embeddings/** + - packages/react-native-executorch/src/modules/natural_language_processing/TextEmbeddingsModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts + - packages/react-native-executorch/src/modules/computer_vision/ImageEmbeddingsModule.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useTextEmbeddings.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - packages/react-native-executorch/src/hooks/computer_vision/useImageEmbeddings.ts + - apps/text-embeddings/** + bare-rn: + - *shared + - *llm-pkg + - packages/bare-resource-fetcher/** + - apps/bare-rn/** + + - name: Compute matrix + id: matrix + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo 'apps=["llm","computer-vision","speech","text-embeddings","bare-rn"]' >> "$GITHUB_OUTPUT" + else + echo "apps=${{ steps.filter.outputs.changes }}" >> "$GITHUB_OUTPUT" + fi + + build: + needs: detect-changes + if: needs.detect-changes.outputs.apps != '[]' && needs.detect-changes.outputs.apps != '' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + app: ${{ fromJSON(needs.detect-changes.outputs.apps) }} + concurrency: + group: android-${{ matrix.app }}-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: recursive + - name: Build Android app + uses: ./.github/actions/build-android-app + with: + app-path: apps/${{ matrix.app }} + expo-prebuild: ${{ matrix.app == 'bare-rn' && 'false' || 'true' }} diff --git a/.github/workflows/build-ios-llm-example.yml b/.github/workflows/build-ios-llm-example.yml deleted file mode 100644 index 4a2b2b9b9..000000000 --- a/.github/workflows/build-ios-llm-example.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: LLM Example app iOS build check -on: - push: - branches: - - main - paths: - - ".github/workflows/build-ios-llm-example.yml" - - "*.podspec" - - "apps/llm/**" - - "packages/react-native-executorch/**" - pull_request: - paths: - - ".github/workflows/build-ios-llm-example.yml" - - "*.podspec" - - "apps/llm/**" - - "packages/react-native-executorch/**" - workflow_dispatch: -jobs: - build: - if: github.repository == 'software-mansion/react-native-executorch' - runs-on: macos-latest - concurrency: - group: ios-${{ github.ref }} - cancel-in-progress: true - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest-stable - - name: Check out Git repository - uses: actions/checkout@v6 - with: - submodules: recursive - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: "24" - cache: "yarn" - - name: Install root dependencies - run: yarn install --immutable - - name: Install Expo CLI - run: | - npm install -g @expo/cli - echo "$(npm prefix -g)/bin" >> $GITHUB_PATH - - name: Generate native iOS project - working-directory: apps/llm - run: | - rm -rf ios - npx expo prebuild --platform ios --no-install - - name: Install CocoaPods dependencies - working-directory: apps/llm/ios - run: | - pod install - - name: Build app - working-directory: apps/llm/ios - run: | - set -o pipefail && xcodebuild \ - -workspace llm.xcworkspace \ - -scheme llm \ - -sdk iphonesimulator \ - -configuration Debug \ - -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ - build \ - CODE_SIGNING_ALLOWED=NO \ - -jobs $(sysctl -n hw.ncpu) \ - COMPILER_INDEX_STORE_ENABLE=NO \ - ONLY_ACTIVE_ARCH=YES | xcbeautify diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml new file mode 100644 index 000000000..beb150197 --- /dev/null +++ b/.github/workflows/build-ios.yml @@ -0,0 +1,165 @@ +name: iOS example apps build check +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + workflow_dispatch: + +jobs: + detect-changes: + if: github.repository == 'software-mansion/react-native-executorch' && github.event.pull_request.draft != true + runs-on: ubuntu-latest + outputs: + apps: ${{ steps.matrix.outputs.apps }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Detect changed apps + id: filter + if: github.event_name != 'workflow_dispatch' + uses: dorny/paths-filter@v3 + with: + filters: | + workflow: &workflow + - .github/workflows/build-ios.yml + - .github/actions/build-ios-app/** + shared: &shared + - *workflow + - packages/react-native-executorch/ios/** + - packages/react-native-executorch/scripts/** + - packages/react-native-executorch/third-party/** + - packages/react-native-executorch/package.json + - packages/react-native-executorch/react-native-executorch.podspec + - packages/react-native-executorch/tsconfig.json + - packages/react-native-executorch/common/ada/** + - packages/react-native-executorch/common/runner/** + - packages/react-native-executorch/common/rnexecutorch/Error.h + - packages/react-native-executorch/common/rnexecutorch/ErrorCodes.h + - packages/react-native-executorch/common/rnexecutorch/Log.h + - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp + - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.h + - packages/react-native-executorch/common/rnexecutorch/data_processing/** + - packages/react-native-executorch/common/rnexecutorch/host_objects/** + - packages/react-native-executorch/common/rnexecutorch/jsi/** + - packages/react-native-executorch/common/rnexecutorch/metaprogramming/** + - packages/react-native-executorch/common/rnexecutorch/tests/** + - packages/react-native-executorch/common/rnexecutorch/threads/** + - packages/react-native-executorch/common/rnexecutorch/utils/** + - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.cpp + - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.h + - packages/react-native-executorch/src/index.ts + - packages/react-native-executorch/src/common/** + - packages/react-native-executorch/src/constants/** + - packages/react-native-executorch/src/errors/** + - packages/react-native-executorch/src/native/** + - packages/react-native-executorch/src/types/** + - packages/react-native-executorch/src/utils/** + - packages/react-native-executorch/src/modules/BaseModule.ts + - packages/react-native-executorch/src/modules/general/** + - packages/react-native-executorch/src/hooks/general/** + - packages/react-native-executorch/src/hooks/useModule.ts + - packages/react-native-executorch/src/hooks/useModuleFactory.ts + - package.json + - yarn.lock + expo-shared: &expo-shared + - *shared + - packages/expo-resource-fetcher/** + llm-pkg: &llm-pkg + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h + - packages/react-native-executorch/common/rnexecutorch/models/llm/** + - packages/react-native-executorch/src/modules/natural_language_processing/LLMModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useLLM.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - packages/react-native-executorch/src/controllers/LLMController.ts + llm: + - *expo-shared + - *llm-pkg + - apps/llm/** + computer-vision: + - *expo-shared + - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.cpp + - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.h + - packages/react-native-executorch/common/rnexecutorch/models/classification/** + - packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/** + - packages/react-native-executorch/common/rnexecutorch/models/object_detection/** + - packages/react-native-executorch/common/rnexecutorch/models/ocr/** + - packages/react-native-executorch/common/rnexecutorch/models/semantic_segmentation/** + - packages/react-native-executorch/common/rnexecutorch/models/style_transfer/** + - packages/react-native-executorch/common/rnexecutorch/models/text_to_image/** + - packages/react-native-executorch/common/rnexecutorch/models/vertical_ocr/** + - packages/react-native-executorch/src/modules/computer_vision/** + - packages/react-native-executorch/src/hooks/computer_vision/** + - packages/react-native-executorch/src/controllers/BaseOCRController.ts + - packages/react-native-executorch/src/controllers/OCRController.ts + - packages/react-native-executorch/src/controllers/VerticalOCRController.ts + - apps/computer-vision/** + speech: + - *expo-shared + - packages/react-native-executorch/common/pfft/** + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h + - packages/react-native-executorch/common/rnexecutorch/models/speech_to_text/** + - packages/react-native-executorch/common/rnexecutorch/models/text_to_speech/** + - packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/** + - packages/react-native-executorch/src/modules/natural_language_processing/SpeechToTextModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/TextToSpeechModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/VADModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useSpeechToText.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useTextToSpeech.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useVAD.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - apps/speech/** + text-embeddings: + - *expo-shared + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h + - packages/react-native-executorch/common/rnexecutorch/models/embeddings/** + - packages/react-native-executorch/src/modules/natural_language_processing/TextEmbeddingsModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts + - packages/react-native-executorch/src/modules/computer_vision/ImageEmbeddingsModule.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useTextEmbeddings.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - packages/react-native-executorch/src/hooks/computer_vision/useImageEmbeddings.ts + - apps/text-embeddings/** + bare-rn: + - *shared + - *llm-pkg + - packages/bare-resource-fetcher/** + - apps/bare-rn/** + + - name: Compute matrix + id: matrix + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo 'apps=["llm","computer-vision","speech","text-embeddings","bare-rn"]' >> "$GITHUB_OUTPUT" + else + echo "apps=${{ steps.filter.outputs.changes }}" >> "$GITHUB_OUTPUT" + fi + + build: + needs: detect-changes + if: needs.detect-changes.outputs.apps != '[]' && needs.detect-changes.outputs.apps != '' + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + app: ${{ fromJSON(needs.detect-changes.outputs.apps) }} + concurrency: + group: ios-${{ matrix.app }}-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: recursive + - name: Build iOS app + uses: ./.github/actions/build-ios-app + with: + app-path: apps/${{ matrix.app }} + expo-prebuild: ${{ matrix.app == 'bare-rn' && 'false' || 'true' }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96fd27ad6..3bf6626ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,15 +3,25 @@ on: push: branches: - main + paths-ignore: + - docs/** + - readmes/** + - "**.md" pull_request: branches: - main + types: [opened, synchronize, reopened, ready_for_review] + paths-ignore: + - docs/** + - readmes/** + - "**.md" merge_group: types: - checks_requested workflow_dispatch: jobs: lint: + if: github.event.pull_request.draft != true runs-on: ubuntu-latest steps: - name: Checkout @@ -27,6 +37,7 @@ jobs: run: yarn workspaces foreach --all --topological-dev run prepare && yarn typecheck build-library: + if: github.event.pull_request.draft != true runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/docs-build-check.yml b/.github/workflows/docs-build-check.yml index eec9d3a4a..ed3a2e332 100644 --- a/.github/workflows/docs-build-check.yml +++ b/.github/workflows/docs-build-check.yml @@ -10,13 +10,14 @@ on: pull_request: branches: - main + types: [opened, synchronize, reopened, ready_for_review] paths: - 'docs/**' - '.github/workflows/docs-build-check.yml' workflow_dispatch: jobs: check: - if: github.repository == 'software-mansion/react-native-executorch' + if: github.repository == 'software-mansion/react-native-executorch' && github.event.pull_request.draft != true runs-on: ubuntu-latest concurrency: group: docs-check-${{ github.ref }} diff --git a/.github/workflows/test-bare-rn.yml b/.github/workflows/test-bare-rn.yml new file mode 100644 index 000000000..f128606ed --- /dev/null +++ b/.github/workflows/test-bare-rn.yml @@ -0,0 +1,43 @@ +name: bare-rn Jest tests +on: + push: + branches: + - main + paths: + - .github/workflows/test-bare-rn.yml + - apps/bare-rn/** + - packages/bare-resource-fetcher/** + - packages/react-native-executorch/src/** + - packages/react-native-executorch/package.json + - packages/react-native-executorch/tsconfig.json + - package.json + - yarn.lock + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - .github/workflows/test-bare-rn.yml + - apps/bare-rn/** + - packages/bare-resource-fetcher/** + - packages/react-native-executorch/src/** + - packages/react-native-executorch/package.json + - packages/react-native-executorch/tsconfig.json + - package.json + - yarn.lock + workflow_dispatch: + +jobs: + test: + if: github.repository == 'software-mansion/react-native-executorch' && github.event.pull_request.draft != true + runs-on: ubuntu-latest + concurrency: + group: test-bare-rn-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup + uses: ./.github/actions/setup + + - name: Run Jest + run: yarn workspace bare-rn test From 471403f659831b64254c30e2b146d98c4427c9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 29 Apr 2026 18:32:49 +0200 Subject: [PATCH 02/10] ci: compact path filters with brace expansion --- .github/workflows/build-android.yml | 103 +++++++-------------------- .github/workflows/build-ios.yml | 104 +++++++--------------------- 2 files changed, 52 insertions(+), 155 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index aebd76709..7e14327cb 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -23,57 +23,29 @@ jobs: uses: dorny/paths-filter@v3 with: filters: | - workflow: &workflow + shared: &shared - .github/workflows/build-android.yml - .github/actions/build-android-app/** - shared: &shared - - *workflow - - packages/react-native-executorch/android/** - - packages/react-native-executorch/scripts/** - - packages/react-native-executorch/third-party/** - - packages/react-native-executorch/package.json - - packages/react-native-executorch/tsconfig.json - - packages/react-native-executorch/common/ada/** - - packages/react-native-executorch/common/runner/** - - packages/react-native-executorch/common/rnexecutorch/Error.h - - packages/react-native-executorch/common/rnexecutorch/ErrorCodes.h - - packages/react-native-executorch/common/rnexecutorch/Log.h - - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp - - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.h - - packages/react-native-executorch/common/rnexecutorch/data_processing/** - - packages/react-native-executorch/common/rnexecutorch/host_objects/** - - packages/react-native-executorch/common/rnexecutorch/jsi/** - - packages/react-native-executorch/common/rnexecutorch/metaprogramming/** - - packages/react-native-executorch/common/rnexecutorch/tests/** - - packages/react-native-executorch/common/rnexecutorch/threads/** - - packages/react-native-executorch/common/rnexecutorch/utils/** - - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.cpp - - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.h + - packages/react-native-executorch/{android,scripts,third-party}/** + - packages/react-native-executorch/{package.json,tsconfig.json} + - packages/react-native-executorch/common/{ada,runner}/** + - packages/react-native-executorch/common/rnexecutorch/{Error,ErrorCodes,Log}.h + - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.{cpp,h} + - packages/react-native-executorch/common/rnexecutorch/{data_processing,host_objects,jsi,metaprogramming,tests,threads,utils}/** + - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.{cpp,h} + - packages/react-native-executorch/src/{common,constants,errors,native,types,utils}/** - packages/react-native-executorch/src/index.ts - - packages/react-native-executorch/src/common/** - - packages/react-native-executorch/src/constants/** - - packages/react-native-executorch/src/errors/** - - packages/react-native-executorch/src/native/** - - packages/react-native-executorch/src/types/** - - packages/react-native-executorch/src/utils/** - - packages/react-native-executorch/src/modules/BaseModule.ts - - packages/react-native-executorch/src/modules/general/** - - packages/react-native-executorch/src/hooks/general/** - - packages/react-native-executorch/src/hooks/useModule.ts - - packages/react-native-executorch/src/hooks/useModuleFactory.ts - - package.json - - yarn.lock + - packages/react-native-executorch/src/modules/{BaseModule.ts,general/**} + - packages/react-native-executorch/src/hooks/{general/**,useModule.ts,useModuleFactory.ts} + - "{package.json,yarn.lock}" expo-shared: &expo-shared - *shared - packages/expo-resource-fetcher/** llm-pkg: &llm-pkg - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} - packages/react-native-executorch/common/rnexecutorch/models/llm/** - - packages/react-native-executorch/src/modules/natural_language_processing/LLMModule.ts - - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useLLM.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - packages/react-native-executorch/src/modules/natural_language_processing/{LLMModule,TokenizerModule}.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/{useLLM,useTokenizer}.ts - packages/react-native-executorch/src/controllers/LLMController.ts llm: - *expo-shared @@ -81,49 +53,26 @@ jobs: - apps/llm/** computer-vision: - *expo-shared - - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.cpp - - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.h - - packages/react-native-executorch/common/rnexecutorch/models/classification/** - - packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/** - - packages/react-native-executorch/common/rnexecutorch/models/object_detection/** - - packages/react-native-executorch/common/rnexecutorch/models/ocr/** - - packages/react-native-executorch/common/rnexecutorch/models/semantic_segmentation/** - - packages/react-native-executorch/common/rnexecutorch/models/style_transfer/** - - packages/react-native-executorch/common/rnexecutorch/models/text_to_image/** - - packages/react-native-executorch/common/rnexecutorch/models/vertical_ocr/** - - packages/react-native-executorch/src/modules/computer_vision/** - - packages/react-native-executorch/src/hooks/computer_vision/** - - packages/react-native-executorch/src/controllers/BaseOCRController.ts - - packages/react-native-executorch/src/controllers/OCRController.ts - - packages/react-native-executorch/src/controllers/VerticalOCRController.ts + - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.{cpp,h} + - packages/react-native-executorch/common/rnexecutorch/models/{classification,instance_segmentation,object_detection,ocr,semantic_segmentation,style_transfer,text_to_image,vertical_ocr}/** + - packages/react-native-executorch/src/{modules,hooks}/computer_vision/** + - packages/react-native-executorch/src/controllers/{BaseOCRController,OCRController,VerticalOCRController}.ts - apps/computer-vision/** speech: - *expo-shared - packages/react-native-executorch/common/pfft/** - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h - - packages/react-native-executorch/common/rnexecutorch/models/speech_to_text/** - - packages/react-native-executorch/common/rnexecutorch/models/text_to_speech/** - - packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/** - - packages/react-native-executorch/src/modules/natural_language_processing/SpeechToTextModule.ts - - packages/react-native-executorch/src/modules/natural_language_processing/TextToSpeechModule.ts - - packages/react-native-executorch/src/modules/natural_language_processing/VADModule.ts - - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useSpeechToText.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useTextToSpeech.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useVAD.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} + - packages/react-native-executorch/common/rnexecutorch/models/{speech_to_text,text_to_speech,voice_activity_detection}/** + - packages/react-native-executorch/src/modules/natural_language_processing/{SpeechToTextModule,TextToSpeechModule,VADModule,TokenizerModule}.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/{useSpeechToText,useTextToSpeech,useVAD,useTokenizer}.ts - apps/speech/** text-embeddings: - *expo-shared - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} - packages/react-native-executorch/common/rnexecutorch/models/embeddings/** - - packages/react-native-executorch/src/modules/natural_language_processing/TextEmbeddingsModule.ts - - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/{TextEmbeddingsModule,TokenizerModule}.ts - packages/react-native-executorch/src/modules/computer_vision/ImageEmbeddingsModule.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useTextEmbeddings.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/{useTextEmbeddings,useTokenizer}.ts - packages/react-native-executorch/src/hooks/computer_vision/useImageEmbeddings.ts - apps/text-embeddings/** bare-rn: diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index beb150197..0a517e021 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -23,58 +23,29 @@ jobs: uses: dorny/paths-filter@v3 with: filters: | - workflow: &workflow + shared: &shared - .github/workflows/build-ios.yml - .github/actions/build-ios-app/** - shared: &shared - - *workflow - - packages/react-native-executorch/ios/** - - packages/react-native-executorch/scripts/** - - packages/react-native-executorch/third-party/** - - packages/react-native-executorch/package.json - - packages/react-native-executorch/react-native-executorch.podspec - - packages/react-native-executorch/tsconfig.json - - packages/react-native-executorch/common/ada/** - - packages/react-native-executorch/common/runner/** - - packages/react-native-executorch/common/rnexecutorch/Error.h - - packages/react-native-executorch/common/rnexecutorch/ErrorCodes.h - - packages/react-native-executorch/common/rnexecutorch/Log.h - - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.cpp - - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.h - - packages/react-native-executorch/common/rnexecutorch/data_processing/** - - packages/react-native-executorch/common/rnexecutorch/host_objects/** - - packages/react-native-executorch/common/rnexecutorch/jsi/** - - packages/react-native-executorch/common/rnexecutorch/metaprogramming/** - - packages/react-native-executorch/common/rnexecutorch/tests/** - - packages/react-native-executorch/common/rnexecutorch/threads/** - - packages/react-native-executorch/common/rnexecutorch/utils/** - - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.cpp - - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.h + - packages/react-native-executorch/{ios,scripts,third-party}/** + - packages/react-native-executorch/{package.json,tsconfig.json,react-native-executorch.podspec} + - packages/react-native-executorch/common/{ada,runner}/** + - packages/react-native-executorch/common/rnexecutorch/{Error,ErrorCodes,Log}.h + - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.{cpp,h} + - packages/react-native-executorch/common/rnexecutorch/{data_processing,host_objects,jsi,metaprogramming,tests,threads,utils}/** + - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.{cpp,h} + - packages/react-native-executorch/src/{common,constants,errors,native,types,utils}/** - packages/react-native-executorch/src/index.ts - - packages/react-native-executorch/src/common/** - - packages/react-native-executorch/src/constants/** - - packages/react-native-executorch/src/errors/** - - packages/react-native-executorch/src/native/** - - packages/react-native-executorch/src/types/** - - packages/react-native-executorch/src/utils/** - - packages/react-native-executorch/src/modules/BaseModule.ts - - packages/react-native-executorch/src/modules/general/** - - packages/react-native-executorch/src/hooks/general/** - - packages/react-native-executorch/src/hooks/useModule.ts - - packages/react-native-executorch/src/hooks/useModuleFactory.ts - - package.json - - yarn.lock + - packages/react-native-executorch/src/modules/{BaseModule.ts,general/**} + - packages/react-native-executorch/src/hooks/{general/**,useModule.ts,useModuleFactory.ts} + - "{package.json,yarn.lock}" expo-shared: &expo-shared - *shared - packages/expo-resource-fetcher/** llm-pkg: &llm-pkg - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} - packages/react-native-executorch/common/rnexecutorch/models/llm/** - - packages/react-native-executorch/src/modules/natural_language_processing/LLMModule.ts - - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useLLM.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - packages/react-native-executorch/src/modules/natural_language_processing/{LLMModule,TokenizerModule}.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/{useLLM,useTokenizer}.ts - packages/react-native-executorch/src/controllers/LLMController.ts llm: - *expo-shared @@ -82,49 +53,26 @@ jobs: - apps/llm/** computer-vision: - *expo-shared - - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.cpp - - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.h - - packages/react-native-executorch/common/rnexecutorch/models/classification/** - - packages/react-native-executorch/common/rnexecutorch/models/instance_segmentation/** - - packages/react-native-executorch/common/rnexecutorch/models/object_detection/** - - packages/react-native-executorch/common/rnexecutorch/models/ocr/** - - packages/react-native-executorch/common/rnexecutorch/models/semantic_segmentation/** - - packages/react-native-executorch/common/rnexecutorch/models/style_transfer/** - - packages/react-native-executorch/common/rnexecutorch/models/text_to_image/** - - packages/react-native-executorch/common/rnexecutorch/models/vertical_ocr/** - - packages/react-native-executorch/src/modules/computer_vision/** - - packages/react-native-executorch/src/hooks/computer_vision/** - - packages/react-native-executorch/src/controllers/BaseOCRController.ts - - packages/react-native-executorch/src/controllers/OCRController.ts - - packages/react-native-executorch/src/controllers/VerticalOCRController.ts + - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.{cpp,h} + - packages/react-native-executorch/common/rnexecutorch/models/{classification,instance_segmentation,object_detection,ocr,semantic_segmentation,style_transfer,text_to_image,vertical_ocr}/** + - packages/react-native-executorch/src/{modules,hooks}/computer_vision/** + - packages/react-native-executorch/src/controllers/{BaseOCRController,OCRController,VerticalOCRController}.ts - apps/computer-vision/** speech: - *expo-shared - packages/react-native-executorch/common/pfft/** - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h - - packages/react-native-executorch/common/rnexecutorch/models/speech_to_text/** - - packages/react-native-executorch/common/rnexecutorch/models/text_to_speech/** - - packages/react-native-executorch/common/rnexecutorch/models/voice_activity_detection/** - - packages/react-native-executorch/src/modules/natural_language_processing/SpeechToTextModule.ts - - packages/react-native-executorch/src/modules/natural_language_processing/TextToSpeechModule.ts - - packages/react-native-executorch/src/modules/natural_language_processing/VADModule.ts - - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useSpeechToText.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useTextToSpeech.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useVAD.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} + - packages/react-native-executorch/common/rnexecutorch/models/{speech_to_text,text_to_speech,voice_activity_detection}/** + - packages/react-native-executorch/src/modules/natural_language_processing/{SpeechToTextModule,TextToSpeechModule,VADModule,TokenizerModule}.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/{useSpeechToText,useTextToSpeech,useVAD,useTokenizer}.ts - apps/speech/** text-embeddings: - *expo-shared - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.cpp - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.h + - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} - packages/react-native-executorch/common/rnexecutorch/models/embeddings/** - - packages/react-native-executorch/src/modules/natural_language_processing/TextEmbeddingsModule.ts - - packages/react-native-executorch/src/modules/natural_language_processing/TokenizerModule.ts + - packages/react-native-executorch/src/modules/natural_language_processing/{TextEmbeddingsModule,TokenizerModule}.ts - packages/react-native-executorch/src/modules/computer_vision/ImageEmbeddingsModule.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useTextEmbeddings.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/useTokenizer.ts + - packages/react-native-executorch/src/hooks/natural_language_processing/{useTextEmbeddings,useTokenizer}.ts - packages/react-native-executorch/src/hooks/computer_vision/useImageEmbeddings.ts - apps/text-embeddings/** bare-rn: From 12385c0e0d84b37d8ddd100d4585450a03121556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 29 Apr 2026 18:44:21 +0200 Subject: [PATCH 03/10] ci: build workspace packages before running bare-rn jest --- .github/workflows/test-bare-rn.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test-bare-rn.yml b/.github/workflows/test-bare-rn.yml index f128606ed..1c0ec656b 100644 --- a/.github/workflows/test-bare-rn.yml +++ b/.github/workflows/test-bare-rn.yml @@ -39,5 +39,8 @@ jobs: - name: Setup uses: ./.github/actions/setup + - name: Build workspace packages + run: yarn workspaces foreach --all --topological-dev run prepare + - name: Run Jest run: yarn workspace bare-rn test From ecbf7062be1c806d6460d3a9a4e3b7ba2983279b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 29 Apr 2026 18:51:47 +0200 Subject: [PATCH 04/10] ci: merge Android + iOS workflows into one with single filter block --- .../{build-android.yml => build-apps.yml} | 120 +++++++++++++----- .github/workflows/build-ios.yml | 113 ----------------- 2 files changed, 91 insertions(+), 142 deletions(-) rename .github/workflows/{build-android.yml => build-apps.yml} (55%) delete mode 100644 .github/workflows/build-ios.yml diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-apps.yml similarity index 55% rename from .github/workflows/build-android.yml rename to .github/workflows/build-apps.yml index 7e14327cb..72f65460c 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-apps.yml @@ -1,4 +1,4 @@ -name: Android example apps build check +name: Example apps build check on: push: branches: @@ -12,7 +12,8 @@ jobs: if: github.repository == 'software-mansion/react-native-executorch' && github.event.pull_request.draft != true runs-on: ubuntu-latest outputs: - apps: ${{ steps.matrix.outputs.apps }} + android_apps: ${{ steps.matrix.outputs.android_apps }} + ios_apps: ${{ steps.matrix.outputs.ios_apps }} steps: - name: Checkout uses: actions/checkout@v6 @@ -23,10 +24,10 @@ jobs: uses: dorny/paths-filter@v3 with: filters: | - shared: &shared - - .github/workflows/build-android.yml - - .github/actions/build-android-app/** - - packages/react-native-executorch/{android,scripts,third-party}/** + # Cross-platform shared infrastructure + core-shared: &core-shared + - .github/workflows/build-apps.yml + - packages/react-native-executorch/{scripts,third-party}/** - packages/react-native-executorch/{package.json,tsconfig.json} - packages/react-native-executorch/common/{ada,runner}/** - packages/react-native-executorch/common/rnexecutorch/{Error,ErrorCodes,Log}.h @@ -38,66 +39,105 @@ jobs: - packages/react-native-executorch/src/modules/{BaseModule.ts,general/**} - packages/react-native-executorch/src/hooks/{general/**,useModule.ts,useModuleFactory.ts} - "{package.json,yarn.lock}" - expo-shared: &expo-shared - - *shared + # Platform-specific shared + android-shared: &android-shared + - .github/actions/build-android-app/** + - packages/react-native-executorch/android/** + ios-shared: &ios-shared + - .github/actions/build-ios-app/** + - packages/react-native-executorch/ios/** + - packages/react-native-executorch/react-native-executorch.podspec + # Resource fetchers + expo-fetcher: &expo-fetcher - packages/expo-resource-fetcher/** + bare-fetcher: &bare-fetcher + - packages/bare-resource-fetcher/** + # Per-app package paths (TS modules + C++ models for the app's domain) llm-pkg: &llm-pkg - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} - packages/react-native-executorch/common/rnexecutorch/models/llm/** - packages/react-native-executorch/src/modules/natural_language_processing/{LLMModule,TokenizerModule}.ts - packages/react-native-executorch/src/hooks/natural_language_processing/{useLLM,useTokenizer}.ts - packages/react-native-executorch/src/controllers/LLMController.ts - llm: - - *expo-shared - - *llm-pkg - - apps/llm/** - computer-vision: - - *expo-shared + cv-pkg: &cv-pkg - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.{cpp,h} - packages/react-native-executorch/common/rnexecutorch/models/{classification,instance_segmentation,object_detection,ocr,semantic_segmentation,style_transfer,text_to_image,vertical_ocr}/** - packages/react-native-executorch/src/{modules,hooks}/computer_vision/** - packages/react-native-executorch/src/controllers/{BaseOCRController,OCRController,VerticalOCRController}.ts - - apps/computer-vision/** - speech: - - *expo-shared + speech-pkg: &speech-pkg - packages/react-native-executorch/common/pfft/** - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} - packages/react-native-executorch/common/rnexecutorch/models/{speech_to_text,text_to_speech,voice_activity_detection}/** - packages/react-native-executorch/src/modules/natural_language_processing/{SpeechToTextModule,TextToSpeechModule,VADModule,TokenizerModule}.ts - packages/react-native-executorch/src/hooks/natural_language_processing/{useSpeechToText,useTextToSpeech,useVAD,useTokenizer}.ts - - apps/speech/** - text-embeddings: - - *expo-shared + text-embeddings-pkg: &text-embeddings-pkg - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} - packages/react-native-executorch/common/rnexecutorch/models/embeddings/** - packages/react-native-executorch/src/modules/natural_language_processing/{TextEmbeddingsModule,TokenizerModule}.ts - packages/react-native-executorch/src/modules/computer_vision/ImageEmbeddingsModule.ts - packages/react-native-executorch/src/hooks/natural_language_processing/{useTextEmbeddings,useTokenizer}.ts - packages/react-native-executorch/src/hooks/computer_vision/useImageEmbeddings.ts + # Per-app bundle: core + fetcher + pkg + app dir + llm-app: &llm-app + - *core-shared + - *expo-fetcher + - *llm-pkg + - apps/llm/** + computer-vision-app: &computer-vision-app + - *core-shared + - *expo-fetcher + - *cv-pkg + - apps/computer-vision/** + speech-app: &speech-app + - *core-shared + - *expo-fetcher + - *speech-pkg + - apps/speech/** + text-embeddings-app: &text-embeddings-app + - *core-shared + - *expo-fetcher + - *text-embeddings-pkg - apps/text-embeddings/** - bare-rn: - - *shared + bare-rn-app: &bare-rn-app + - *core-shared + - *bare-fetcher - *llm-pkg - - packages/bare-resource-fetcher/** - apps/bare-rn/** + # Final per-platform per-app filters (the only ones the matrix consumes) + llm-android: [*llm-app, *android-shared] + llm-ios: [*llm-app, *ios-shared] + computer-vision-android: [*computer-vision-app, *android-shared] + computer-vision-ios: [*computer-vision-app, *ios-shared] + speech-android: [*speech-app, *android-shared] + speech-ios: [*speech-app, *ios-shared] + text-embeddings-android: [*text-embeddings-app, *android-shared] + text-embeddings-ios: [*text-embeddings-app, *ios-shared] + bare-rn-android: [*bare-rn-app, *android-shared] + bare-rn-ios: [*bare-rn-app, *ios-shared] - - name: Compute matrix + - name: Compute matrices id: matrix run: | + all='["llm","computer-vision","speech","text-embeddings","bare-rn"]' if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo 'apps=["llm","computer-vision","speech","text-embeddings","bare-rn"]' >> "$GITHUB_OUTPUT" + echo "android_apps=$all" >> "$GITHUB_OUTPUT" + echo "ios_apps=$all" >> "$GITHUB_OUTPUT" else - echo "apps=${{ steps.filter.outputs.changes }}" >> "$GITHUB_OUTPUT" + changes='${{ steps.filter.outputs.changes }}' + android_apps=$(echo "$changes" | jq -c '[.[] | select(endswith("-android")) | sub("-android$"; "")]') + ios_apps=$(echo "$changes" | jq -c '[.[] | select(endswith("-ios")) | sub("-ios$"; "")]') + echo "android_apps=$android_apps" >> "$GITHUB_OUTPUT" + echo "ios_apps=$ios_apps" >> "$GITHUB_OUTPUT" fi - build: + build-android: needs: detect-changes - if: needs.detect-changes.outputs.apps != '[]' && needs.detect-changes.outputs.apps != '' + if: needs.detect-changes.outputs.android_apps != '[]' && needs.detect-changes.outputs.android_apps != '' runs-on: ubuntu-latest strategy: fail-fast: false matrix: - app: ${{ fromJSON(needs.detect-changes.outputs.apps) }} + app: ${{ fromJSON(needs.detect-changes.outputs.android_apps) }} concurrency: group: android-${{ matrix.app }}-${{ github.ref }} cancel-in-progress: true @@ -111,3 +151,25 @@ jobs: with: app-path: apps/${{ matrix.app }} expo-prebuild: ${{ matrix.app == 'bare-rn' && 'false' || 'true' }} + + build-ios: + needs: detect-changes + if: needs.detect-changes.outputs.ios_apps != '[]' && needs.detect-changes.outputs.ios_apps != '' + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + app: ${{ fromJSON(needs.detect-changes.outputs.ios_apps) }} + concurrency: + group: ios-${{ matrix.app }}-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: recursive + - name: Build iOS app + uses: ./.github/actions/build-ios-app + with: + app-path: apps/${{ matrix.app }} + expo-prebuild: ${{ matrix.app == 'bare-rn' && 'false' || 'true' }} diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml deleted file mode 100644 index 0a517e021..000000000 --- a/.github/workflows/build-ios.yml +++ /dev/null @@ -1,113 +0,0 @@ -name: iOS example apps build check -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - workflow_dispatch: - -jobs: - detect-changes: - if: github.repository == 'software-mansion/react-native-executorch' && github.event.pull_request.draft != true - runs-on: ubuntu-latest - outputs: - apps: ${{ steps.matrix.outputs.apps }} - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Detect changed apps - id: filter - if: github.event_name != 'workflow_dispatch' - uses: dorny/paths-filter@v3 - with: - filters: | - shared: &shared - - .github/workflows/build-ios.yml - - .github/actions/build-ios-app/** - - packages/react-native-executorch/{ios,scripts,third-party}/** - - packages/react-native-executorch/{package.json,tsconfig.json,react-native-executorch.podspec} - - packages/react-native-executorch/common/{ada,runner}/** - - packages/react-native-executorch/common/rnexecutorch/{Error,ErrorCodes,Log}.h - - packages/react-native-executorch/common/rnexecutorch/RnExecutorchInstaller.{cpp,h} - - packages/react-native-executorch/common/rnexecutorch/{data_processing,host_objects,jsi,metaprogramming,tests,threads,utils}/** - - packages/react-native-executorch/common/rnexecutorch/models/BaseModel.{cpp,h} - - packages/react-native-executorch/src/{common,constants,errors,native,types,utils}/** - - packages/react-native-executorch/src/index.ts - - packages/react-native-executorch/src/modules/{BaseModule.ts,general/**} - - packages/react-native-executorch/src/hooks/{general/**,useModule.ts,useModuleFactory.ts} - - "{package.json,yarn.lock}" - expo-shared: &expo-shared - - *shared - - packages/expo-resource-fetcher/** - llm-pkg: &llm-pkg - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} - - packages/react-native-executorch/common/rnexecutorch/models/llm/** - - packages/react-native-executorch/src/modules/natural_language_processing/{LLMModule,TokenizerModule}.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/{useLLM,useTokenizer}.ts - - packages/react-native-executorch/src/controllers/LLMController.ts - llm: - - *expo-shared - - *llm-pkg - - apps/llm/** - computer-vision: - - *expo-shared - - packages/react-native-executorch/common/rnexecutorch/models/VisionModel.{cpp,h} - - packages/react-native-executorch/common/rnexecutorch/models/{classification,instance_segmentation,object_detection,ocr,semantic_segmentation,style_transfer,text_to_image,vertical_ocr}/** - - packages/react-native-executorch/src/{modules,hooks}/computer_vision/** - - packages/react-native-executorch/src/controllers/{BaseOCRController,OCRController,VerticalOCRController}.ts - - apps/computer-vision/** - speech: - - *expo-shared - - packages/react-native-executorch/common/pfft/** - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} - - packages/react-native-executorch/common/rnexecutorch/models/{speech_to_text,text_to_speech,voice_activity_detection}/** - - packages/react-native-executorch/src/modules/natural_language_processing/{SpeechToTextModule,TextToSpeechModule,VADModule,TokenizerModule}.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/{useSpeechToText,useTextToSpeech,useVAD,useTokenizer}.ts - - apps/speech/** - text-embeddings: - - *expo-shared - - packages/react-native-executorch/common/rnexecutorch/TokenizerModule.{cpp,h} - - packages/react-native-executorch/common/rnexecutorch/models/embeddings/** - - packages/react-native-executorch/src/modules/natural_language_processing/{TextEmbeddingsModule,TokenizerModule}.ts - - packages/react-native-executorch/src/modules/computer_vision/ImageEmbeddingsModule.ts - - packages/react-native-executorch/src/hooks/natural_language_processing/{useTextEmbeddings,useTokenizer}.ts - - packages/react-native-executorch/src/hooks/computer_vision/useImageEmbeddings.ts - - apps/text-embeddings/** - bare-rn: - - *shared - - *llm-pkg - - packages/bare-resource-fetcher/** - - apps/bare-rn/** - - - name: Compute matrix - id: matrix - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo 'apps=["llm","computer-vision","speech","text-embeddings","bare-rn"]' >> "$GITHUB_OUTPUT" - else - echo "apps=${{ steps.filter.outputs.changes }}" >> "$GITHUB_OUTPUT" - fi - - build: - needs: detect-changes - if: needs.detect-changes.outputs.apps != '[]' && needs.detect-changes.outputs.apps != '' - runs-on: macos-latest - strategy: - fail-fast: false - matrix: - app: ${{ fromJSON(needs.detect-changes.outputs.apps) }} - concurrency: - group: ios-${{ matrix.app }}-${{ github.ref }} - cancel-in-progress: true - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - submodules: recursive - - name: Build iOS app - uses: ./.github/actions/build-ios-app - with: - app-path: apps/${{ matrix.app }} - expo-prebuild: ${{ matrix.app == 'bare-rn' && 'false' || 'true' }} From dd347707fe7384b76c7b9dfb3a415d69e5226cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 29 Apr 2026 19:03:20 +0200 Subject: [PATCH 05/10] test(bare-rn): mock native packages so smoke test can render --- apps/bare-rn/__mocks__/background-downloader.js | 3 +++ ...eact-native-executorch-bare-resource-fetcher.js | 3 +++ apps/bare-rn/__mocks__/react-native-executorch.js | 14 ++++++++++++++ apps/bare-rn/jest.config.js | 8 ++++++++ 4 files changed, 28 insertions(+) create mode 100644 apps/bare-rn/__mocks__/background-downloader.js create mode 100644 apps/bare-rn/__mocks__/react-native-executorch-bare-resource-fetcher.js create mode 100644 apps/bare-rn/__mocks__/react-native-executorch.js diff --git a/apps/bare-rn/__mocks__/background-downloader.js b/apps/bare-rn/__mocks__/background-downloader.js new file mode 100644 index 000000000..034ce1e1d --- /dev/null +++ b/apps/bare-rn/__mocks__/background-downloader.js @@ -0,0 +1,3 @@ +module.exports = { + setConfig: jest.fn(), +}; diff --git a/apps/bare-rn/__mocks__/react-native-executorch-bare-resource-fetcher.js b/apps/bare-rn/__mocks__/react-native-executorch-bare-resource-fetcher.js new file mode 100644 index 000000000..a2fc0d0ee --- /dev/null +++ b/apps/bare-rn/__mocks__/react-native-executorch-bare-resource-fetcher.js @@ -0,0 +1,3 @@ +module.exports = { + BareResourceFetcher: {}, +}; diff --git a/apps/bare-rn/__mocks__/react-native-executorch.js b/apps/bare-rn/__mocks__/react-native-executorch.js new file mode 100644 index 000000000..5d7b39a2f --- /dev/null +++ b/apps/bare-rn/__mocks__/react-native-executorch.js @@ -0,0 +1,14 @@ +module.exports = { + initExecutorch: jest.fn(), + useLLM: () => ({ + isReady: false, + isGenerating: false, + response: '', + messageHistory: [], + downloadProgress: 0, + error: null, + sendMessage: jest.fn(), + interrupt: jest.fn(), + }), + LLAMA3_2_1B_SPINQUANT: 'LLAMA3_2_1B_SPINQUANT', +}; diff --git a/apps/bare-rn/jest.config.js b/apps/bare-rn/jest.config.js index 8eb675e9b..ee30550a8 100644 --- a/apps/bare-rn/jest.config.js +++ b/apps/bare-rn/jest.config.js @@ -1,3 +1,11 @@ module.exports = { preset: 'react-native', + moduleNameMapper: { + '^react-native-executorch$': + '/__mocks__/react-native-executorch.js', + '^react-native-executorch-bare-resource-fetcher$': + '/__mocks__/react-native-executorch-bare-resource-fetcher.js', + '^@kesha-antonov/react-native-background-downloader$': + '/__mocks__/background-downloader.js', + }, }; From c9d2a9f307a89b27096a0e3fd6b4cebf44679815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 29 Apr 2026 19:12:59 +0200 Subject: [PATCH 06/10] ci: use generic iOS Simulator destination so build doesn't need a specific device --- .github/actions/build-ios-app/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/build-ios-app/action.yml b/.github/actions/build-ios-app/action.yml index 9e579b486..9d0b45922 100644 --- a/.github/actions/build-ios-app/action.yml +++ b/.github/actions/build-ios-app/action.yml @@ -59,7 +59,7 @@ runs: -scheme "$SCHEME" \ -sdk iphonesimulator \ -configuration Debug \ - -destination 'platform=iOS Simulator,name=iPhone 16 Pro' \ + -destination 'generic/platform=iOS Simulator' \ build \ CODE_SIGNING_ALLOWED=NO \ -jobs $(sysctl -n hw.ncpu) \ From 2c68b807ad09bfb822e12c5e7277445b42e46d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 29 Apr 2026 19:49:15 +0200 Subject: [PATCH 07/10] ci: add Metro bundle check matrix to catch JS errors before native build --- .github/workflows/build-apps.yml | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/workflows/build-apps.yml b/.github/workflows/build-apps.yml index 72f65460c..2c9eff594 100644 --- a/.github/workflows/build-apps.yml +++ b/.github/workflows/build-apps.yml @@ -14,6 +14,7 @@ jobs: outputs: android_apps: ${{ steps.matrix.outputs.android_apps }} ios_apps: ${{ steps.matrix.outputs.ios_apps }} + bundle_apps: ${{ steps.matrix.outputs.bundle_apps }} steps: - name: Checkout uses: actions/checkout@v6 @@ -122,12 +123,47 @@ jobs: if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "android_apps=$all" >> "$GITHUB_OUTPUT" echo "ios_apps=$all" >> "$GITHUB_OUTPUT" + echo "bundle_apps=$all" >> "$GITHUB_OUTPUT" else changes='${{ steps.filter.outputs.changes }}' android_apps=$(echo "$changes" | jq -c '[.[] | select(endswith("-android")) | sub("-android$"; "")]') ios_apps=$(echo "$changes" | jq -c '[.[] | select(endswith("-ios")) | sub("-ios$"; "")]') + bundle_apps=$(echo "$changes" | jq -c '[.[] | select(endswith("-app")) | sub("-app$"; "")]') echo "android_apps=$android_apps" >> "$GITHUB_OUTPUT" echo "ios_apps=$ios_apps" >> "$GITHUB_OUTPUT" + echo "bundle_apps=$bundle_apps" >> "$GITHUB_OUTPUT" + fi + + bundle: + needs: detect-changes + if: needs.detect-changes.outputs.bundle_apps != '[]' && needs.detect-changes.outputs.bundle_apps != '' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + app: ${{ fromJSON(needs.detect-changes.outputs.bundle_apps) }} + platform: [android, ios] + concurrency: + group: bundle-${{ matrix.platform }}-${{ matrix.app }}-${{ github.ref }} + cancel-in-progress: true + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Setup + uses: ./.github/actions/setup + - name: Bundle JS for ${{ matrix.platform }} + working-directory: apps/${{ matrix.app }} + run: | + if [ "${{ matrix.app }}" = "bare-rn" ]; then + npx react-native bundle \ + --platform ${{ matrix.platform }} \ + --entry-file index.js \ + --bundle-output /tmp/bundle.js \ + --dev false + else + npx expo export \ + --platform ${{ matrix.platform }} \ + --output-dir /tmp/expo-export fi build-android: From d1af2edf327862ee9dcc53b12a9b87922f4586cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 29 Apr 2026 20:03:33 +0200 Subject: [PATCH 08/10] ci: fail lint when source files aren't covered by build-apps filter --- .github/workflows/build-apps.yml | 6 +++ .github/workflows/ci.yml | 3 ++ scripts/check-ci-filter-coverage.js | 63 +++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 scripts/check-ci-filter-coverage.js diff --git a/.github/workflows/build-apps.yml b/.github/workflows/build-apps.yml index 2c9eff594..483a05fdc 100644 --- a/.github/workflows/build-apps.yml +++ b/.github/workflows/build-apps.yml @@ -1,3 +1,9 @@ +# Per-app build matrix gated by `dorny/paths-filter`. Adding a new model +# directory, controller, or top-level module under packages/react-native-executorch/ +# probably means updating the filter block below — `core-shared` if it affects every +# app, or one of the per-app `-pkg` anchors otherwise. CI runs +# `scripts/check-ci-filter-coverage.js` (in ci.yml's lint job) to flag uncovered +# files so this stays accurate. name: Example apps build check on: push: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bf6626ed..ab549cbcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,9 @@ jobs: - name: Lint files run: yarn lint + - name: Check CI filter coverage + run: node scripts/check-ci-filter-coverage.js + - name: Typecheck files run: yarn workspaces foreach --all --topological-dev run prepare && yarn typecheck diff --git a/scripts/check-ci-filter-coverage.js b/scripts/check-ci-filter-coverage.js new file mode 100644 index 000000000..55805ce3d --- /dev/null +++ b/scripts/check-ci-filter-coverage.js @@ -0,0 +1,63 @@ +#!/usr/bin/env node +// Verifies every source file under packages/react-native-executorch/ is matched by +// at least one filter in .github/workflows/build-apps.yml. Prevents new files (e.g. +// a new model directory or controller) from silently slipping past CI's per-app +// path triggers. + +const fs = require('fs'); +const cp = require('child_process'); +const yaml = require('js-yaml'); +const picomatch = require('picomatch'); + +const WORKFLOW = '.github/workflows/build-apps.yml'; +const PACKAGE_ROOT = 'packages/react-native-executorch/'; + +// Files that legitimately don't belong to any per-app or shared filter. +const ALLOWLIST = new Set([ + 'packages/react-native-executorch/.gitignore', + 'packages/react-native-executorch/.watchmanconfig', + 'packages/react-native-executorch/tsconfig.doc.json', +]); + +const flatten = (x) => (Array.isArray(x) ? x.flatMap(flatten) : [x]); + +const wf = yaml.load(fs.readFileSync(WORKFLOW, 'utf8')); +const filtersStr = wf.jobs['detect-changes'].steps.find( + (s) => s.id === 'filter' +).with.filters; +const filters = yaml.load(filtersStr); + +const patterns = new Set(); +for (const v of Object.values(filters)) { + flatten(v) + .filter((p) => typeof p === 'string') + .forEach((p) => patterns.add(p)); +} +const matchers = [...patterns].map((p) => picomatch(p, { dot: true })); +const matchAny = (file) => matchers.some((m) => m(file)); + +const tracked = cp + .execSync('git ls-files', { encoding: 'utf8' }) + .trim() + .split('\n'); +const orphans = tracked + .filter((f) => f.startsWith(PACKAGE_ROOT)) + .filter((f) => !ALLOWLIST.has(f)) + .filter((f) => !matchAny(f)); + +if (orphans.length > 0) { + console.error( + `\n${WORKFLOW} does not cover ${orphans.length} file(s) under ${PACKAGE_ROOT}:\n` + ); + orphans.forEach((f) => console.error(' ' + f)); + console.error( + `\nAdd them to the appropriate filter (core-shared, llm-pkg, cv-pkg, speech-pkg,\n` + + `text-embeddings-pkg, or one of the platform-shared blocks). If the file is\n` + + `genuinely build-irrelevant, add it to ALLOWLIST in scripts/check-ci-filter-coverage.js.` + ); + process.exit(1); +} + +console.log( + `OK: every file under ${PACKAGE_ROOT} is covered by build-apps.yml.` +); From 9aef7d09fc0a84aab6de864cb1188bc5bed79c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 29 Apr 2026 20:23:22 +0200 Subject: [PATCH 09/10] ci: skip already-passing app builds via content-hash cache marker --- .github/actions/build-android-app/action.yml | 65 ++++++++++++++++---- .github/actions/build-ios-app/action.yml | 54 +++++++++++++--- .github/workflows/build-apps.yml | 29 +++++++++ scripts/compute-app-hash.js | 56 +++++++++++++++++ 4 files changed, 185 insertions(+), 19 deletions(-) create mode 100644 scripts/compute-app-hash.js diff --git a/.github/actions/build-android-app/action.yml b/.github/actions/build-android-app/action.yml index c1b15a9d6..3afe2e0c5 100644 --- a/.github/actions/build-android-app/action.yml +++ b/.github/actions/build-android-app/action.yml @@ -9,11 +9,45 @@ inputs: description: Whether to run `expo prebuild --platform android` before the Gradle build required: false default: "true" + filter-name: + description: dorny/paths-filter filter name for this app+platform (e.g. llm-android). Used as the content-hash cache key so a previously-passing build can be skipped if nothing relevant has changed. + required: true runs: using: composite steps: + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + cache: "yarn" + + - name: Install root dependencies + shell: bash + run: yarn install --immutable + + - name: Compute build hash + id: hash + shell: bash + run: | + h=$(node scripts/compute-app-hash.js "${{ inputs.filter-name }}") + echo "key=build-${{ inputs.filter-name }}-$h" >> "$GITHUB_OUTPUT" + + - name: Lookup pass marker + id: cache + uses: actions/cache/restore@v4 + with: + path: ${{ runner.temp }}/ci-marker + key: ${{ steps.hash.outputs.key }} + lookup-only: true + + - name: Skip notice + if: steps.cache.outputs.cache-hit == 'true' + shell: bash + run: echo "Skipping build — ${{ inputs.filter-name }} already passed at this content hash." + - name: Free disk space + if: steps.cache.outputs.cache-hit != 'true' shell: bash run: | sudo rm -rf /usr/share/dotnet @@ -21,32 +55,23 @@ runs: sudo rm -rf /opt/hostedtoolcache/CodeQL sudo docker system prune -af - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version-file: .nvmrc - cache: "yarn" - - name: Setup Java 17 + if: steps.cache.outputs.cache-hit != 'true' uses: actions/setup-java@v5 with: distribution: "zulu" java-version: 17 cache: "gradle" - - name: Install root dependencies - shell: bash - run: yarn install --immutable - - name: Install Expo CLI - if: inputs.expo-prebuild == 'true' + if: steps.cache.outputs.cache-hit != 'true' && inputs.expo-prebuild == 'true' shell: bash run: | npm install -g @expo/cli echo "$(npm prefix -g)/bin" >> $GITHUB_PATH - name: Generate native Android project - if: inputs.expo-prebuild == 'true' + if: steps.cache.outputs.cache-hit != 'true' && inputs.expo-prebuild == 'true' working-directory: ${{ inputs.app-path }} shell: bash run: | @@ -54,6 +79,7 @@ runs: npx expo prebuild --platform android --no-install - name: Cache Gradle + if: steps.cache.outputs.cache-hit != 'true' uses: actions/cache@v5 with: path: | @@ -65,6 +91,7 @@ runs: ${{ runner.os }}-gradle- - name: Build app + if: steps.cache.outputs.cache-hit != 'true' working-directory: ${{ inputs.app-path }}/android shell: bash run: | @@ -76,3 +103,17 @@ runs: -PreactNativeArchitectures=arm64-v8a \ -Dorg.gradle.jvmargs="-Xmx4g -XX:+HeapDumpOnOutOfMemoryError" \ -Dorg.gradle.workers.max=4 + + - name: Save pass marker + if: steps.cache.outputs.cache-hit != 'true' && success() + shell: bash + run: | + mkdir -p "${{ runner.temp }}/ci-marker" + touch "${{ runner.temp }}/ci-marker/passed" + + - name: Cache pass marker + if: steps.cache.outputs.cache-hit != 'true' && success() + uses: actions/cache/save@v4 + with: + path: ${{ runner.temp }}/ci-marker + key: ${{ steps.hash.outputs.key }} diff --git a/.github/actions/build-ios-app/action.yml b/.github/actions/build-ios-app/action.yml index 9d0b45922..31c9835c2 100644 --- a/.github/actions/build-ios-app/action.yml +++ b/.github/actions/build-ios-app/action.yml @@ -9,15 +9,13 @@ inputs: description: Whether to run `expo prebuild --platform ios` before pod install required: false default: "true" + filter-name: + description: dorny/paths-filter filter name for this app+platform (e.g. llm-ios). Used as the content-hash cache key so a previously-passing build can be skipped if nothing relevant has changed. + required: true runs: using: composite steps: - - name: Setup Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: latest-stable - - name: Setup Node.js uses: actions/setup-node@v6 with: @@ -28,15 +26,41 @@ runs: shell: bash run: yarn install --immutable + - name: Compute build hash + id: hash + shell: bash + run: | + h=$(node scripts/compute-app-hash.js "${{ inputs.filter-name }}") + echo "key=build-${{ inputs.filter-name }}-$h" >> "$GITHUB_OUTPUT" + + - name: Lookup pass marker + id: cache + uses: actions/cache/restore@v4 + with: + path: ${{ runner.temp }}/ci-marker + key: ${{ steps.hash.outputs.key }} + lookup-only: true + + - name: Skip notice + if: steps.cache.outputs.cache-hit == 'true' + shell: bash + run: echo "Skipping build — ${{ inputs.filter-name }} already passed at this content hash." + + - name: Setup Xcode + if: steps.cache.outputs.cache-hit != 'true' + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + - name: Install Expo CLI - if: inputs.expo-prebuild == 'true' + if: steps.cache.outputs.cache-hit != 'true' && inputs.expo-prebuild == 'true' shell: bash run: | npm install -g @expo/cli echo "$(npm prefix -g)/bin" >> $GITHUB_PATH - name: Generate native iOS project - if: inputs.expo-prebuild == 'true' + if: steps.cache.outputs.cache-hit != 'true' && inputs.expo-prebuild == 'true' working-directory: ${{ inputs.app-path }} shell: bash run: | @@ -44,11 +68,13 @@ runs: npx expo prebuild --platform ios --no-install - name: Install CocoaPods dependencies + if: steps.cache.outputs.cache-hit != 'true' working-directory: ${{ inputs.app-path }}/ios shell: bash run: pod install - name: Build app + if: steps.cache.outputs.cache-hit != 'true' working-directory: ${{ inputs.app-path }}/ios shell: bash run: | @@ -65,3 +91,17 @@ runs: -jobs $(sysctl -n hw.ncpu) \ COMPILER_INDEX_STORE_ENABLE=NO \ ONLY_ACTIVE_ARCH=YES | xcbeautify + + - name: Save pass marker + if: steps.cache.outputs.cache-hit != 'true' && success() + shell: bash + run: | + mkdir -p "${{ runner.temp }}/ci-marker" + touch "${{ runner.temp }}/ci-marker/passed" + + - name: Cache pass marker + if: steps.cache.outputs.cache-hit != 'true' && success() + uses: actions/cache/save@v4 + with: + path: ${{ runner.temp }}/ci-marker + key: ${{ steps.hash.outputs.key }} diff --git a/.github/workflows/build-apps.yml b/.github/workflows/build-apps.yml index 483a05fdc..2063f7962 100644 --- a/.github/workflows/build-apps.yml +++ b/.github/workflows/build-apps.yml @@ -157,7 +157,23 @@ jobs: uses: actions/checkout@v6 - name: Setup uses: ./.github/actions/setup + - name: Compute bundle hash + id: hash + run: | + h=$(node scripts/compute-app-hash.js "${{ matrix.app }}-app") + echo "key=bundle-${{ matrix.platform }}-${{ matrix.app }}-$h" >> "$GITHUB_OUTPUT" + - name: Lookup pass marker + id: cache + uses: actions/cache/restore@v4 + with: + path: ${{ runner.temp }}/ci-marker + key: ${{ steps.hash.outputs.key }} + lookup-only: true + - name: Skip notice + if: steps.cache.outputs.cache-hit == 'true' + run: echo "Skipping bundle — bundle-${{ matrix.platform }}-${{ matrix.app }} already passed at this content hash." - name: Bundle JS for ${{ matrix.platform }} + if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/${{ matrix.app }} run: | if [ "${{ matrix.app }}" = "bare-rn" ]; then @@ -171,6 +187,17 @@ jobs: --platform ${{ matrix.platform }} \ --output-dir /tmp/expo-export fi + - name: Save pass marker + if: steps.cache.outputs.cache-hit != 'true' && success() + run: | + mkdir -p "${{ runner.temp }}/ci-marker" + touch "${{ runner.temp }}/ci-marker/passed" + - name: Cache pass marker + if: steps.cache.outputs.cache-hit != 'true' && success() + uses: actions/cache/save@v4 + with: + path: ${{ runner.temp }}/ci-marker + key: ${{ steps.hash.outputs.key }} build-android: needs: detect-changes @@ -193,6 +220,7 @@ jobs: with: app-path: apps/${{ matrix.app }} expo-prebuild: ${{ matrix.app == 'bare-rn' && 'false' || 'true' }} + filter-name: ${{ matrix.app }}-android build-ios: needs: detect-changes @@ -215,3 +243,4 @@ jobs: with: app-path: apps/${{ matrix.app }} expo-prebuild: ${{ matrix.app == 'bare-rn' && 'false' || 'true' }} + filter-name: ${{ matrix.app }}-ios diff --git a/scripts/compute-app-hash.js b/scripts/compute-app-hash.js new file mode 100644 index 000000000..85dba7205 --- /dev/null +++ b/scripts/compute-app-hash.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node +// Computes a stable content hash of every tracked file matched by a given +// filter in .github/workflows/build-apps.yml. Used as a cache key in the +// build-apps matrix so a previously-passing app/platform cell can be skipped +// when nothing relevant has changed since. + +const fs = require('fs'); +const cp = require('child_process'); +const crypto = require('crypto'); +const yaml = require('js-yaml'); +const picomatch = require('picomatch'); + +const filterName = process.argv[2]; +if (!filterName) { + console.error('Usage: compute-app-hash.js '); + process.exit(1); +} + +const wf = yaml.load( + fs.readFileSync('.github/workflows/build-apps.yml', 'utf8') +); +const filtersStr = wf.jobs['detect-changes'].steps.find( + (s) => s.id === 'filter' +).with.filters; +const filters = yaml.load(filtersStr); + +const flatten = (x) => (Array.isArray(x) ? x.flatMap(flatten) : [x]); +const patterns = flatten(filters[filterName]).filter( + (p) => typeof p === 'string' +); +if (patterns.length === 0) { + console.error(`Unknown filter: ${filterName}`); + process.exit(1); +} +const matchers = patterns.map((p) => picomatch(p, { dot: true })); +const matchAny = (file) => matchers.some((m) => m(file)); + +// `git ls-files -s` outputs: \t +// The hash is a content-addressable git blob hash, so the same content always +// produces the same line — the SHA256 below is stable across machines. +const lines = cp + .execSync('git ls-files -s', { encoding: 'utf8' }) + .trim() + .split('\n') + .filter((line) => { + const tabIdx = line.indexOf('\t'); + return tabIdx !== -1 && matchAny(line.slice(tabIdx + 1)); + }) + .sort(); + +const sha = crypto + .createHash('sha256') + .update(lines.join('\n')) + .digest('hex') + .slice(0, 16); +console.log(sha); From dbb873715ac6c7b5bbfb76d2769ca3fae822a857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=C5=82uszniak?= Date: Wed, 29 Apr 2026 21:10:15 +0200 Subject: [PATCH 10/10] ci: bare-rn bundle prep, Pods cache, exclude workflow file from build hash - bundle job now runs `yarn prepare` before bundling bare-rn so react-native-executorch-bare-resource-fetcher's lib/ exists for metro (Expo apps don't need it; expo export resolves workspace deps directly). - iOS composite caches CocoaPods recipes + installed Pods keyed by Podfile.lock, saving ~3-5 min per cell on cache hits. - compute-app-hash.js excludes .github/workflows/build-apps.yml from the content hash. Workflow edits still trigger the matrix, but each cell hits its existing marker and skips. Build-behavior workflow edits (with:, runs-on:, env:) need a manual cache clear via the UI. --- .github/actions/build-ios-app/action.yml | 11 +++++++++++ .github/workflows/build-apps.yml | 6 ++++++ scripts/compute-app-hash.js | 16 +++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.github/actions/build-ios-app/action.yml b/.github/actions/build-ios-app/action.yml index 31c9835c2..a09a6af3f 100644 --- a/.github/actions/build-ios-app/action.yml +++ b/.github/actions/build-ios-app/action.yml @@ -67,6 +67,17 @@ runs: rm -rf ios npx expo prebuild --platform ios --no-install + - name: Cache CocoaPods + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/cache@v4 + with: + path: | + ~/Library/Caches/CocoaPods + ${{ inputs.app-path }}/ios/Pods + key: ${{ runner.os }}-pods-${{ inputs.filter-name }}-${{ hashFiles(format('{0}/ios/Podfile.lock', inputs.app-path)) }} + restore-keys: | + ${{ runner.os }}-pods-${{ inputs.filter-name }}- + - name: Install CocoaPods dependencies if: steps.cache.outputs.cache-hit != 'true' working-directory: ${{ inputs.app-path }}/ios diff --git a/.github/workflows/build-apps.yml b/.github/workflows/build-apps.yml index 2063f7962..f59e1af6b 100644 --- a/.github/workflows/build-apps.yml +++ b/.github/workflows/build-apps.yml @@ -172,6 +172,12 @@ jobs: - name: Skip notice if: steps.cache.outputs.cache-hit == 'true' run: echo "Skipping bundle — bundle-${{ matrix.platform }}-${{ matrix.app }} already passed at this content hash." + - name: Build workspace packages + if: steps.cache.outputs.cache-hit != 'true' && matrix.app == 'bare-rn' + run: yarn workspaces foreach --all --topological-dev run prepare + - name: Build workspace packages + if: steps.cache.outputs.cache-hit != 'true' && matrix.app == 'bare-rn' + run: yarn workspaces foreach --all --topological-dev run prepare - name: Bundle JS for ${{ matrix.platform }} if: steps.cache.outputs.cache-hit != 'true' working-directory: apps/${{ matrix.app }} diff --git a/scripts/compute-app-hash.js b/scripts/compute-app-hash.js index 85dba7205..bfc3df384 100644 --- a/scripts/compute-app-hash.js +++ b/scripts/compute-app-hash.js @@ -10,6 +10,18 @@ const crypto = require('crypto'); const yaml = require('js-yaml'); const picomatch = require('picomatch'); +// Files that the per-app filters reference for trigger purposes but that +// should not invalidate cached build markers. The workflow file itself sits in +// core-shared so editing it triggers CI on every app, but the vast majority of +// edits are orchestration (filter paths, matrix shape, concurrency, triggers) +// and don't change build behavior. Excluding it from the hash means workflow +// edits re-run the matrix but each cell hits its existing marker and skips. +// +// Caveat: workflow edits that DO change build behavior — `with:` inputs to a +// composite, `runs-on:`, an `env:` var, an action's pinned version — won't be +// caught here. Force-clear caches via the GitHub Actions UI when you make one. +const HASH_EXCLUDE = new Set(['.github/workflows/build-apps.yml']); + const filterName = process.argv[2]; if (!filterName) { console.error('Usage: compute-app-hash.js '); @@ -44,7 +56,9 @@ const lines = cp .split('\n') .filter((line) => { const tabIdx = line.indexOf('\t'); - return tabIdx !== -1 && matchAny(line.slice(tabIdx + 1)); + if (tabIdx === -1) return false; + const path = line.slice(tabIdx + 1); + return !HASH_EXCLUDE.has(path) && matchAny(path); }) .sort();