From 2809c3852af2e73b9fae4b95fbccb214ecb5c81f Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Mon, 3 Aug 2026 12:28:25 +0200 Subject: [PATCH 1/2] Upload built applications Turn `android-build.yml` and `ios-build.yml` into reusable workflows driven by new `android.yml` / `ios.yml` entry points, and have them upload the app they built. Builds now run in Release for the simulator/emulator ABI, so the uploaded APK / .app bundle is exactly what a later job can install instead of building the app a second time. --- .github/workflows/android-build.yml | 57 ++++++++------- .github/workflows/android.yml | 38 ++++++++++ .github/workflows/ios-build.yml | 108 +++++++++++++++++++--------- .github/workflows/ios.yml | 44 ++++++++++++ 4 files changed, 190 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/android.yml create mode 100644 .github/workflows/ios.yml diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 590dd55ad9..f65b075cdb 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -1,30 +1,28 @@ -name: Test Android build +name: Build Android app +# Reusable: builds one example app in Release and uploads the APK, so +# `android-e2e.yml` can install that exact APK instead of building the app a +# second time. Called by `android.yml`. on: - pull_request: - paths: - - .github/workflows/android-build.yml - - packages/react-native-gesture-handler/android/** - - apps/basic-example/android/** - - apps/basic-example/package.json - - rnrepo.config.json - - yarn.lock - push: - branches: - - main - workflow_dispatch: + workflow_call: + inputs: + app: + description: Directory under apps/ holding the example app to build. + required: true + type: string + artifact-name: + description: Name to upload the built APK under. + required: true + type: string + abi: + description: ABI to build. Must match the emulator the e2e job installs the APK on. + required: true + type: string jobs: build: - if: github.repository == 'software-mansion/react-native-gesture-handler' - runs-on: ubuntu-latest - strategy: - matrix: - working-directory: [apps/basic-example, apps/expo-example] - concurrency: - group: android-${{ matrix.working-directory }}-${{ github.ref }} - cancel-in-progress: true + timeout-minutes: 60 steps: - name: checkout @@ -43,9 +41,18 @@ jobs: cache: yarn - name: Install node dependencies - working-directory: ${{ matrix.working-directory }} + working-directory: apps/${{ inputs.app }} run: yarn install --immutable - - name: Build app - working-directory: ${{ matrix.working-directory }}/android - run: ./gradlew :app:assembleDebug --console=plain -PreactNativeArchitectures=arm64-v8a + - name: Build app in Release mode + working-directory: apps/${{ inputs.app }}/android + run: ./gradlew :app:assembleRelease --console=plain -PreactNativeArchitectures=${{ inputs.abi }} + + - name: Upload APK artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: apps/${{ inputs.app }}/android/app/build/outputs/apk/release/*.apk + if-no-files-found: error + compression-level: 0 + retention-days: 3 diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000000..16f2006011 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,38 @@ +name: Test Android + +on: + pull_request: + paths: + - .github/workflows/android.yml + - .github/workflows/android-build.yml + - packages/react-native-gesture-handler/android/** + - packages/react-native-gesture-handler/shared/** + - packages/react-native-gesture-handler/src/** + - apps/basic-example/** + - apps/expo-example/** + - apps/common-app/** + - rnrepo.config.json + - yarn.lock + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: android-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + if: github.repository == 'software-mansion/react-native-gesture-handler' + + strategy: + fail-fast: false + matrix: + app: [basic-example, expo-example] + + uses: ./.github/workflows/android-build.yml + with: + app: ${{ matrix.app }} + artifact-name: android-apk-${{ matrix.app }} + abi: x86_64 diff --git a/.github/workflows/ios-build.yml b/.github/workflows/ios-build.yml index 30076035ac..f88fc621aa 100644 --- a/.github/workflows/ios-build.yml +++ b/.github/workflows/ios-build.yml @@ -1,40 +1,50 @@ -name: Test iOS build +name: Build iOS app +# Reusable: builds one example app for the simulator in Release and uploads the +# .app bundle, so `ios-e2e.yml` can install that exact binary instead of +# building the app a second time. Called by `ios.yml`. on: - pull_request: - paths: - - .github/workflows/ios-build.yml - - packages/react-native-gesture-handler/RNGestureHandler.podspec - - packages/react-native-gesture-handler/apple/** - - apps/basic-example/ios/** - - apps/basic-example/package.json - - rnrepo.config.json - - yarn.lock - push: - branches: - - main - workflow_dispatch: + workflow_call: + inputs: + app: + description: Directory under apps/ holding the example app to build. + required: true + type: string + scheme: + description: Xcode scheme of the app (also the workspace name). + required: true + type: string + artifact-name: + description: Name to upload the packaged .app bundle under. + required: true + type: string + xcode-version: + description: Xcode version to build with. + required: true + type: string jobs: build: - if: github.repository == 'software-mansion/react-native-gesture-handler' - runs-on: macos-26 - strategy: - matrix: - working-directory: [apps/basic-example, apps/expo-example] - concurrency: - group: ios-${{ matrix.working-directory }}-${{ github.ref }} - cancel-in-progress: true + timeout-minutes: 60 steps: - name: checkout uses: actions/checkout@v4 - - name: Use latest stable Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '26.4.1' + - name: Select Xcode + env: + XCODE_VERSION: ${{ inputs.xcode-version }} + run: | + XCODE_APP="/Applications/Xcode_${XCODE_VERSION}.app" + if [ ! -d "$XCODE_APP" ]; then + echo "Xcode ${XCODE_VERSION} is not installed on this runner. Available:" >&2 + ls -d /Applications/Xcode*.app >&2 + exit 1 + fi + echo "Using $XCODE_APP" + echo "DEVELOPER_DIR=$XCODE_APP/Contents/Developer" >> "$GITHUB_ENV" + DEVELOPER_DIR="$XCODE_APP/Contents/Developer" xcodebuild -version - name: Use Node.js 24 uses: actions/setup-node@v6 @@ -43,14 +53,48 @@ jobs: cache: yarn - name: Install node dependencies - working-directory: ${{ matrix.working-directory }} + working-directory: apps/${{ inputs.app }} run: yarn install --immutable + # expo-example runs `expo prebuild` on postinstall, which installs its pods. - name: Install pods - if: ${{ matrix.working-directory == 'apps/basic-example' }} - working-directory: ${{ matrix.working-directory }}/ios + if: ${{ inputs.app == 'basic-example' }} + working-directory: apps/${{ inputs.app }}/ios run: bundle install && NO_FLIPPER=1 bundle exec pod install - - name: Build app - working-directory: ${{ matrix.working-directory }} - run: npx react-native run-ios + - name: Build app in Release mode + working-directory: apps/${{ inputs.app }}/ios + run: | + xcodebuild \ + -workspace ${{ inputs.scheme }}.xcworkspace \ + -scheme ${{ inputs.scheme }} \ + -configuration Release \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + -derivedDataPath build \ + CODE_SIGNING_ALLOWED=NO \ + build + + # Artifact zips drop POSIX permissions and symlinks, which would leave the + # .app with a non-executable binary — tar the bundle to keep it installable. + - name: Package app bundle + working-directory: apps/${{ inputs.app }}/ios + run: | + APP_PATH=$(find build/Build/Products/Release-iphonesimulator -maxdepth 1 -name '*.app' -type d | head -1) + if [ -z "$APP_PATH" ]; then + echo "Could not find built .app" >&2 + exit 1 + fi + echo "Packaging $APP_PATH" + mkdir -p "$RUNNER_TEMP/artifact" + tar -czf "$RUNNER_TEMP/artifact/app.tar.gz" \ + -C "$(dirname "$APP_PATH")" "$(basename "$APP_PATH")" + + - name: Upload app artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: ${{ runner.temp }}/artifact/app.tar.gz + if-no-files-found: error + compression-level: 0 + retention-days: 3 diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml new file mode 100644 index 0000000000..50a0d2f101 --- /dev/null +++ b/.github/workflows/ios.yml @@ -0,0 +1,44 @@ +name: Test iOS + +on: + pull_request: + paths: + - .github/workflows/ios.yml + - .github/workflows/ios-build.yml + - packages/react-native-gesture-handler/RNGestureHandler.podspec + - packages/react-native-gesture-handler/apple/** + - packages/react-native-gesture-handler/shared/** + - packages/react-native-gesture-handler/src/** + - apps/basic-example/** + - apps/expo-example/** + - apps/common-app/** + - rnrepo.config.json + - yarn.lock + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: ios-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + if: github.repository == 'software-mansion/react-native-gesture-handler' + + strategy: + fail-fast: false + matrix: + include: + - app: basic-example + scheme: BasicExample + - app: expo-example + scheme: ExpoExample + + uses: ./.github/workflows/ios-build.yml + with: + app: ${{ matrix.app }} + scheme: ${{ matrix.scheme }} + artifact-name: ios-app-${{ matrix.app }} + xcode-version: '26.4.1' From 56ae3e90eee696b49848fabe940408991f7561a1 Mon Sep 17 00:00:00 2001 From: Jakub Piasecki Date: Wed, 5 Aug 2026 10:29:22 +0200 Subject: [PATCH 2/2] Split expo and basic example builds --- .github/workflows/android-basic.yml | 34 ++++++++++++++++++ .github/workflows/android-build.yml | 4 +-- .github/workflows/android.yml | 15 +++----- .github/workflows/ios-basic.yml | 35 +++++++++++++++++++ .github/workflows/ios-build.yml | 3 +- .github/workflows/ios.yml | 21 ++++------- .../workflows/static-example-apps-checks.yml | 2 +- 7 files changed, 83 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/android-basic.yml create mode 100644 .github/workflows/ios-basic.yml diff --git a/.github/workflows/android-basic.yml b/.github/workflows/android-basic.yml new file mode 100644 index 0000000000..e283655989 --- /dev/null +++ b/.github/workflows/android-basic.yml @@ -0,0 +1,34 @@ +name: Build Android (basic-example) + +on: + pull_request: + paths: + - .github/workflows/android-basic.yml + - .github/workflows/android-build.yml + - packages/react-native-gesture-handler/package.json + - packages/react-native-gesture-handler/android/** + - packages/react-native-gesture-handler/shared/** + - packages/react-native-gesture-handler/src/specs/** + - apps/basic-example/** + - '!apps/basic-example/ios/**' + - '!apps/basic-example/Gemfile*' + - rnrepo.config.json + - yarn.lock + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: android-basic-${{ github.ref }} + cancel-in-progress: true + +jobs: + basic-example: + if: github.repository == 'software-mansion/react-native-gesture-handler' + + uses: ./.github/workflows/android-build.yml + with: + app: basic-example + artifact-name: android-apk-basic-example + abi: x86_64 diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index f65b075cdb..78b3d91c5a 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -1,8 +1,6 @@ name: Build Android app -# Reusable: builds one example app in Release and uploads the APK, so -# `android-e2e.yml` can install that exact APK instead of building the app a -# second time. Called by `android.yml`. +# Reusable: builds one example app in Release and uploads the APK. on: workflow_call: inputs: diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 16f2006011..6ad16bb9b2 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,14 +1,14 @@ -name: Test Android +name: Build Android (expo-example) on: pull_request: paths: - .github/workflows/android.yml - .github/workflows/android-build.yml + - packages/react-native-gesture-handler/package.json - packages/react-native-gesture-handler/android/** - packages/react-native-gesture-handler/shared/** - packages/react-native-gesture-handler/src/** - - apps/basic-example/** - apps/expo-example/** - apps/common-app/** - rnrepo.config.json @@ -23,16 +23,11 @@ concurrency: cancel-in-progress: true jobs: - build: + expo-example: if: github.repository == 'software-mansion/react-native-gesture-handler' - strategy: - fail-fast: false - matrix: - app: [basic-example, expo-example] - uses: ./.github/workflows/android-build.yml with: - app: ${{ matrix.app }} - artifact-name: android-apk-${{ matrix.app }} + app: expo-example + artifact-name: android-apk-expo-example abi: x86_64 diff --git a/.github/workflows/ios-basic.yml b/.github/workflows/ios-basic.yml new file mode 100644 index 0000000000..c05c6e6f43 --- /dev/null +++ b/.github/workflows/ios-basic.yml @@ -0,0 +1,35 @@ +name: Build iOS (basic-example) + +on: + pull_request: + paths: + - .github/workflows/ios-basic.yml + - .github/workflows/ios-build.yml + - packages/react-native-gesture-handler/package.json + - packages/react-native-gesture-handler/RNGestureHandler.podspec + - packages/react-native-gesture-handler/apple/** + - packages/react-native-gesture-handler/shared/** + - packages/react-native-gesture-handler/src/specs/** + - apps/basic-example/** + - '!apps/basic-example/android/**' + - rnrepo.config.json + - yarn.lock + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: ios-basic-${{ github.ref }} + cancel-in-progress: true + +jobs: + basic-example: + if: github.repository == 'software-mansion/react-native-gesture-handler' + + uses: ./.github/workflows/ios-build.yml + with: + app: basic-example + scheme: BasicExample + artifact-name: ios-app-basic-example + xcode-version: '26.4.1' diff --git a/.github/workflows/ios-build.yml b/.github/workflows/ios-build.yml index f88fc621aa..ad22f873fb 100644 --- a/.github/workflows/ios-build.yml +++ b/.github/workflows/ios-build.yml @@ -1,8 +1,7 @@ name: Build iOS app # Reusable: builds one example app for the simulator in Release and uploads the -# .app bundle, so `ios-e2e.yml` can install that exact binary instead of -# building the app a second time. Called by `ios.yml`. +# .app bundle. on: workflow_call: inputs: diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index 50a0d2f101..a40c21d9ac 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -1,15 +1,15 @@ -name: Test iOS +name: Build iOS (expo-example) on: pull_request: paths: - .github/workflows/ios.yml - .github/workflows/ios-build.yml + - packages/react-native-gesture-handler/package.json - packages/react-native-gesture-handler/RNGestureHandler.podspec - packages/react-native-gesture-handler/apple/** - packages/react-native-gesture-handler/shared/** - packages/react-native-gesture-handler/src/** - - apps/basic-example/** - apps/expo-example/** - apps/common-app/** - rnrepo.config.json @@ -24,21 +24,12 @@ concurrency: cancel-in-progress: true jobs: - build: + expo-example: if: github.repository == 'software-mansion/react-native-gesture-handler' - strategy: - fail-fast: false - matrix: - include: - - app: basic-example - scheme: BasicExample - - app: expo-example - scheme: ExpoExample - uses: ./.github/workflows/ios-build.yml with: - app: ${{ matrix.app }} - scheme: ${{ matrix.scheme }} - artifact-name: ios-app-${{ matrix.app }} + app: expo-example + scheme: ExpoExample + artifact-name: ios-app-expo-example xcode-version: '26.4.1' diff --git a/.github/workflows/static-example-apps-checks.yml b/.github/workflows/static-example-apps-checks.yml index a454c914cc..92e4d572a4 100644 --- a/.github/workflows/static-example-apps-checks.yml +++ b/.github/workflows/static-example-apps-checks.yml @@ -3,7 +3,7 @@ name: Test TypeScript and Lint on: pull_request: paths: - - apps/basic-example/**' + - apps/basic-example/** - apps/common-app/** push: branches: