From ee3f49c4a93b4620901a09f7701e5fd00e872121 Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Mon, 29 Jun 2026 16:47:52 +0100 Subject: [PATCH 1/2] Build React Native iOS E2E IPA artifact Assisted-By: devx/6c1e3ad5-96c8-4972-b087-da7ff7b195c3 --- e2e/BITRISE.md | 16 ++++++++++++ e2e/bitrise.yml | 66 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/e2e/BITRISE.md b/e2e/BITRISE.md index 5cb95b42..1f48fdde 100644 --- a/e2e/BITRISE.md +++ b/e2e/BITRISE.md @@ -64,6 +64,22 @@ These secrets are configured in Bitrise.io; they cannot live in the repository. | `STOREFRONT_DOMAIN` | Storefront domain for sample app builds. | | `STOREFRONT_ACCESS_TOKEN` | Storefront access token for sample app builds. | +## Code signing + +React Native iOS IPA generation uses Bitrise's certificate and profile installer before running `xcodebuild archive` and `xcodebuild -exportArchive`. + +The React Native iOS artifact workflow overrides the default Linux stack and runs on `osx-xcode-26.2.x` with `g2.mac.4large`, because it requires Xcode and iOS signing. Its cache keys are prefixed with `rn-ios-macos-` so macOS caches cannot restore Linux-built dependencies. + +Upload the signing certificate and provisioning profile for the React Native sample app to the Bitrise app; the iOS artifact workflow installs them before archiving. The iOS build reads the following signing values with the defaults shown, and each can be overridden with a matching Bitrise environment variable: + +| Variable | Default | Purpose | +|---|---|---| +| `E2E_IOS_EXPORT_METHOD` | `development` | Export method for the React Native iOS IPA. | +| `E2E_IOS_BUNDLE_ID` | `com.shopify.checkoutkit.reactnativedemo` | Bundle identifier used for iOS archive and export signing. | +| `E2E_IOS_DEVELOPMENT_TEAM` | `A7XGC83MZE` | Apple development team used for iOS archive signing. | +| `E2E_IOS_CODE_SIGN_IDENTITY` | `Apple Development` | Code signing identity used for iOS archive and export signing. | +| `E2E_IOS_PROVISIONING_PROFILE_SPECIFIER` | `bitrise-checkout-kit-e2e` | Provisioning profile specifier installed by Bitrise and passed to `xcodebuild`; override it if the Bitrise-installed profile uses a different name. | + ## Caching React Native Android E2E builds use the released native Maven artifact versions declared by the React Native sample and module configuration. Do not pass the React Native `--local` flag or set local native SDK override environment variables for these builds. diff --git a/e2e/bitrise.yml b/e2e/bitrise.yml index 765f68ce..536b0f1a 100644 --- a/e2e/bitrise.yml +++ b/e2e/bitrise.yml @@ -59,6 +59,40 @@ step_bundles: inputs: - key: *rn_node_modules_cache_key - paths: platforms/react-native/node_modules + install-ruby-gems: + steps: + - restore-cache@3: + inputs: + - key: &rn_ruby_gems_cache_key |- + rn-ruby-gems-{{ .OS }}-{{ .Arch }}-ruby-3.4.8-{{ checksum "platforms/react-native/sample/Gemfile.lock" }} + - script@1: + title: Install iOS Ruby dependencies + inputs: + - content: |- + set -euo pipefail + cd platforms/react-native/sample + bundle install + - save-cache@1: + inputs: + - key: *rn_ruby_gems_cache_key + - paths: platforms/react-native/sample/vendor/bundle + install-cocoapods: + steps: + - restore-cache@3: + inputs: + - key: &rn_cocoapods_cache_key |- + rn-cocoapods-{{ .OS }}-{{ .Arch }}-{{ checksum "platforms/react-native/sample/ios/Podfile.lock" }} + - script@1: + title: Install CocoaPods dependencies + inputs: + - content: |- + set -euo pipefail + cd platforms/react-native/sample/ios + bundle exec pod install --deployment + - save-cache@1: + inputs: + - key: *rn_cocoapods_cache_key + - paths: platforms/react-native/sample/ios/Pods workflows: e2e-produce-browserstack-run-plan: @@ -93,31 +127,33 @@ workflows: $BITRISE_DEPLOY_DIR/browserstack-run-plan.json:E2E_BROWSERSTACK_RUN_PLAN_JSON e2e-build-react-native-ios: + meta: + bitrise.io: + # Keep in sync with dev.yml's xcode.version (26.2). Bitrise stable stacks + # only receive Xcode patch updates, so this pins Xcode 26.2 / iOS 26.2 + # (23C54) to match local development. + stack: osx-xcode-26.2.x + machine_type_id: g2.mac.4large steps: - git-clone@8: {} - - restore-cache@3: - inputs: - - key: |- - rn-ios-{{ .OS }}-{{ .Arch }}-{{ checksum "platforms/react-native/pnpm-lock.yaml" }}-{{ checksum "platforms/react-native/sample/ios/Podfile.lock" }} + - certificate-and-profile-installer@1: {} + - bundle::install-node-modules: {} + - bundle::install-ruby-gems: {} + - bundle::install-cocoapods: {} - script@1: - title: Create React Native iOS artifact placeholder + title: Build React Native iOS IPA artifact + timeout: 5400 + no_output_timeout: 5400 inputs: - content: |- set -euo pipefail - mkdir -p "$BITRISE_DEPLOY_DIR/e2e" - echo "Phase 2 placeholder for React Native iOS IPA" > "$BITRISE_DEPLOY_DIR/e2e/react-native-ios.ipa" - envman add --key E2E_REACT_NATIVE_IOS_APP_PATH --value "$BITRISE_DEPLOY_DIR/e2e/react-native-ios.ipa" + e2e/scripts/build_react_native_ios - deploy-to-bitrise-io@2: + is_always_run: false inputs: - pipeline_intermediate_files: |- $E2E_REACT_NATIVE_IOS_APP_PATH:E2E_REACT_NATIVE_IOS_APP_PATH - - save-cache@1: - inputs: - - key: |- - rn-ios-{{ .OS }}-{{ .Arch }}-{{ checksum "platforms/react-native/pnpm-lock.yaml" }}-{{ checksum "platforms/react-native/sample/ios/Podfile.lock" }} - - paths: |- - platforms/react-native/node_modules - platforms/react-native/sample/ios/Pods + $E2E_REACT_NATIVE_IOS_SIMULATOR_APP_PATH:E2E_REACT_NATIVE_IOS_SIMULATOR_APP_PATH e2e-build-react-native-android: meta: From 1ae9b71fb4d4c62971d750931fac504d9732477a Mon Sep 17 00:00:00 2001 From: Kieran Osgood Date: Thu, 2 Jul 2026 11:56:18 +0100 Subject: [PATCH 2/2] Fix iOS E2E Ruby cache key Assisted-By: devx/9ae89f69-61a5-46e3-8faf-006d105b0cdd --- e2e/BITRISE.md | 2 - e2e/bitrise.yml | 10 +- e2e/scripts/bitrise_ci_helpers | 6 ++ e2e/scripts/build_react_native_ios | 152 +++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 7 deletions(-) create mode 100755 e2e/scripts/build_react_native_ios diff --git a/e2e/BITRISE.md b/e2e/BITRISE.md index 1f48fdde..c56025c9 100644 --- a/e2e/BITRISE.md +++ b/e2e/BITRISE.md @@ -68,8 +68,6 @@ These secrets are configured in Bitrise.io; they cannot live in the repository. React Native iOS IPA generation uses Bitrise's certificate and profile installer before running `xcodebuild archive` and `xcodebuild -exportArchive`. -The React Native iOS artifact workflow overrides the default Linux stack and runs on `osx-xcode-26.2.x` with `g2.mac.4large`, because it requires Xcode and iOS signing. Its cache keys are prefixed with `rn-ios-macos-` so macOS caches cannot restore Linux-built dependencies. - Upload the signing certificate and provisioning profile for the React Native sample app to the Bitrise app; the iOS artifact workflow installs them before archiving. The iOS build reads the following signing values with the defaults shown, and each can be overridden with a matching Bitrise environment variable: | Variable | Default | Purpose | diff --git a/e2e/bitrise.yml b/e2e/bitrise.yml index 536b0f1a..633745b4 100644 --- a/e2e/bitrise.yml +++ b/e2e/bitrise.yml @@ -64,7 +64,7 @@ step_bundles: - restore-cache@3: inputs: - key: &rn_ruby_gems_cache_key |- - rn-ruby-gems-{{ .OS }}-{{ .Arch }}-ruby-3.4.8-{{ checksum "platforms/react-native/sample/Gemfile.lock" }} + rn-ruby-gems-{{ .OS }}-{{ .Arch }}-{{ checksum "platforms/react-native/sample/Gemfile.lock" }} - script@1: title: Install iOS Ruby dependencies inputs: @@ -101,7 +101,7 @@ workflows: - script@1: title: Produce BrowserStack run plan placeholder timeout: 1800 - no_output_timeout: 1800 + no_output_timeout: 900 inputs: - content: |- set -euo pipefail @@ -143,7 +143,7 @@ workflows: - script@1: title: Build React Native iOS IPA artifact timeout: 5400 - no_output_timeout: 5400 + no_output_timeout: 1800 inputs: - content: |- set -euo pipefail @@ -170,7 +170,7 @@ workflows: - script@1: title: Build React Native Android APK artifact timeout: 3600 - no_output_timeout: 3600 + no_output_timeout: 1800 inputs: - content: |- set -euo pipefail @@ -197,7 +197,7 @@ workflows: - script@1: title: Resolve BrowserStack run plan row placeholder timeout: 2700 - no_output_timeout: 2700 + no_output_timeout: 1350 inputs: - content: |- set -euo pipefail diff --git a/e2e/scripts/bitrise_ci_helpers b/e2e/scripts/bitrise_ci_helpers index 337bb953..2b274578 100755 --- a/e2e/scripts/bitrise_ci_helpers +++ b/e2e/scripts/bitrise_ci_helpers @@ -11,6 +11,12 @@ e2e_enable_corepack() { corepack enable } +e2e_deploy_dir() { + local dir="$BITRISE_DEPLOY_DIR/e2e" + mkdir -p "$dir" + printf '%s\n' "$dir" +} + e2e_configure_storefront() { e2e_log "Checking storefront configuration secrets" : "${STOREFRONT_DOMAIN:?STOREFRONT_DOMAIN is required. Check https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76/workflow_editor#!/secrets and enable Expose for pull requests.}" diff --git a/e2e/scripts/build_react_native_ios b/e2e/scripts/build_react_native_ios new file mode 100755 index 00000000..931edf80 --- /dev/null +++ b/e2e/scripts/build_react_native_ios @@ -0,0 +1,152 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$script_dir/bitrise_ci_helpers" + +e2e_configure_storefront + +cd platforms/react-native/sample/ios + +app_name="CheckoutKitReactNativeDemo" +workspace="${app_name}.xcworkspace" +scheme="$app_name" + +e2e_dir="$(e2e_deploy_dir)" +archive_path="$e2e_dir/${app_name}.xcarchive" +export_path="$e2e_dir/react-native-ios-export" +export_options="$e2e_dir/ExportOptions.plist" +provisioned_ipa="$export_path/${app_name}-Provisioned.ipa" +simulator_derived_data="$PWD/build/e2e-simulator" +simulator_app_zip="$e2e_dir/${app_name}-Simulator.zip" + +export_method="${E2E_IOS_EXPORT_METHOD:-development}" +ios_bundle_id="${E2E_IOS_BUNDLE_ID:-com.shopify.checkoutkit.reactnativedemo}" +ios_development_team="${E2E_IOS_DEVELOPMENT_TEAM:-A7XGC83MZE}" +ios_code_sign_identity="${E2E_IOS_CODE_SIGN_IDENTITY:-Apple Development}" +ios_profile_specifier="${E2E_IOS_PROVISIONING_PROFILE_SPECIFIER:-bitrise-checkout-kit-e2e}" + +xcodebuild_common_args=( + -workspace "$workspace" + -scheme "$scheme" + -configuration Release + -skipPackagePluginValidation +) + +# Builds the device archive that export_device_ipa packages into the provisioned IPA. +archive_device_app() { + xcodebuild archive \ + "${xcodebuild_common_args[@]}" \ + -disableAutomaticPackageResolution \ + -sdk iphoneos \ + -destination generic/platform=iOS \ + -archivePath "$archive_path" \ + "CODE_SIGN_STYLE=Manual" \ + "DEVELOPMENT_TEAM=$ios_development_team" \ + "CODE_SIGN_IDENTITY=$ios_code_sign_identity" \ + "PROVISIONING_PROFILE_SPECIFIER=$ios_profile_specifier" +} + +# Packages the device archive into the signed IPA uploaded to Bitrise. +export_device_ipa() { + xcodebuild -exportArchive \ + -archivePath "$archive_path" \ + -exportPath "$export_path" \ + -exportOptionsPlist "$export_options" +} + +# Builds the simulator app bundle that is zipped for BrowserStack. +build_simulator_app() { + xcodebuild build \ + "${xcodebuild_common_args[@]}" \ + -disableAutomaticPackageResolution \ + -sdk iphonesimulator \ + -destination 'generic/platform=iOS Simulator' \ + -derivedDataPath "$simulator_derived_data" \ + ARCHS=arm64 \ + ONLY_ACTIVE_ARCH=NO \ + CODE_SIGNING_ALLOWED=NO +} + +cat > "$export_options" < + + + + method + $export_method + signingStyle + manual + teamID + $ios_development_team + signingCertificate + $ios_code_sign_identity + provisioningProfiles + + $ios_bundle_id + $ios_profile_specifier + + stripSwiftSymbols + + compileBitcode + + + +PLIST + +e2e_log "Archiving React Native iOS app" +if ! archive_device_app; then + e2e_log "iOS archive failed" + echo "Check Bitrise code signing assets for bundle id ${ios_bundle_id} and team ${ios_development_team}." >&2 + echo "Expected provisioning profile specifier: ${ios_profile_specifier}." >&2 + echo "Upload an Apple Development certificate and matching development provisioning profile at https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76/workflow_editor#!/code_signing." >&2 + echo "The e2e-build-react-native-ios workflow runs certificate-and-profile-installer before xcodebuild." >&2 + exit 1 +fi + +e2e_log "Exporting React Native iOS IPA" +if ! export_device_ipa; then + e2e_log "iOS IPA export failed" + echo "Check that the installed provisioning profile matches export method ${export_method}." >&2 + echo "Export options plist: ${export_options}" >&2 + exit 1 +fi + +ios_ipa="$(find "$export_path" -name "*.ipa" -print -quit)" +if [ -z "$ios_ipa" ] || [ ! -f "$ios_ipa" ]; then + e2e_log "iOS IPA export did not create an IPA" + find "$export_path" -maxdepth 3 -print >&2 || true + exit 1 +fi +mv "$ios_ipa" "$provisioned_ipa" + +e2e_log "Publishing iOS IPA path" +envman add --key E2E_REACT_NATIVE_IOS_APP_PATH --value "$provisioned_ipa" + +e2e_log "Building React Native iOS simulator app" +# Restrict the simulator build to arm64: react-native-quick-crypto@1.0.9 +# ships a libQuickCrypto.a whose blake3 dispatch references Intel SIMD +# symbols (avx512/avx2/sse41/sse2) that are not compiled for x86_64, so +# the x86_64 slice fails to link. CI/BrowserStack simulators are Apple +# Silicon, so arm64 is sufficient. +# TODO: Remove ARCHS/ONLY_ACTIVE_ARCH override once the Expo prebuild +# sample app lands (https://github.com/Shopify/checkout-kit/pull/297). +if ! build_simulator_app; then + e2e_log "iOS simulator build failed" + exit 1 +fi + +simulator_app="$(find "$simulator_derived_data/Build/Products" -maxdepth 2 -name "${app_name}.app" -print -quit)" +if [ -z "$simulator_app" ] || [ ! -d "$simulator_app" ]; then + e2e_log "iOS simulator build did not create an app bundle" + find "$simulator_derived_data/Build/Products" -maxdepth 2 -print >&2 || true + exit 1 +fi + +e2e_log "Packaging iOS simulator app" +rm -f "$simulator_app_zip" +ditto -c -k --keepParent "$simulator_app" "$simulator_app_zip" + +e2e_log "Publishing iOS simulator app path" +envman add --key E2E_REACT_NATIVE_IOS_SIMULATOR_APP_PATH --value "$simulator_app_zip"