Skip to content

ci: per-app build matrix with precise path triggers #8

ci: per-app build matrix with precise path triggers

ci: per-app build matrix with precise path triggers #8

Workflow file for this run

# 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