ci: per-app build matrix with precise path triggers #8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 `<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: | |
| 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: | |
| 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 | |
| - name: Detect changed apps | |
| id: filter | |
| if: github.event_name != 'workflow_dispatch' | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| filters: | | |
| # 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 | |
| - 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}" | |
| # 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 | |
| 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 | |
| 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 | |
| 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-app: &bare-rn-app | |
| - *core-shared | |
| - *bare-fetcher | |
| - *llm-pkg | |
| - 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 matrices | |
| id: matrix | |
| run: | | |
| all='["llm","computer-vision","speech","text-embeddings","bare-rn"]' | |
| 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: 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: 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 }} | |
| 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 | |
| - 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 | |
| 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.android_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' }} | |
| filter-name: ${{ matrix.app }}-android | |
| 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' }} | |
| filter-name: ${{ matrix.app }}-ios |