From 827a4a9e1a667515523e52b30ef4c9f9e4bd62f1 Mon Sep 17 00:00:00 2001 From: Christian Falch Date: Mon, 2 Nov 2026 07:58:41 +0100 Subject: [PATCH 1/2] Build the iOS test apps with SwiftPM in CI The iOS CI jobs that build RNTester, HelloWorld and the template app used by the Maestro end-to-end tests still built them with CocoaPods. SwiftPM is becoming the default way to build React Native apps on iOS, so these jobs now build with SwiftPM. - HelloWorld is converted to SwiftPM and checked in. Its Podfile, workspace and Ruby files are removed. `yarn bootstrap ios` and `yarn build ios` use SwiftPM. - RNTester stays a CocoaPods project in git, so developers keep building React Native from source. CI converts it to SwiftPM before each build. The app uploaded for the Maestro tests keeps its name. - The template app is converted to SwiftPM in its build job. - A new action, setup-spm-artifacts, gives every job the prebuilt frameworks from the same CI run. - test_ios_rntester now blocks a merge. - The CocoaPods-only jobs (Ruby 3.2, dynamic frameworks, use_frameworks) and the separate SwiftPM workflows for RNTester and HelloWorld are removed. Verify locally: `yarn bootstrap ios && yarn build ios` in private/helloworld prints BUILD SUCCEEDED. --- .../actions/setup-spm-artifacts/action.yml | 57 +++++ .../actions/test-ios-helloworld/action.yml | 45 +--- .github/actions/test-ios-rntester/action.yml | 150 ++++-------- .github/workflows/e2e-ios-rntester.yml | 4 +- .github/workflows/e2e-ios-templateapp.yml | 47 ++-- .github/workflows/test-all.yml | 85 ------- .github/workflows/test-ios-spm-helloworld.yml | 137 ----------- .github/workflows/test-ios-spm-newapp.yml | 75 +----- .github/workflows/test-ios-spm-rntester.yml | 137 ----------- .gitignore | 1 - private/core-cli-utils/README.md | 6 +- private/core-cli-utils/src/private/apple.js | 90 +++++-- private/helloworld/.bundle/config | 2 - private/helloworld/.gitignore | 4 - private/helloworld/.react-native.config | 4 +- private/helloworld/Gemfile | 26 -- private/helloworld/Gemfile.lock | 117 --------- private/helloworld/cli.flow.js | 86 ++----- private/helloworld/ios/.gitignore | 6 + .../HelloWorld.xcodeproj/.spm-injected.json | 69 ++++++ .../ios/HelloWorld.xcodeproj/project.pbxproj | 190 ++++++--------- .../xcschemes/HelloWorld.xcscheme | 19 ++ .../contents.xcworkspacedata | 10 - private/helloworld/ios/Podfile | 38 --- .../ReactNativeSPM/ReactNativeSPM.xcconfig | 68 ++++++ .../Scripts/embed-flavored-frameworks.sh | 43 ++++ .../Scripts/sync-autolinking.sh | 226 ++++++++++++++++++ private/helloworld/lib/filesystem.js | 14 -- private/helloworld/lib/ios.js | 13 +- private/helloworld/lib/xcode.js | 2 +- 30 files changed, 745 insertions(+), 1026 deletions(-) create mode 100644 .github/actions/setup-spm-artifacts/action.yml delete mode 100644 .github/workflows/test-ios-spm-helloworld.yml delete mode 100644 .github/workflows/test-ios-spm-rntester.yml delete mode 100644 private/helloworld/.bundle/config delete mode 100644 private/helloworld/Gemfile delete mode 100644 private/helloworld/Gemfile.lock create mode 100644 private/helloworld/ios/.gitignore create mode 100644 private/helloworld/ios/HelloWorld.xcodeproj/.spm-injected.json delete mode 100644 private/helloworld/ios/HelloWorld.xcworkspace/contents.xcworkspacedata delete mode 100644 private/helloworld/ios/Podfile create mode 100644 private/helloworld/ios/ReactNativeSPM/ReactNativeSPM.xcconfig create mode 100644 private/helloworld/ios/ReactNativeSPM/Scripts/embed-flavored-frameworks.sh create mode 100644 private/helloworld/ios/ReactNativeSPM/Scripts/sync-autolinking.sh diff --git a/.github/actions/setup-spm-artifacts/action.yml b/.github/actions/setup-spm-artifacts/action.yml new file mode 100644 index 000000000000..059cf03b4e5a --- /dev/null +++ b/.github/actions/setup-spm-artifacts/action.yml @@ -0,0 +1,57 @@ +name: setup-spm-artifacts +description: >- + Downloads the ReactCore and ReactNativeDependencies XCFrameworks built in this + run, in both flavors, into the SwiftPM artifact root /tmp/spm-artifacts. Pass + `--artifacts /tmp/spm-artifacts --download skip` to the `react-native spm` + command that follows. +inputs: + working-directory: + description: The app directory that `npx react-native spm download` runs from. + required: true +runs: + using: composite + steps: + # Both flavors are needed by every build, not just the one it builds: + # `spm` stages both flavor framework trees and lets a per-configuration + # build setting pick one at build time, so it validates the debug/ and + # release/ artifact slots together (and `--download skip` refuses an + # incomplete slot). Please don't "optimise" this down to one flavor. + - name: Download ReactCore (Debug) + uses: ./.github/actions/download-artifact + with: + name: ReactCoreDebug.xcframework.tar.gz + path: /tmp/rc-debug + - name: Download ReactCore (Release) + uses: ./.github/actions/download-artifact + with: + name: ReactCoreRelease.xcframework.tar.gz + path: /tmp/rc-release + - name: Download ReactNativeDependencies (Debug) + uses: ./.github/actions/download-artifact + with: + name: ReactNativeDependenciesDebug.xcframework.tar.gz + path: /tmp/deps-debug + - name: Download ReactNativeDependencies (Release) + uses: ./.github/actions/download-artifact + with: + name: ReactNativeDependenciesRelease.xcframework.tar.gz + path: /tmp/deps-release + # The ordinary `spm download`, pointed at the XCFrameworks this run just + # built rather than at the published nightly (RN_CORE_TARBALL_PATH / + # RN_DEPS_TARBALL_PATH are overrides download-spm-artifacts.js already + # supports). It fills both flavor slots per call and skips any slot that + # already validates, so it runs twice: the first pass fills both slots + # from the Release tarballs, then debug/ is dropped and refilled from the + # Debug tarballs while release/ is left alone. hermes-engine is fetched + # from Maven by the command itself. + - name: Stage the XCFrameworks for SwiftPM (Debug + Release) + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + RN_CORE_TARBALL_PATH=/tmp/rc-release/ReactCoreRelease.xcframework.tar.gz \ + RN_DEPS_TARBALL_PATH=/tmp/deps-release/ReactNativeDependenciesRelease.xcframework.tar.gz \ + npx react-native spm download --artifacts /tmp/spm-artifacts + rm -rf /tmp/spm-artifacts/debug + RN_CORE_TARBALL_PATH=/tmp/rc-debug/ReactCoreDebug.xcframework.tar.gz \ + RN_DEPS_TARBALL_PATH=/tmp/deps-debug/ReactNativeDependenciesDebug.xcframework.tar.gz \ + npx react-native spm download --artifacts /tmp/spm-artifacts diff --git a/.github/actions/test-ios-helloworld/action.yml b/.github/actions/test-ios-helloworld/action.yml index 639249c0cca8..2b8d28accf09 100644 --- a/.github/actions/test-ios-helloworld/action.yml +++ b/.github/actions/test-ios-helloworld/action.yml @@ -1,12 +1,6 @@ name: test-ios-helloworld description: Test iOS Hello World inputs: - use-frameworks: - description: Whether to build with dynamic frameworks. - default: false - ruby-version: - description: The version of ruby that must be used - default: 2.6.10 flavor: description: The flavor of the build. Must be one of "Debug", "Release". default: Debug @@ -19,46 +13,21 @@ runs: uses: ./.github/actions/setup-node - name: Run yarn install uses: ./.github/actions/yarn-install - - name: Setup ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ inputs.ruby-version }} - name: Set Hermes prebuilt version shell: bash run: | node ./scripts/releases/use-hermes-prebuilt.js - name: Run yarn install again, with the correct hermes version uses: ./.github/actions/yarn-install - - name: Download ReactNativeDependencies - uses: ./.github/actions/download-artifact - with: - name: ReactNativeDependencies${{ inputs.flavor }}.xcframework.tar.gz - path: /tmp/third-party - - name: Print third-party folder - shell: bash - run: ls -lR /tmp/third-party - - name: Download React Native Prebuilds - uses: ./.github/actions/download-artifact + - name: Download the XCFrameworks built in this run + uses: ./.github/actions/setup-spm-artifacts with: - name: ReactCore${{ inputs.flavor }}.xcframework.tar.gz - path: /tmp/ReactCore - - name: Print ReactCore folder + working-directory: private/helloworld/ios + # RN_STRICT_API=1 keeps the C++ stable API enforced, as `yarn bootstrap ios` does. + - name: Set up the Swift packages shell: bash - run: ls -lR /tmp/ReactCore - - name: Install iOS dependencies - Configuration ${{ inputs.flavor }}; - shell: bash - run: | - cd private/helloworld - args=() - - if [[ "${{ inputs.use-frameworks }}" == "true" ]]; then - args+=(--frameworks dynamic) - fi - - export RCT_USE_LOCAL_RN_DEP="/tmp/third-party/ReactNativeDependencies${{ inputs.flavor }}.xcframework.tar.gz" - export RCT_TESTONLY_RNCORE_TARBALL_PATH="/tmp/ReactCore/ReactCore${{ inputs.flavor }}.xcframework.tar.gz" - - yarn bootstrap ios "${args[@]}" | cat + working-directory: private/helloworld/ios + run: RN_STRICT_API=1 npx react-native spm update --artifacts /tmp/spm-artifacts --download skip - name: Run Helloworld tests shell: bash diff --git a/.github/actions/test-ios-rntester/action.yml b/.github/actions/test-ios-rntester/action.yml index 22382fb7b943..5e1725abe0ff 100644 --- a/.github/actions/test-ios-rntester/action.yml +++ b/.github/actions/test-ios-rntester/action.yml @@ -1,9 +1,6 @@ name: test-ios-rntester description: Test iOS RNTester inputs: - ruby-version: - description: The version of ruby that must be used - default: 2.6.10 run-unit-tests: description: whether unit tests should run or not. default: false @@ -14,19 +11,6 @@ inputs: description: Whether we want to run E2E tests or not required: false default: false - use-frameworks: - description: Whether we have to build with Dynamic Frameworks. - required: false - default: false - use-prebuilds: - description: >- - Whether to consume the prebuilt ReactCore/ReactNativeDependencies - artifacts. 'auto' (default) keeps the historical coupling: prebuilds for - static, source for dynamic frameworks. Pass 'true' with - use-frameworks:true for the prebuilt + dynamic-frameworks lane (the - config of the 2026-07-03 SocketRocket dual-copy regression). - required: false - default: auto runs: using: composite @@ -37,31 +21,6 @@ runs: uses: ./.github/actions/setup-node - name: Run yarn uses: ./.github/actions/yarn-install - - name: Setup ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ inputs.ruby-version }} - bundler: none - - name: Install Bundler - shell: bash - run: | - BUNDLER_VERSION="$(awk '/BUNDLED WITH/{getline; print $1}' Gemfile.lock)" - if [ -z "$BUNDLER_VERSION" ]; then - echo "Could not determine Bundler version from Gemfile.lock." >&2 - exit 1 - fi - for attempt in 1 2 3; do - echo "Attempt $attempt of 3" - if gem install bundler --version "$BUNDLER_VERSION" --no-document; then - exit 0 - fi - if [ "$attempt" -lt 3 ]; then - echo "Attempt $attempt failed. Retrying..." - sleep "$((attempt * 10))" - fi - done - echo "All 3 attempts failed." - exit 1 - name: Set Hermes prebuilt version shell: bash run: | @@ -71,77 +30,73 @@ runs: - name: Prepare IOS Tests if: ${{ inputs.run-unit-tests == 'true' }} uses: ./.github/actions/prepare-ios-tests - - name: Resolve prebuilds mode - id: prebuilds - shell: bash - run: | - if [[ "${{ inputs.use-prebuilds }}" == "auto" ]]; then - # Historical coupling: prebuilds for static, source for dynamic frameworks. - if [[ "${{ inputs.use-frameworks }}" == "true" ]]; then - echo "enabled=false" >> "$GITHUB_OUTPUT" - else - echo "enabled=true" >> "$GITHUB_OUTPUT" - fi - elif [[ "${{ inputs.use-prebuilds }}" == "true" ]]; then - echo "enabled=true" >> "$GITHUB_OUTPUT" - elif [[ "${{ inputs.use-prebuilds }}" == "false" ]]; then - echo "enabled=false" >> "$GITHUB_OUTPUT" - else - # Don't silently treat a typo as 'disabled' — surface it. - echo "::warning::Unexpected use-prebuilds value '${{ inputs.use-prebuilds }}' (expected auto/true/false); treating as disabled." - echo "enabled=false" >> "$GITHUB_OUTPUT" - fi - - name: Download ReactNativeDependencies - if: ${{ steps.prebuilds.outputs.enabled == 'true' }} - uses: ./.github/actions/download-artifact + - name: Download the XCFrameworks built in this run + uses: ./.github/actions/setup-spm-artifacts with: - name: ReactNativeDependencies${{ inputs.flavor }}.xcframework.tar.gz - path: /tmp/third-party/ - - name: Print third-party folder - if: ${{ steps.prebuilds.outputs.enabled == 'true' }} + working-directory: packages/rn-tester + # RNTester is a CocoaPods project in git, so developers can build React + # Native from source. CI converts it to SwiftPM on every run. + - name: Ensure CocoaPods (`spm add --deintegrate` shells out to `pod`) shell: bash - run: ls -lR /tmp/third-party - - name: Download React Native Prebuilds - if: ${{ steps.prebuilds.outputs.enabled == 'true' }} - uses: ./.github/actions/download-artifact - with: - name: ReactCore${{ inputs.flavor }}.xcframework.tar.gz - path: /tmp/ReactCore - - name: Print ReactCore folder - if: ${{ steps.prebuilds.outputs.enabled == 'true' }} + run: pod --version || sudo gem install cocoapods --no-document + # react-native-test-library ships only a podspec; scaffold writes its + # Package.swift. + - name: Scaffold Package.swift manifests for community dependencies + shell: bash + working-directory: packages/rn-tester + run: npx react-native spm scaffold --artifacts /tmp/spm-artifacts --download skip || true + - name: Convert RNTester to SwiftPM shell: bash - run: ls -lR /tmp/ReactCore - - name: Install CocoaPods dependencies + working-directory: packages/rn-tester + run: | + # pod deintegrate fails on non-ASCII strings without a UTF-8 locale. + LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 RN_STRICT_API=1 \ + npx react-native spm add --deintegrate --artifacts /tmp/spm-artifacts --download skip + - name: Assert RNTester is on SwiftPM shell: bash + working-directory: packages/rn-tester run: | - if [[ ${{ inputs.use-frameworks }} == "true" ]]; then - export USE_FRAMEWORKS=dynamic + if [[ ! -f RNTesterPods.xcodeproj/.spm-injected.json ]]; then + echo "::error::spm add did not inject SwiftPM: RNTesterPods.xcodeproj/.spm-injected.json is missing" + exit 1 fi - if [[ "${{ steps.prebuilds.outputs.enabled }}" == "true" ]]; then - export RCT_USE_LOCAL_RN_DEP="/tmp/third-party/ReactNativeDependencies${{ inputs.flavor }}.xcframework.tar.gz" - export RCT_TESTONLY_RNCORE_TARBALL_PATH="/tmp/ReactCore/ReactCore${{ inputs.flavor }}.xcframework.tar.gz" + if [[ -f Podfile ]] && grep -q 'use_react_native!' Podfile; then + echo "::error::spm add --deintegrate left use_react_native! in the Podfile" + exit 1 fi - - cd packages/rn-tester - - bundle install - bundle exec pod update hermes-engine --no-repo-update + echo "SwiftPM injected in place; Podfile de-integrated." - name: Build RNTester shell: bash run: | xcodebuild \ -scheme "RNTester" \ - -workspace packages/rn-tester/RNTesterPods.xcworkspace \ + -project packages/rn-tester/RNTesterPods.xcodeproj \ -configuration "${{ inputs.flavor }}" \ -sdk "iphonesimulator" \ -destination "generic/platform=iOS Simulator" \ -derivedDataPath "/tmp/RNTesterBuild" - echo "Print path to *.app file" - APP_PATH=$(find "/tmp/RNTesterBuild" -type d -name "*.app") - - echo "App found at $APP_PATH" - echo "app-path=$APP_PATH" >> $GITHUB_ENV + APP_PATH="/tmp/RNTesterBuild/Build/Products/${{ inputs.flavor }}-iphonesimulator/RNTester.app" + echo "App found at $APP_PATH" + echo "app-path=$APP_PATH" >> $GITHUB_ENV + - name: Check the embedded React.framework flavor + shell: bash + run: | + BINARY="${{ env.app-path }}/Frameworks/React.framework/React" + if [[ ! -f "$BINARY" ]]; then + echo "::error::No embedded React.framework binary at $BINARY" + exit 1 + fi + COUNT=$(nm "$BINARY" | grep -c getDebugProps || true) + echo "getDebugProps symbols in $BINARY: $COUNT" + if [[ "${{ inputs.flavor }}" == 'Debug' && "$COUNT" -eq 0 ]]; then + echo "::error::Debug build embeds a Release React.framework (expected getDebugProps symbols, found none)" + exit 1 + fi + if [[ "${{ inputs.flavor }}" == 'Release' && "$COUNT" -ne 0 ]]; then + echo "::error::Release build embeds a Debug React.framework ($COUNT getDebugProps symbols, expected none)" + exit 1 + fi - name: 'Run Tests: iOS Unit and Integration Tests' if: ${{ inputs.run-unit-tests == 'true' }} shell: bash @@ -158,10 +113,9 @@ runs: uses: ./.github/actions/upload-artifact if: ${{ inputs.run-unit-tests == 'true' }} with: - name: xcresults-${{ inputs.flavor }}-${{ inputs.use-frameworks == 'true' && 'dynamic-frameworks' || 'static-libraries' }}-Ruby${{ inputs.ruby-version }} + name: xcresults-${{ inputs.flavor }} path: /Users/runner/Library/Developer/Xcode/xcresults.tar.gz - name: Upload RNTester App - if: ${{ inputs.use-frameworks == 'false' && inputs.ruby-version == '2.6.10' }} # This is needed to avoid conflicts with the artifacts uses: ./.github/actions/upload-artifact with: name: RNTesterApp-NewArch-${{ inputs.flavor }} @@ -170,5 +124,5 @@ runs: if: ${{ inputs.run-unit-tests == 'true' }} uses: ./.github/actions/upload-artifact with: - name: test-results-${{ inputs.flavor }}-${{ inputs.use-frameworks == 'true' && 'dynamic-frameworks' || 'static-libraries' }}-Ruby${{ inputs.ruby-version }} + name: test-results-${{ inputs.flavor }} path: ./reports/junit diff --git a/.github/workflows/e2e-ios-rntester.yml b/.github/workflows/e2e-ios-rntester.yml index 9f5fd3324e1d..c884c73e9581 100644 --- a/.github/workflows/e2e-ios-rntester.yml +++ b/.github/workflows/e2e-ios-rntester.yml @@ -112,9 +112,7 @@ jobs: if [[ "${{ steps.test-state.outputs.should-run }}" == "false" || "${{ steps.run-tests.outcome }}" == "success" ]]; then RESULT=passed elif [[ "${{ steps.download-app.outcome }}" == "failure" ]]; then - # test_ios_rntester uses continue-on-error, so it reports success - # even when the build that uploads this app failed. - echo "::error::RNTesterApp-NewArch-${{ matrix.flavor }} is missing. Check the test_ios_rntester (${{ matrix.flavor }}, frameworks: false) job." + echo "::error::RNTesterApp-NewArch-${{ matrix.flavor }} is missing. Check the test_ios_rntester (${{ matrix.flavor }}) job." RESULT=failed elif [[ -f "$STATE" ]] && jq -e 'any(.flows[]; .status == "failed")' "$STATE" > /dev/null; then RESULT=failed diff --git a/.github/workflows/e2e-ios-templateapp.yml b/.github/workflows/e2e-ios-templateapp.yml index 22795a58ef0a..235126474ab3 100644 --- a/.github/workflows/e2e-ios-templateapp.yml +++ b/.github/workflows/e2e-ios-templateapp.yml @@ -32,10 +32,9 @@ jobs: uses: ./.github/actions/setup-node - name: Run yarn uses: ./.github/actions/yarn-install - - name: Setup ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: 2.6.10 + - name: Ensure CocoaPods (`spm add --deintegrate` shells out to `pod`) + shell: bash + run: pod --version || sudo gem install cocoapods --no-document - name: Download React Native Package uses: ./.github/actions/download-artifact with: @@ -43,28 +42,12 @@ jobs: path: /tmp/react-native-tmp - name: Print /tmp folder run: ls -lR /tmp/react-native-tmp - - name: Download ReactNativeDependencies - uses: ./.github/actions/download-artifact - with: - name: ReactNativeDependencies${{ matrix.flavor }}.xcframework.tar.gz - path: /tmp/third-party - - name: Print third-party folder - shell: bash - run: ls -lR /tmp/third-party - - name: Download React Native Prebuilds - uses: ./.github/actions/download-artifact - with: - name: ReactCore${{ matrix.flavor }}.xcframework.tar.gz - path: /tmp/ReactCore - - name: Print ReactCore folder - shell: bash - run: ls -lR /tmp/ReactCore - name: Configure git shell: bash run: | git config --global user.email "react-native-bot@meta.com" git config --global user.name "React Native Bot" - - name: Build the app + - name: Create the app run: | REACT_NATIVE_PKG=$(find /tmp/react-native-tmp -type f -name "*.tgz") echo "React Native tgs is $REACT_NATIVE_PKG" @@ -77,19 +60,19 @@ jobs: fi node ./scripts/e2e/init-project-e2e.js --projectName RNTestProject --currentBranch $BRANCH --directory /tmp/RNTestProject --pathToLocalReactNative $REACT_NATIVE_PKG - - cd /tmp/RNTestProject/ios - bundle install - NEW_ARCH_ENABLED=1 - - export RCT_USE_LOCAL_RN_DEP=/tmp/third-party/ReactNativeDependencies${{ matrix.flavor }}.xcframework.tar.gz - # Disable prebuilds for now, as they are causing issues with E2E tests for 0.82-stable branch - export RCT_TESTONLY_RNCORE_TARBALL_PATH="/tmp/ReactCore/ReactCore${{ matrix.flavor }}.xcframework.tar.gz" - RN_STRICT_API=1 RCT_NEW_ARCH_ENABLED=$NEW_ARCH_ENABLED bundle exec pod install - + - name: Download the XCFrameworks built in this run + uses: ./.github/actions/setup-spm-artifacts + with: + working-directory: /tmp/RNTestProject/ios + - name: Convert the app to SwiftPM + working-directory: /tmp/RNTestProject/ios + run: LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 RN_STRICT_API=1 npx react-native spm add --deintegrate --artifacts /tmp/spm-artifacts --download skip + - name: Build the app + working-directory: /tmp/RNTestProject/ios + run: | xcodebuild \ -scheme "RNTestProject" \ - -workspace RNTestProject.xcworkspace \ + -project RNTestProject.xcodeproj \ -configuration "${{ matrix.flavor }}" \ -sdk "iphonesimulator" \ -destination "generic/platform=iOS Simulator" \ diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index 2c67bb36dc05..19bbb97aca4d 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -113,62 +113,20 @@ jobs: secrets: inherit needs: [prebuild_apple_dependencies] - test_ios_rntester_ruby_3_2_0: - runs-on: macos-15 - needs: [prebuild_apple_dependencies, prebuild_react_native_core] - if: ${{ needs.prebuild_react_native_core.result == 'success' }} - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Run it - uses: ./.github/actions/test-ios-rntester - with: - ruby-version: '3.2.0' - flavor: Debug - - test_ios_rntester_dynamic_frameworks: - runs-on: macos-15-large - needs: check_code_changes - if: | - needs.check_code_changes.outputs.any_code_change == 'true' && - needs.check_code_changes.outputs.should_test_ios == 'true' - strategy: - fail-fast: false - matrix: - flavor: [Debug, Release] - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Run it - uses: ./.github/actions/test-ios-rntester - with: - flavor: ${{ matrix.flavor }} - use-frameworks: true - run-unit-tests: false # tests for dynamic frameworks are already run in the test_ios_rntester job; this is to just a test build from source (no prebuilds) - test_ios_rntester: runs-on: macos-15-large needs: [prebuild_apple_dependencies, prebuild_react_native_core] if: ${{ needs.prebuild_react_native_core.result == 'success' }} - continue-on-error: true strategy: fail-fast: false matrix: flavor: [Debug, Release] - frameworks: [false, true] steps: - name: Checkout uses: actions/checkout@v6 - name: Run it uses: ./.github/actions/test-ios-rntester with: - use-frameworks: ${{ matrix.frameworks }} - # Consume the prebuilt artifacts in the dynamic-frameworks cells too: - # prebuilt + use_frameworks is the config of the 2026-07-03 - # SocketRocket dual-copy regression, previously covered by no lane - # (source-built dynamic frameworks stay covered by - # test_ios_rntester_dynamic_frameworks). - use-prebuilds: true flavor: ${{ matrix.flavor }} test_e2e_ios_rntester: @@ -225,28 +183,6 @@ jobs: fail-on-error: true secrets: inherit - test_ios_spm_rntester: - needs: - [ - prebuild_apple_dependencies, - prebuild_react_native_core, - check_code_changes, - ] - if: ${{ needs.prebuild_react_native_core.result == 'success' && needs.check_code_changes.outputs.should_test_ios == 'true' }} - uses: ./.github/workflows/test-ios-spm-rntester.yml - secrets: inherit - - test_ios_spm_helloworld: - needs: - [ - prebuild_apple_dependencies, - prebuild_react_native_core, - check_code_changes, - ] - if: ${{ needs.prebuild_react_native_core.result == 'success' && needs.check_code_changes.outputs.should_test_ios == 'true' }} - uses: ./.github/workflows/test-ios-spm-helloworld.yml - secrets: inherit - test_ios_spm_newapp: needs: [ @@ -490,21 +426,6 @@ jobs: path: ./private/helloworld/android/app/build/outputs/apk/ compression-level: 0 - test_ios_helloworld_with_ruby_3_2_0: - runs-on: macos-15 - needs: [prebuild_apple_dependencies, prebuild_react_native_core] - if: ${{ needs.prebuild_react_native_core.result == 'success' }} - env: - PROJECT_NAME: iOSTemplateProject - YARN_ENABLE_IMMUTABLE_INSTALLS: false - steps: - - name: Checkout - uses: actions/checkout@v6 - - uses: ./.github/actions/test-ios-helloworld - with: - ruby-version: 3.2.0 - flavor: Debug - test_ios_helloworld: runs-on: macos-15 needs: [prebuild_apple_dependencies, prebuild_react_native_core] @@ -512,11 +433,6 @@ jobs: strategy: matrix: flavor: [Debug, Release] - use_frameworks: [false, true] - exclude: - # This config is tested with Ruby 3.2.0. Let's not double test it. - - flavor: Debug - use_frameworks: false env: PROJECT_NAME: iOSTemplateProject YARN_ENABLE_IMMUTABLE_INSTALLS: false @@ -526,7 +442,6 @@ jobs: - uses: ./.github/actions/test-ios-helloworld with: flavor: ${{ matrix.flavor }} - use-frameworks: ${{ matrix.use_frameworks }} lint: runs-on: ubuntu-latest diff --git a/.github/workflows/test-ios-spm-helloworld.yml b/.github/workflows/test-ios-spm-helloworld.yml deleted file mode 100644 index 12713e06dc9f..000000000000 --- a/.github/workflows/test-ios-spm-helloworld.yml +++ /dev/null @@ -1,137 +0,0 @@ -name: Test iOS SwiftPM - Hello World - -permissions: - contents: read - -on: - workflow_call: - -jobs: - test: - runs-on: macos-15-large - strategy: - fail-fast: false - matrix: - flavor: [Debug, Release] - env: - APP_IOS_DIR: private/helloworld/ios - XCODE_PROJECT: HelloWorld.xcodeproj - XCODE_SCHEME: HelloWorld - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Setup xcode - uses: ./.github/actions/setup-xcode - - name: Setup node.js - uses: ./.github/actions/setup-node - - name: Run yarn install - uses: ./.github/actions/yarn-install - - name: Set Hermes prebuilt version - shell: bash - run: node ./scripts/releases/use-hermes-prebuilt.js - - name: Run yarn install again, with the correct hermes version - uses: ./.github/actions/yarn-install - - name: Ensure CocoaPods (`spm add --deintegrate` shells out to `pod`) - shell: bash - run: pod --version || sudo gem install cocoapods --no-document - # Both flavors are needed by every matrix cell, not just the one it builds: - # `spm add` stages both flavor framework trees and lets a per-configuration - # build setting pick one at build time, so it validates the debug/ and - # release/ artifact slots together (and `--download skip` refuses an - # incomplete slot). Please don't "optimise" this down to one flavor. - - name: Download ReactCore (Debug) - uses: ./.github/actions/download-artifact - with: - name: ReactCoreDebug.xcframework.tar.gz - path: /tmp/rc-debug - - name: Download ReactCore (Release) - uses: ./.github/actions/download-artifact - with: - name: ReactCoreRelease.xcframework.tar.gz - path: /tmp/rc-release - - name: Download ReactNativeDependencies (Debug) - uses: ./.github/actions/download-artifact - with: - name: ReactNativeDependenciesDebug.xcframework.tar.gz - path: /tmp/deps-debug - - name: Download ReactNativeDependencies (Release) - uses: ./.github/actions/download-artifact - with: - name: ReactNativeDependenciesRelease.xcframework.tar.gz - path: /tmp/deps-release - # The ordinary `spm download`, pointed at the XCFrameworks this run just - # built rather than at the published nightly (RN_CORE_TARBALL_PATH / - # RN_DEPS_TARBALL_PATH are overrides download-spm-artifacts.js already - # supports). It fills both flavor slots per call and skips any slot that - # already validates, so it runs twice: the first pass fills both slots - # from the Release tarballs, then debug/ is dropped and refilled from the - # Debug tarballs while release/ is left alone. hermes-engine is fetched - # from Maven by the command itself. - - name: Download XCFrameworks built in this run (Debug + Release) - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: | - RN_CORE_TARBALL_PATH=/tmp/rc-release/ReactCoreRelease.xcframework.tar.gz \ - RN_DEPS_TARBALL_PATH=/tmp/deps-release/ReactNativeDependenciesRelease.xcframework.tar.gz \ - npx react-native spm download --artifacts /tmp/spm-artifacts - rm -rf /tmp/spm-artifacts/debug - RN_CORE_TARBALL_PATH=/tmp/rc-debug/ReactCoreDebug.xcframework.tar.gz \ - RN_DEPS_TARBALL_PATH=/tmp/deps-debug/ReactNativeDependenciesDebug.xcframework.tar.gz \ - npx react-native spm download --artifacts /tmp/spm-artifacts - - name: Scaffold Package.swift manifests for community dependencies - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: npx react-native spm scaffold || true - - name: Convert the app to SwiftPM - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: npx react-native spm add --deintegrate --artifacts /tmp/spm-artifacts --download skip - - name: Assert the app is on SwiftPM - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: | - if [[ ! -f "$XCODE_PROJECT/.spm-injected.json" ]]; then - echo "::error::spm add did not inject SwiftPM: $XCODE_PROJECT/.spm-injected.json is missing" - exit 1 - fi - if grep -q 'Pods-' "$XCODE_PROJECT/project.pbxproj"; then - echo "::error::spm add --deintegrate left CocoaPods references in $XCODE_PROJECT" - exit 1 - fi - echo "SwiftPM injected in place; CocoaPods de-integrated." - - name: Build ${{ env.XCODE_SCHEME }} (${{ matrix.flavor }}) - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: | - xcodebuild \ - -project "$XCODE_PROJECT" \ - -scheme "$XCODE_SCHEME" \ - -configuration "${{ matrix.flavor }}" \ - -sdk iphonesimulator \ - -destination 'generic/platform=iOS Simulator' \ - -derivedDataPath build/spm-e2e-dd \ - build - - name: Check the embedded React.framework flavor - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: | - PRODUCTS="build/spm-e2e-dd/Build/Products/${{ matrix.flavor }}-iphonesimulator" - if [[ ! -d "$PRODUCTS" ]]; then - echo "Skipping flavor check: no build products directory." - exit 0 - fi - BINARY=$(find "$PRODUCTS" -maxdepth 4 -path '*.app/Frameworks/React.framework/React' -type f 2>/dev/null | head -1 || true) - if [[ -z "$BINARY" ]] || ! command -v nm >/dev/null; then - echo "Skipping flavor check: no embedded React.framework binary or no nm." - exit 0 - fi - COUNT=$(nm "$BINARY" | grep -c getDebugProps || true) - echo "getDebugProps symbols in $BINARY: $COUNT" - if [[ "${{ matrix.flavor }}" == 'Debug' && "$COUNT" -eq 0 ]]; then - echo "::error::Debug build embeds a Release React.framework (expected getDebugProps symbols, found none)" - exit 1 - fi - if [[ "${{ matrix.flavor }}" == 'Release' && "$COUNT" -ne 0 ]]; then - echo "::error::Release build embeds a Debug React.framework ($COUNT getDebugProps symbols, expected none)" - exit 1 - fi diff --git a/.github/workflows/test-ios-spm-newapp.yml b/.github/workflows/test-ios-spm-newapp.yml index 8fafbf48fa55..7c9daaa19412 100644 --- a/.github/workflows/test-ios-spm-newapp.yml +++ b/.github/workflows/test-ios-spm-newapp.yml @@ -31,34 +31,6 @@ jobs: run: node ./scripts/releases/use-hermes-prebuilt.js - name: Run yarn install again, with the correct hermes version uses: ./.github/actions/yarn-install - - name: Ensure CocoaPods (`spm add --deintegrate` shells out to `pod`) - shell: bash - run: pod --version || sudo gem install cocoapods --no-document - # Both flavors are needed by every matrix cell, not just the one it builds: - # `spm add` stages both flavor framework trees and lets a per-configuration - # build setting pick one at build time, so it validates the debug/ and - # release/ artifact slots together (and `--download skip` refuses an - # incomplete slot). Please don't "optimise" this down to one flavor. - - name: Download ReactCore (Debug) - uses: ./.github/actions/download-artifact - with: - name: ReactCoreDebug.xcframework.tar.gz - path: /tmp/rc-debug - - name: Download ReactCore (Release) - uses: ./.github/actions/download-artifact - with: - name: ReactCoreRelease.xcframework.tar.gz - path: /tmp/rc-release - - name: Download ReactNativeDependencies (Debug) - uses: ./.github/actions/download-artifact - with: - name: ReactNativeDependenciesDebug.xcframework.tar.gz - path: /tmp/deps-debug - - name: Download ReactNativeDependencies (Release) - uses: ./.github/actions/download-artifact - with: - name: ReactNativeDependenciesRelease.xcframework.tar.gz - path: /tmp/deps-release - name: Download React Native package uses: ./.github/actions/download-artifact with: @@ -70,46 +42,17 @@ jobs: - name: Prepare the HelloWorld application shell: bash run: node ./scripts/e2e/init-project-e2e.js --useHelloWorld --pathToLocalReactNative "/tmp/react-native-tmp/$(cat /tmp/react-native-tmp/react-native-package-version)" - # The ordinary `spm download`, pointed at the XCFrameworks this run just - # built rather than at the published nightly (RN_CORE_TARBALL_PATH / - # RN_DEPS_TARBALL_PATH are overrides download-spm-artifacts.js already - # supports). It fills both flavor slots per call and skips any slot that - # already validates, so it runs twice: the first pass fills both slots - # from the Release tarballs, then debug/ is dropped and refilled from the - # Debug tarballs while release/ is left alone. hermes-engine is fetched - # from Maven by the command itself. - - name: Download XCFrameworks built in this run (Debug + Release) - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: | - RN_CORE_TARBALL_PATH=/tmp/rc-release/ReactCoreRelease.xcframework.tar.gz \ - RN_DEPS_TARBALL_PATH=/tmp/deps-release/ReactNativeDependenciesRelease.xcframework.tar.gz \ - npx react-native spm download --artifacts /tmp/spm-artifacts - rm -rf /tmp/spm-artifacts/debug - RN_CORE_TARBALL_PATH=/tmp/rc-debug/ReactCoreDebug.xcframework.tar.gz \ - RN_DEPS_TARBALL_PATH=/tmp/deps-debug/ReactNativeDependenciesDebug.xcframework.tar.gz \ - npx react-native spm download --artifacts /tmp/spm-artifacts - - name: Scaffold Package.swift manifests for community dependencies - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: npx react-native spm scaffold || true - - name: Convert the app to SwiftPM - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: npx react-native spm add --deintegrate --artifacts /tmp/spm-artifacts --download skip - - name: Assert the app is on SwiftPM + - name: Download the XCFrameworks built in this run + uses: ./.github/actions/setup-spm-artifacts + with: + working-directory: ${{ env.APP_IOS_DIR }} + # HelloWorld is committed on SwiftPM; `update` regenerates its Swift + # packages against the reinstalled react-native. + # RN_STRICT_API=1 keeps the C++ stable API enforced, as `yarn bootstrap ios` does. + - name: Set up the Swift packages shell: bash working-directory: ${{ env.APP_IOS_DIR }} - run: | - if [[ ! -f "$XCODE_PROJECT/.spm-injected.json" ]]; then - echo "::error::spm add did not inject SwiftPM: $XCODE_PROJECT/.spm-injected.json is missing" - exit 1 - fi - if grep -q 'Pods-' "$XCODE_PROJECT/project.pbxproj"; then - echo "::error::spm add --deintegrate left CocoaPods references in $XCODE_PROJECT" - exit 1 - fi - echo "SwiftPM injected in place; CocoaPods de-integrated." + run: RN_STRICT_API=1 npx react-native spm update --artifacts /tmp/spm-artifacts --download skip - name: Build ${{ env.XCODE_SCHEME }} (${{ matrix.flavor }}) shell: bash working-directory: ${{ env.APP_IOS_DIR }} diff --git a/.github/workflows/test-ios-spm-rntester.yml b/.github/workflows/test-ios-spm-rntester.yml deleted file mode 100644 index cce98b0ba3d4..000000000000 --- a/.github/workflows/test-ios-spm-rntester.yml +++ /dev/null @@ -1,137 +0,0 @@ -name: Test iOS SwiftPM - RN Tester - -permissions: - contents: read - -on: - workflow_call: - -jobs: - test: - runs-on: macos-15-large - strategy: - fail-fast: false - matrix: - flavor: [Debug, Release] - env: - APP_IOS_DIR: packages/rn-tester - XCODE_PROJECT: RNTesterPods.xcodeproj - XCODE_SCHEME: RNTester - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Setup xcode - uses: ./.github/actions/setup-xcode - - name: Setup node.js - uses: ./.github/actions/setup-node - - name: Run yarn install - uses: ./.github/actions/yarn-install - - name: Set Hermes prebuilt version - shell: bash - run: node ./scripts/releases/use-hermes-prebuilt.js - - name: Run yarn install again, with the correct hermes version - uses: ./.github/actions/yarn-install - - name: Ensure CocoaPods (`spm add --deintegrate` shells out to `pod`) - shell: bash - run: pod --version || sudo gem install cocoapods --no-document - # Both flavors are needed by every matrix cell, not just the one it builds: - # `spm add` stages both flavor framework trees and lets a per-configuration - # build setting pick one at build time, so it validates the debug/ and - # release/ artifact slots together (and `--download skip` refuses an - # incomplete slot). Please don't "optimise" this down to one flavor. - - name: Download ReactCore (Debug) - uses: ./.github/actions/download-artifact - with: - name: ReactCoreDebug.xcframework.tar.gz - path: /tmp/rc-debug - - name: Download ReactCore (Release) - uses: ./.github/actions/download-artifact - with: - name: ReactCoreRelease.xcframework.tar.gz - path: /tmp/rc-release - - name: Download ReactNativeDependencies (Debug) - uses: ./.github/actions/download-artifact - with: - name: ReactNativeDependenciesDebug.xcframework.tar.gz - path: /tmp/deps-debug - - name: Download ReactNativeDependencies (Release) - uses: ./.github/actions/download-artifact - with: - name: ReactNativeDependenciesRelease.xcframework.tar.gz - path: /tmp/deps-release - # The ordinary `spm download`, pointed at the XCFrameworks this run just - # built rather than at the published nightly (RN_CORE_TARBALL_PATH / - # RN_DEPS_TARBALL_PATH are overrides download-spm-artifacts.js already - # supports). It fills both flavor slots per call and skips any slot that - # already validates, so it runs twice: the first pass fills both slots - # from the Release tarballs, then debug/ is dropped and refilled from the - # Debug tarballs while release/ is left alone. hermes-engine is fetched - # from Maven by the command itself. - - name: Download XCFrameworks built in this run (Debug + Release) - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: | - RN_CORE_TARBALL_PATH=/tmp/rc-release/ReactCoreRelease.xcframework.tar.gz \ - RN_DEPS_TARBALL_PATH=/tmp/deps-release/ReactNativeDependenciesRelease.xcframework.tar.gz \ - npx react-native spm download --artifacts /tmp/spm-artifacts - rm -rf /tmp/spm-artifacts/debug - RN_CORE_TARBALL_PATH=/tmp/rc-debug/ReactCoreDebug.xcframework.tar.gz \ - RN_DEPS_TARBALL_PATH=/tmp/deps-debug/ReactNativeDependenciesDebug.xcframework.tar.gz \ - npx react-native spm download --artifacts /tmp/spm-artifacts - - name: Scaffold Package.swift manifests for community dependencies - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: npx react-native spm scaffold || true - - name: Convert the app to SwiftPM - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: npx react-native spm add --deintegrate --artifacts /tmp/spm-artifacts --download skip - - name: Assert the app is on SwiftPM - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: | - if [[ ! -f "$XCODE_PROJECT/.spm-injected.json" ]]; then - echo "::error::spm add did not inject SwiftPM: $XCODE_PROJECT/.spm-injected.json is missing" - exit 1 - fi - if grep -q 'Pods-' "$XCODE_PROJECT/project.pbxproj"; then - echo "::error::spm add --deintegrate left CocoaPods references in $XCODE_PROJECT" - exit 1 - fi - echo "SwiftPM injected in place; CocoaPods de-integrated." - - name: Build ${{ env.XCODE_SCHEME }} (${{ matrix.flavor }}) - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: | - xcodebuild \ - -project "$XCODE_PROJECT" \ - -scheme "$XCODE_SCHEME" \ - -configuration "${{ matrix.flavor }}" \ - -sdk iphonesimulator \ - -destination 'generic/platform=iOS Simulator' \ - -derivedDataPath build/spm-e2e-dd \ - build - - name: Check the embedded React.framework flavor - shell: bash - working-directory: ${{ env.APP_IOS_DIR }} - run: | - PRODUCTS="build/spm-e2e-dd/Build/Products/${{ matrix.flavor }}-iphonesimulator" - if [[ ! -d "$PRODUCTS" ]]; then - echo "Skipping flavor check: no build products directory." - exit 0 - fi - BINARY=$(find "$PRODUCTS" -maxdepth 4 -path '*.app/Frameworks/React.framework/React' -type f 2>/dev/null | head -1 || true) - if [[ -z "$BINARY" ]] || ! command -v nm >/dev/null; then - echo "Skipping flavor check: no embedded React.framework binary or no nm." - exit 0 - fi - COUNT=$(nm "$BINARY" | grep -c getDebugProps || true) - echo "getDebugProps symbols in $BINARY: $COUNT" - if [[ "${{ matrix.flavor }}" == 'Debug' && "$COUNT" -eq 0 ]]; then - echo "::error::Debug build embeds a Release React.framework (expected getDebugProps symbols, found none)" - exit 1 - fi - if [[ "${{ matrix.flavor }}" == 'Release' && "$COUNT" -ne 0 ]]; then - echo "::error::Release build embeds a Debug React.framework ($COUNT getDebugProps symbols, expected none)" - exit 1 - fi diff --git a/.gitignore b/.gitignore index d1e403fa6aa7..d61a2d94a866 100644 --- a/.gitignore +++ b/.gitignore @@ -107,7 +107,6 @@ package-lock.json # Ruby Gems (Bundler) /packages/react-native/vendor -/private/helloworld/vendor .ruby-version /**/.ruby-version vendor/ diff --git a/private/core-cli-utils/README.md b/private/core-cli-utils/README.md index 545affa2aa5d..a881b6e90a12 100644 --- a/private/core-cli-utils/README.md +++ b/private/core-cli-utils/README.md @@ -1,18 +1,18 @@ # @react-native/core-cli-utils -A reference implementation of React Native CLI tooling. This package provides composable, ordered `Task` objects for common CLI operations including Android Gradle builds, iOS Xcode/CocoaPods workflows, Metro bundling with Hermes support, and cache cleaning. +A reference implementation of React Native CLI tooling. This package provides composable, ordered `Task` objects for common CLI operations including Android Gradle builds, iOS Xcode workflows with CocoaPods or SwiftPM, Metro bundling with Hermes support, and cache cleaning. This is not published to npm. Framework authors can use this code as a starting point for their own CLI tooling, but should not depend on it as a versioned API. ## Modules - **`android`** — Gradle-based Android build tasks (assemble, build, install) -- **`apple`** — Xcode/CocoaPods-based iOS tasks (bootstrap, build, install) +- **`apple`** — Xcode-based iOS tasks: bootstrap with CocoaPods (`bootstrap`) or SwiftPM (`bootstrapSwiftPM`), build, install - **`app`** — Metro bundler tasks (watch mode, bundle mode, Hermes bytecode compilation) - **`clean`** — Cache-cleaning tasks (Android/Gradle, Metro, npm, Watchman, Yarn, CocoaPods) - **`version`** — Semver version requirements for platform toolchains (Android NDK/SDK, Xcode, Node, etc.) ## Consumers -- [`private/helloworld/`](../helloworld/) — the primary consumer, using Android, iOS, and Metro modules +- [`private/helloworld/`](../helloworld/) — the primary consumer, using Android, iOS (SwiftPM), and Metro modules - [`packages/rn-tester/`](../../packages/rn-tester/) — uses iOS bootstrap for CocoaPods setup diff --git a/private/core-cli-utils/src/private/apple.js b/private/core-cli-utils/src/private/apple.js index d15664506c4b..293493c997e7 100644 --- a/private/core-cli-utils/src/private/apple.js +++ b/private/core-cli-utils/src/private/apple.js @@ -27,9 +27,13 @@ type AppleBuildOptions = { mode: AppleBuildMode, scheme?: string, destination: 'device' | 'simulator' | string, + // How the project gets its native dependencies. Defaults to 'cocoapods'. + dependencyManager?: AppleDependencyManager, ...AppleOptions, }; +type AppleDependencyManager = 'cocoapods' | 'swiftpm'; + type AppleBootstrapOption = { // Enabled by default hermes: boolean, @@ -38,6 +42,10 @@ type AppleBootstrapOption = { ...AppleOptions, }; +type AppleSwiftPMBootstrapOption = { + ...AppleOptions, +}; + type AppleInstallApp = { // Install the app on a simulator or device, typically this is the description from // `xcrun simctl list devices` @@ -78,6 +86,28 @@ const FIRST = 1, FOURTH = 4, FIFTH = 5; +// The Swift packages `react-native spm` generates, which Xcode resolves before +// it runs any build phase. +const SWIFT_PACKAGE_DIRS = [ + 'build/xcframeworks', + 'build/generated/autolinking', + 'build/generated/ios', +]; + +function assertReadable(cwd /*: string */, files /*: ReadonlyArray */) { + for (const file of files) { + try { + fs.accessSync( + path.join(cwd, file), + /* eslint-disable-next-line no-bitwise */ + fs.constants.F_OK | fs.constants.R_OK, + ); + } catch (e /*: unknown */) { + throw new Error('Please run: yarn run bootstrap ios: ' + String(e)); + } + } +} + function getNodePackagePath(packageName /*: string */) /*: string */ { // $FlowFixMe[prop-missing] type definition is incomplete return require.resolve(packageName, {cwd: [process.cwd(), ...module.paths]}); @@ -92,9 +122,14 @@ type BootstrapTasks = { installDependencies: Task, }; +type SwiftPMBootstrapTasks = { + cleanupBuildFolder: Task, + installDependencies: Task, +}; + type BuildTasks = { validate: Task, - hasPodsInstalled: Task, + hasDependenciesInstalled: Task, build: Task, }; @@ -158,6 +193,29 @@ const tasks = { }), }), + // 1. Or set up an Xcode project that uses SwiftPM instead of CocoaPods + bootstrapSwiftPM: ( + options /*: AppleSwiftPMBootstrapOption */, + ) /*: SwiftPMBootstrapTasks */ => ({ + cleanupBuildFolder: task(FIRST, 'Cleanup build folder', () => { + execa.sync('rm', ['-rf', 'build'], { + cwd: options.cwd, + }); + }), + installDependencies: task(SECOND, 'Set up the Swift packages', () => { + const reactNativePath = path.dirname(getNodePackagePath('react-native')); + const setupScript = path.join( + reactNativePath, + 'scripts', + 'setup-apple-spm.js', + ); + return execa('node', [setupScript, 'update'], { + cwd: options.cwd, + env: options.env, + }); + }), + }), + // 2. Build the iOS app using a setup environment build: ( options /*: AppleBuildOptions */, @@ -170,23 +228,21 @@ const tasks = { assertDependencies(isOnPath('xcodebuild', 'Xcode Commandline Tools')); }, ), - hasPodsInstalled: task(FIRST, 'Check Pods are installed', () => { - for (const file of ['Podfile.lock', 'Pods']) { - try { - fs.accessSync( - path.join(options.cwd, file), - /* eslint-disable-next-line no-bitwise */ - fs.constants.F_OK | fs.constants.R_OK, - ); - } catch (e /*: unknown */) { - throw new Error('Please run: yarn run bootstrap ios: ' + String(e)); + hasDependenciesInstalled: task( + FIRST, + 'Check the native dependencies are installed', + () => { + if (options.dependencyManager === 'swiftpm') { + assertReadable(options.cwd, SWIFT_PACKAGE_DIRS); + return; } - } - checkPodfileInSyncWithManifest( - path.join(options.cwd, 'Podfile.lock'), - path.join(options.cwd, 'Pods/Manifest.lock'), - ); - }), + assertReadable(options.cwd, ['Podfile.lock', 'Pods']); + checkPodfileInSyncWithManifest( + path.join(options.cwd, 'Podfile.lock'), + path.join(options.cwd, 'Pods/Manifest.lock'), + ); + }, + ), build: task(SECOND, 'build an app artifact', () => { const _args = [ options.isWorkspace ? '-workspace' : '-project', diff --git a/private/helloworld/.bundle/config b/private/helloworld/.bundle/config deleted file mode 100644 index 848943bb5274..000000000000 --- a/private/helloworld/.bundle/config +++ /dev/null @@ -1,2 +0,0 @@ -BUNDLE_PATH: "vendor/bundle" -BUNDLE_FORCE_RUBY_PLATFORM: 1 diff --git a/private/helloworld/.gitignore b/private/helloworld/.gitignore index a5d48a127bd4..8ab31d607ae8 100644 --- a/private/helloworld/.gitignore +++ b/private/helloworld/.gitignore @@ -40,10 +40,6 @@ yarn-error.log # Bundle artifact *.jsbundle -# Ruby / CocoaPods -ios/Pods/ -vendor/bundle/ - # Temporary files created by Metro to check the health of the file watcher .metro-health-check* diff --git a/private/helloworld/.react-native.config b/private/helloworld/.react-native.config index 67e1e4e23e30..37928745fa35 100644 --- a/private/helloworld/.react-native.config +++ b/private/helloworld/.react-native.config @@ -11,8 +11,8 @@ "ios": { "sourceDir": "HELLOWORLD_PATH/ios", "xcodeProject": { - "name": "HelloWorld.xcworkspace", - "isWorkspace": true + "name": "HelloWorld.xcodeproj", + "isWorkspace": false } }, "android": { diff --git a/private/helloworld/Gemfile b/private/helloworld/Gemfile deleted file mode 100644 index b694d98f521a..000000000000 --- a/private/helloworld/Gemfile +++ /dev/null @@ -1,26 +0,0 @@ -source 'https://rubygems.org' - -ruby ">= 2.6.10" - -# concurrent-ruby >= 1.3.5 no longer requires 'logger', and activesupport 6.1.x -# references Logger without requiring it, so `bundle exec pod` crashes with -# "uninitialized constant ActiveSupport::LoggerThreadSafeLevel::Logger". A bare -# `gem 'logger'` is not enough because bundler does not auto-require Gemfile gems; -# require it here so it is loaded before cocoapods loads active_support. -require 'logger' - -gem 'cocoapods', '~> 1.13', '!= 1.15.0', '!= 1.15.1' -gem 'activesupport', '>= 6.1.7.5', '< 7.1.0' -gem 'xcodeproj', '< 1.26.0' -gem 'concurrent-ruby', '>= 1.3.7' - -# Ruby 3.4.0 has removed some libraries from the standard library. -gem 'bigdecimal' -gem 'logger' -gem 'benchmark' -gem 'mutex_m' -gem 'nkf' -gem 'base64' - -gem 'ffi', '>= 1.17.2' -gem 'rexml', '>= 3.3.9' diff --git a/private/helloworld/Gemfile.lock b/private/helloworld/Gemfile.lock deleted file mode 100644 index e32aa146390f..000000000000 --- a/private/helloworld/Gemfile.lock +++ /dev/null @@ -1,117 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - CFPropertyList (3.0.7) - base64 - nkf - rexml - activesupport (7.0.8.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - addressable (2.9.0) - public_suffix (>= 2.0.2, < 8.0) - algoliasearch (1.27.5) - httpclient (~> 2.8, >= 2.8.3) - json (>= 1.5.1) - atomos (0.1.3) - base64 (0.2.0) - benchmark (0.4.0) - bigdecimal (3.1.9) - claide (1.1.0) - cocoapods (1.15.2) - addressable (~> 2.8) - claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.15.2) - cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 2.1, < 3.0) - cocoapods-plugins (>= 1.0.0, < 2.0) - cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.6.0, < 2.0) - cocoapods-try (>= 1.1.0, < 2.0) - colored2 (~> 3.1) - escape (~> 0.0.4) - fourflusher (>= 2.3.0, < 3.0) - gh_inspector (~> 1.0) - molinillo (~> 0.8.0) - nap (~> 1.0) - ruby-macho (>= 2.3.0, < 3.0) - xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.15.2) - activesupport (>= 5.0, < 8) - addressable (~> 2.8) - algoliasearch (~> 1.0) - concurrent-ruby (~> 1.1) - fuzzy_match (~> 2.0.4) - nap (~> 1.0) - netrc (~> 0.11) - public_suffix (~> 4.0) - typhoeus (~> 1.0) - cocoapods-deintegrate (1.0.5) - cocoapods-downloader (2.1) - cocoapods-plugins (1.0.0) - nap - cocoapods-search (1.0.1) - cocoapods-trunk (1.6.0) - nap (>= 0.8, < 2.0) - netrc (~> 0.11) - cocoapods-try (1.2.0) - colored2 (3.1.2) - concurrent-ruby (1.3.7) - escape (0.0.4) - ethon (0.16.0) - ffi (>= 1.15.0) - ffi (1.17.2) - fourflusher (2.3.1) - fuzzy_match (2.0.4) - gh_inspector (1.1.3) - httpclient (2.8.3) - i18n (1.14.5) - concurrent-ruby (~> 1.0) - json (2.7.2) - logger (1.6.5) - minitest (5.25.1) - molinillo (0.8.0) - mutex_m (0.3.0) - nanaimo (0.3.0) - nap (1.1.0) - netrc (0.11.0) - nkf (0.2.0) - public_suffix (4.0.7) - rexml (3.4.1) - ruby-macho (2.5.1) - typhoeus (1.4.1) - ethon (>= 0.9.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - xcodeproj (1.25.0) - CFPropertyList (>= 2.3.3, < 4.0) - atomos (~> 0.1.3) - claide (>= 1.0.2, < 2.0) - colored2 (~> 3.1) - nanaimo (~> 0.3.0) - rexml (>= 3.3.2, < 4.0) - -PLATFORMS - ruby - -DEPENDENCIES - activesupport (>= 6.1.7.5, < 7.1.0) - base64 - benchmark - bigdecimal - cocoapods (~> 1.13, != 1.15.1, != 1.15.0) - concurrent-ruby (>= 1.3.7) - ffi (>= 1.17.2) - logger - mutex_m - nkf - rexml (>= 3.3.9) - xcodeproj (< 1.26.0) - -RUBY VERSION - ruby 3.3.0p0 - -BUNDLED WITH - 2.5.3 diff --git a/private/helloworld/cli.flow.js b/private/helloworld/cli.flow.js index d9732a991877..53c43a7a8f8e 100644 --- a/private/helloworld/cli.flow.js +++ b/private/helloworld/cli.flow.js @@ -9,12 +9,11 @@ */ import {run} from './lib/cli'; -import {getExistingPath, pauseWatchman} from './lib/filesystem'; +import {pauseWatchman} from './lib/filesystem'; import { bootSimulator, getSimulatorDetails, getXcodeBuildSettings, - hasPodInstalled, launchApp, launchSimulator, } from './lib/ios'; @@ -34,47 +33,27 @@ const cwd = { root: __dirname, }; -const possibleHermescPaths = [ - // OSS checkout - 'destroot/bin/hermesc', - // internal - 'build_host_hermesc/bin/hermesc', -]; - -type BootstrapOptions = { - arch: 'old' | 'new', - jsvm: 'hermes' | 'jsc', - frameworks?: 'static' | 'dynamic', -}; +// A SwiftPM app has no Pods/hermes-engine. Like react-native-xcode.sh, use the +// hermesc of the hermes-compiler package that react-native depends on. +const getHermesConfig = () => ({ + path: path.dirname( + require.resolve('hermes-compiler/package.json', { + paths: [path.dirname(require.resolve('react-native/package.json'))], + }), + ), + hermesc: 'hermesc/osx-bin/hermesc', +}); bootstrap .command('ios') .description('Bootstrap iOS') - .addOption( - new Option('--arch ', "Choose React Native's architecture") - .choices(['new', 'old']) - .default('new'), - ) - .addOption( - new Option( - '--frameworks ', - 'Use frameworks instead of static libraries', - ) - .choices(['static', 'dynamic']) - .default(undefined), - ) - .addOption( - new Option('--jsvm ', 'Choose VM used on device') - .choices(['jsc', 'hermes']) - .default('hermes'), - ) - .action(async ({jsvm, arch, frameworks}: BootstrapOptions) => { + .action(async () => { await run( - apple.bootstrap({ + apple.bootstrapSwiftPM({ cwd: cwd.ios, - frameworks, - hermes: jsvm === 'hermes', - newArchitecture: arch === 'new', + // Build with the C++ stable API enforced. + // Run with RN_STRICT_API=0 to opt out. + env: {RN_STRICT_API: process.env.RN_STRICT_API ?? '1'}, }), ); }); @@ -177,10 +156,11 @@ build await pauseWatchman(async () => { await run( apple.build({ - isWorkspace: true, - name: 'HelloWorld.xcworkspace', + isWorkspace: false, + name: 'HelloWorld.xcodeproj', mode, scheme: 'HelloWorld', + dependencyManager: 'swiftpm', cwd: cwd.ios, env: { SKIP_BUNDLING: 'true', @@ -205,8 +185,8 @@ bundle .action(async ({prod, watch}: BundleOptions) => { const mode = prod ? 'Release' : 'Debug'; - const isHermesInstalled = hasPodInstalled(cwd.ios, 'hermes-engine'); const settings = await getBuildSettings(mode); + const hermes = getHermesConfig(); // Metro: src -> js const jsBundlePath = path.join(settings.bundleBuildDir, 'main.jsbundle.js'); @@ -216,18 +196,6 @@ bundle 'main.jsbundle', ); - // Validate only after initial build, as hermesc may not be prebuilt - const hermesFolder = path.join(cwd.ios, 'Pods/hermes-engine'); - const hermesc = getExistingPath(hermesFolder, possibleHermescPaths); - - if (hermesc == null && isHermesInstalled) { - throw new Error( - `Unable to find hermesc at:\n-${possibleHermescPaths - .map(line => ' - ' + line) - .join('\n')}`, - ); - } - const bundler = watch ? app.bundle({ mode: 'watch', @@ -240,11 +208,8 @@ bundle outputSourceMap: settings.bundleResourceDir, outputBundle: binaryBundlePath, dev: !prod, - jsvm: isHermesInstalled ? 'hermes' : 'jsc', - hermes: { - path: hermesFolder, - hermesc: hermesc ?? '', - }, + jsvm: 'hermes', + hermes, callback: metroProcess => { const readline = require('node:readline'); readline.emitKeypressEvents(process.stdin); @@ -267,11 +232,8 @@ bundle outputSourceMap: settings.bundleResourceDir, outputBundle: binaryBundlePath, dev: !prod, - jsvm: isHermesInstalled ? 'hermes' : 'jsc', - hermes: { - path: hermesFolder, - hermesc: hermesc ?? '', - }, + jsvm: 'hermes', + hermes, }); // JS Bundle diff --git a/private/helloworld/ios/.gitignore b/private/helloworld/ios/.gitignore new file mode 100644 index 000000000000..c1e540af8220 --- /dev/null +++ b/private/helloworld/ios/.gitignore @@ -0,0 +1,6 @@ + +# SPM – auto-generated at build time (do not commit) +Package.resolved +build/generated/ +build/xcframeworks/ +.build/ diff --git a/private/helloworld/ios/HelloWorld.xcodeproj/.spm-injected.json b/private/helloworld/ios/HelloWorld.xcodeproj/.spm-injected.json new file mode 100644 index 000000000000..69f364cbc665 --- /dev/null +++ b/private/helloworld/ios/HelloWorld.xcodeproj/.spm-injected.json @@ -0,0 +1,69 @@ +{ + "rootUuid": "83CBB9F71A601CBA00E9B192", + "target": "HelloWorld", + "targetUuid": "13B07F861A680F5B00A75B9A", + "injectedUuids": [ + "074F3370C5D61C114F081477", + "17CA21E30A8FFCB9D0FC1E35", + "27BDE9D743CB351F53154525", + "3AFDEFB917B01458307FDE0A", + "3F1E298E4A2D7CA403A328B5", + "50A8621D202A6CFB97E2C1B5", + "512EBECF5434FD7A6AF7C967", + "658A88AFBFD620F8BC6F91BA", + "69A7AB74931112BD2779B631", + "8332BE36B9C49BD2E49347C1", + "920F69A9DF98D37C8308D020", + "947DD5EF3BE079FB7F5AFF87", + "94F7C9B714A718310DA6BE9C", + "A700952B9A737913514EB28F", + "B174F870C259554596A4373B", + "C2FECF1430FDAAE3BF355839", + "D6BD17EA9023C9F39246B71F", + "F3857EDDA38AFC0011322D6D" + ], + "createdArrayFields": [ + { + "container": "project", + "key": "packageReferences" + }, + { + "container": "target", + "key": "packageProductDependencies" + } + ], + "xcconfig": { + "fileRefUuid": "074F3370C5D61C114F081477", + "configUuids": [ + "13B07F941A680F5B00A75B9A", + "13B07F951A680F5B00A75B9A" + ], + "includedByConfigUuids": [], + "removedPodsRootReactNativePath": true + }, + "generatedSources": {}, + "scriptPhases": {}, + "linkedFrameworks": [ + { + "id": "react", + "frameworkName": "React" + }, + { + "id": "react-native-dependencies", + "frameworkName": "ReactNativeDependencies" + }, + { + "id": "hermes", + "frameworkName": "hermesvm" + } + ], + "artifactsVersionOverride": null, + "configCommand": null, + "cxxApiDefines": [ + "RN_STRICT_API" + ], + "scheme": { + "file": "HelloWorld.xcscheme", + "created": false + } +} diff --git a/private/helloworld/ios/HelloWorld.xcodeproj/project.pbxproj b/private/helloworld/ios/HelloWorld.xcodeproj/project.pbxproj index e3cd20e1f572..35ea0a4773ad 100644 --- a/private/helloworld/ios/HelloWorld.xcodeproj/project.pbxproj +++ b/private/helloworld/ios/HelloWorld.xcodeproj/project.pbxproj @@ -11,10 +11,14 @@ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 6EA01F72FAC10D00AECACF94 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 0EC7AB76F90EED035707BA4E /* PrivacyInfo.xcprivacy */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; - C5E681697D864CB241D83EFA /* libPods-HelloWorld-HelloWorldTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A8E5ECDA77F0C61C263AE4C /* libPods-HelloWorld-HelloWorldTests.a */; }; CDA0ED1A2D0B2D810079F561 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA0ED192D0B2D810079F561 /* AppDelegate.swift */; }; CDA0ED1B2D0B2D810079F562 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA0ED1C2D0B2D810079F562 /* SceneDelegate.swift */; }; - F139D3B2B0DB3E81C6EC8497 /* libPods-HelloWorld.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E4454510AD252A40B7E239F /* libPods-HelloWorld.a */; }; + 94F7C9B714A718310DA6BE9C /* ReactHeaders in Frameworks */ = {isa = PBXBuildFile; productRef = 512EBECF5434FD7A6AF7C967 /* ReactHeaders */;}; + 17CA21E30A8FFCB9D0FC1E35 /* ReactNativeHeaders in Frameworks */ = {isa = PBXBuildFile; productRef = 920F69A9DF98D37C8308D020 /* ReactNativeHeaders */;}; + 69A7AB74931112BD2779B631 /* ReactNativeDependenciesHeaders in Frameworks */ = {isa = PBXBuildFile; productRef = 8332BE36B9C49BD2E49347C1 /* ReactNativeDependenciesHeaders */;}; + D6BD17EA9023C9F39246B71F /* Autolinked in Frameworks */ = {isa = PBXBuildFile; productRef = 3AFDEFB917B01458307FDE0A /* Autolinked */;}; + 3F1E298E4A2D7CA403A328B5 /* ReactCodegen in Frameworks */ = {isa = PBXBuildFile; productRef = 947DD5EF3BE079FB7F5AFF87 /* ReactCodegen */;}; + 50A8621D202A6CFB97E2C1B5 /* ReactAppDependencyProvider in Frameworks */ = {isa = PBXBuildFile; productRef = F3857EDDA38AFC0011322D6D /* ReactAppDependencyProvider */;}; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -36,16 +40,16 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = HelloWorld/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = HelloWorld/Info.plist; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = HelloWorld/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 3B4392A12AC88292D35C810B /* Pods-HelloWorld.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld.debug.xcconfig"; path = "Target Support Files/Pods-HelloWorld/Pods-HelloWorld.debug.xcconfig"; sourceTree = ""; }; - 3E4454510AD252A40B7E239F /* libPods-HelloWorld.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-HelloWorld.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 5709B34CF0A7D63546082F79 /* Pods-HelloWorld.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld.release.xcconfig"; path = "Target Support Files/Pods-HelloWorld/Pods-HelloWorld.release.xcconfig"; sourceTree = ""; }; - 5B7EB9410499542E8C5724F5 /* Pods-HelloWorld-HelloWorldTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld-HelloWorldTests.debug.xcconfig"; path = "Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests.debug.xcconfig"; sourceTree = ""; }; - 7A8E5ECDA77F0C61C263AE4C /* libPods-HelloWorld-HelloWorldTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-HelloWorld-HelloWorldTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = HelloWorld/LaunchScreen.storyboard; sourceTree = ""; }; - 89C6BE57DB24E9ADA2F236DE /* Pods-HelloWorld-HelloWorldTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HelloWorld-HelloWorldTests.release.xcconfig"; path = "Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests.release.xcconfig"; sourceTree = ""; }; CDA0ED192D0B2D810079F561 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = HelloWorld/AppDelegate.swift; sourceTree = ""; }; CDA0ED1C2D0B2D810079F562 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = HelloWorld/SceneDelegate.swift; sourceTree = ""; }; - ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + 074F3370C5D61C114F081477 /* ReactNativeSPM.xcconfig */ = { + isa = PBXFileReference; + lastKnownFileType = text.xcconfig; + name = ReactNativeSPM.xcconfig; + path = ReactNativeSPM/ReactNativeSPM.xcconfig; + sourceTree = SOURCE_ROOT; + }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,7 +57,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C5E681697D864CB241D83EFA /* libPods-HelloWorld-HelloWorldTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -61,7 +64,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - F139D3B2B0DB3E81C6EC8497 /* libPods-HelloWorld.a in Frameworks */, + 94F7C9B714A718310DA6BE9C /* ReactHeaders in Frameworks */, + 17CA21E30A8FFCB9D0FC1E35 /* ReactNativeHeaders in Frameworks */, + 69A7AB74931112BD2779B631 /* ReactNativeDependenciesHeaders in Frameworks */, + D6BD17EA9023C9F39246B71F /* Autolinked in Frameworks */, + 3F1E298E4A2D7CA403A328B5 /* ReactCodegen in Frameworks */, + 50A8621D202A6CFB97E2C1B5 /* ReactAppDependencyProvider in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -102,9 +110,6 @@ 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { isa = PBXGroup; children = ( - ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 3E4454510AD252A40B7E239F /* libPods-HelloWorld.a */, - 7A8E5ECDA77F0C61C263AE4C /* libPods-HelloWorld-HelloWorldTests.a */, ); name = Frameworks; sourceTree = ""; @@ -124,7 +129,7 @@ 00E356EF1AD99517003FC87E /* HelloWorldTests */, 83CBBA001A601CBA00E9B192 /* Products */, 2D16E6871FA4F8E400B85C8A /* Frameworks */, - BBD78D7AC51CEA395F1C20DB /* Pods */, + 074F3370C5D61C114F081477 /* ReactNativeSPM.xcconfig */, ); indentWidth = 2; sourceTree = ""; @@ -140,17 +145,6 @@ name = Products; sourceTree = ""; }; - BBD78D7AC51CEA395F1C20DB /* Pods */ = { - isa = PBXGroup; - children = ( - 3B4392A12AC88292D35C810B /* Pods-HelloWorld.debug.xcconfig */, - 5709B34CF0A7D63546082F79 /* Pods-HelloWorld.release.xcconfig */, - 5B7EB9410499542E8C5724F5 /* Pods-HelloWorld-HelloWorldTests.debug.xcconfig */, - 89C6BE57DB24E9ADA2F236DE /* Pods-HelloWorld-HelloWorldTests.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -158,12 +152,9 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "HelloWorldTests" */; buildPhases = ( - A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */, - A44ED3CC3037C88F69E3AF15 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -176,16 +167,23 @@ productType = "com.apple.product-type.bundle.unit-test"; }; 13B07F861A680F5B00A75B9A /* HelloWorld */ = { + packageProductDependencies = ( + 512EBECF5434FD7A6AF7C967 /* ReactHeaders */, + 920F69A9DF98D37C8308D020 /* ReactNativeHeaders */, + 8332BE36B9C49BD2E49347C1 /* ReactNativeDependenciesHeaders */, + 3AFDEFB917B01458307FDE0A /* Autolinked */, + 947DD5EF3BE079FB7F5AFF87 /* ReactCodegen */, + F3857EDDA38AFC0011322D6D /* ReactAppDependencyProvider */, + ); isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "HelloWorld" */; buildPhases = ( - C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */, + 658A88AFBFD620F8BC6F91BA /* Sync SPM Autolinking */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, + B174F870C259554596A4373B /* Embed React Native Flavored Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - E235C05ADACE081382539298 /* [CP] Copy Pods Resources */, - D32CB2BA406E97DB62F51C6B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -200,6 +198,11 @@ /* Begin PBXProject section */ 83CBB9F71A601CBA00E9B192 /* Project object */ = { + packageReferences = ( + 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "build/xcframeworks" */, + C2FECF1430FDAAE3BF355839 /* XCLocalSwiftPackageReference "build/generated/autolinking" */, + A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "build/generated/ios" */, + ); isa = PBXProject; attributes = { LastUpgradeCheck = 1210; @@ -270,24 +273,7 @@ shellPath = /bin/sh; shellScript = "set -e\n\nexport CONFIG_JSON=$(sed -e \"s|HELLOWORLD_PATH|$(realpath \"${SRCROOT}/../\")|g\" -e \"s|REACT_NATIVE_PATH|$(realpath \"${REACT_NATIVE_PATH}\")|g\" \"${SRCROOT}/../.react-native.config\")\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - A44ED3CC3037C88F69E3AF15 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */ = { + 658A88AFBFD620F8BC6F91BA /* Sync SPM Autolinking */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -295,21 +281,17 @@ inputFileListPaths = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; + name = "Sync SPM Autolinking"; outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-HelloWorld-HelloWorldTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellPath = /bin/bash; + shellScript = "SCRIPT=\"$SRCROOT/ReactNativeSPM/Scripts/sync-autolinking.sh\"\nif [ ! -f \"$SCRIPT\" ]; then\n echo \"error: $SCRIPT is missing. Run 'npx react-native spm update' to generate it.\"\n exit 1\nfi\nexec /bin/bash \"$SCRIPT\""; }; - C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { + B174F870C259554596A4373B /* Embed React Native Flavored Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -317,70 +299,23 @@ inputFileListPaths = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", + "$(SRCROOT)/build/xcframeworks/.artifact-stamp", + "$(SRCROOT)/ReactNativeSPM/Scripts/embed-flavored-frameworks.sh", + "$(RN_SPM_REACT_FRAMEWORK)", + "$(RN_SPM_REACT_NATIVE_DEPENDENCIES_FRAMEWORK)", + "$(RN_SPM_HERMES_FRAMEWORK)", ); - name = "[CP] Check Pods Manifest.lock"; + name = "Embed React Native Flavored Frameworks"; outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-HelloWorld-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - D32CB2BA406E97DB62F51C6B /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-HelloWorld/Pods-HelloWorld-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-HelloWorld/Pods-HelloWorld-frameworks-${CONFIGURATION}-output-files.xcfilelist", + "$(TARGET_BUILD_DIR)/$(FRAMEWORKS_FOLDER_PATH)/React.framework", + "$(TARGET_BUILD_DIR)/$(FRAMEWORKS_FOLDER_PATH)/ReactNativeDependencies.framework", + "$(TARGET_BUILD_DIR)/$(FRAMEWORKS_FOLDER_PATH)/hermesvm.framework", ); runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-HelloWorld/Pods-HelloWorld-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-HelloWorld/Pods-HelloWorld-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-HelloWorld/Pods-HelloWorld-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-HelloWorld/Pods-HelloWorld-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-HelloWorld-HelloWorldTests/Pods-HelloWorld-HelloWorldTests-resources.sh\"\n"; - showEnvVarsInLog = 0; + shellPath = /bin/bash; + shellScript = "SCRIPT=\"$SRCROOT/ReactNativeSPM/Scripts/embed-flavored-frameworks.sh\"\nif [ ! -f \"$SCRIPT\" ]; then\n echo \"error: $SCRIPT is missing. Run 'npx react-native spm update' to generate it.\"\n exit 1\nfi\nexec /bin/bash \"$SCRIPT\""; }; /* End PBXShellScriptBuildPhase section */ @@ -415,7 +350,6 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5B7EB9410499542E8C5724F5 /* Pods-HelloWorld-HelloWorldTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -443,7 +377,6 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89C6BE57DB24E9ADA2F236DE /* Pods-HelloWorld-HelloWorldTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; @@ -468,7 +401,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-HelloWorld.debug.xcconfig */; + baseConfigurationReference = 074F3370C5D61C114F081477 /* ReactNativeSPM.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -487,7 +420,6 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = HelloWorld; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../packages/react-native"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -496,7 +428,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-HelloWorld.release.xcconfig */; + baseConfigurationReference = 074F3370C5D61C114F081477 /* ReactNativeSPM.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -514,7 +446,6 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = HelloWorld; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../packages/react-native"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; @@ -737,6 +668,21 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "build/xcframeworks" */ = {isa = XCLocalSwiftPackageReference; relativePath = build/xcframeworks;}; + C2FECF1430FDAAE3BF355839 /* XCLocalSwiftPackageReference "build/generated/autolinking" */ = {isa = XCLocalSwiftPackageReference; relativePath = build/generated/autolinking;}; + A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "build/generated/ios" */ = {isa = XCLocalSwiftPackageReference; relativePath = build/generated/ios;}; +/* End XCLocalSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 512EBECF5434FD7A6AF7C967 /* ReactHeaders */ = {isa = XCSwiftPackageProductDependency; package = 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "build/xcframeworks" */; productName = ReactHeaders;}; + 920F69A9DF98D37C8308D020 /* ReactNativeHeaders */ = {isa = XCSwiftPackageProductDependency; package = 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "build/xcframeworks" */; productName = ReactNativeHeaders;}; + 8332BE36B9C49BD2E49347C1 /* ReactNativeDependenciesHeaders */ = {isa = XCSwiftPackageProductDependency; package = 27BDE9D743CB351F53154525 /* XCLocalSwiftPackageReference "build/xcframeworks" */; productName = ReactNativeDependenciesHeaders;}; + 3AFDEFB917B01458307FDE0A /* Autolinked */ = {isa = XCSwiftPackageProductDependency; package = C2FECF1430FDAAE3BF355839 /* XCLocalSwiftPackageReference "build/generated/autolinking" */; productName = Autolinked;}; + 947DD5EF3BE079FB7F5AFF87 /* ReactCodegen */ = {isa = XCSwiftPackageProductDependency; package = A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "build/generated/ios" */; productName = ReactCodegen;}; + F3857EDDA38AFC0011322D6D /* ReactAppDependencyProvider */ = {isa = XCSwiftPackageProductDependency; package = A700952B9A737913514EB28F /* XCLocalSwiftPackageReference "build/generated/ios" */; productName = ReactAppDependencyProvider;}; +/* End XCSwiftPackageProductDependency section */ + }; rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; } diff --git a/private/helloworld/ios/HelloWorld.xcodeproj/xcshareddata/xcschemes/HelloWorld.xcscheme b/private/helloworld/ios/HelloWorld.xcodeproj/xcshareddata/xcschemes/HelloWorld.xcscheme index b57be22ab2d6..cab62ba4e6e2 100644 --- a/private/helloworld/ios/HelloWorld.xcodeproj/xcshareddata/xcschemes/HelloWorld.xcscheme +++ b/private/helloworld/ios/HelloWorld.xcodeproj/xcshareddata/xcschemes/HelloWorld.xcscheme @@ -5,6 +5,25 @@ + + + + + + + + + + - - - - - - diff --git a/private/helloworld/ios/Podfile b/private/helloworld/ios/Podfile deleted file mode 100644 index 18613ea6d2eb..000000000000 --- a/private/helloworld/ios/Podfile +++ /dev/null @@ -1,38 +0,0 @@ -require "../../../packages/react-native/scripts/cocoapods/autolinking.rb" - -platform :ios, min_ios_version_supported -prepare_react_native_project! - -# Build with the C++ stable API enforced. Run `RN_STRICT_API=0 pod install` to opt out. -ENV['RN_STRICT_API'] ||= '1' - -linkage = ENV['USE_FRAMEWORKS'] -if linkage != nil - Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green - use_frameworks! :linkage => linkage.to_sym -end - -target 'HelloWorld' do - config = use_native_modules!(['sh', '../scripts/config.sh']) - - use_react_native!( - :path => "../../../packages/react-native", - # An absolute path to your application root. - :app_path => "#{Pod::Config.instance.installation_root}/.." - ) - - target 'HelloWorldTests' do - inherit! :complete - # Pods for testing - end - - post_install do |installer| - # scripts/react_native_pods.rb - react_native_post_install( - installer, - config[:reactNativePath], - :mac_catalyst_enabled => false, - # :ccache_enabled => true - ) - end -end diff --git a/private/helloworld/ios/ReactNativeSPM/ReactNativeSPM.xcconfig b/private/helloworld/ios/ReactNativeSPM/ReactNativeSPM.xcconfig new file mode 100644 index 000000000000..cbe4618ff0c0 --- /dev/null +++ b/private/helloworld/ios/ReactNativeSPM/ReactNativeSPM.xcconfig @@ -0,0 +1,68 @@ +// Generated by React Native on every `spm add` and `spm update`. +// Do not edit: changes are overwritten. + +CLANG_CXX_LANGUAGE_STANDARD = c++20 +REACT_NATIVE_PATH = $(SRCROOT)/../../../node_modules/react-native + +HEADER_SEARCH_PATHS = $(inherited) "$(SRCROOT)/build/generated/autolinking/headers" +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) RN_STRICT_API +OTHER_LDFLAGS = $(inherited) -ObjC "$(RN_SPM_REACT_BINARY)" "$(RN_SPM_REACT_NATIVE_DEPENDENCIES_BINARY)" "$(RN_SPM_HERMES_BINARY)" +FRAMEWORK_SEARCH_PATHS = $(inherited) "$(RN_SPM_REACT_SEARCH_PATH)" "$(RN_SPM_REACT_NATIVE_DEPENDENCIES_SEARCH_PATH)" "$(RN_SPM_HERMES_SEARCH_PATH)" +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks + +RN_SPM_FLAVOR = release +RN_SPM_FLAVOR[config=Debug] = debug +SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug] = $(inherited) DEBUG +RN_SPM_FLAVOR[config=Release] = release + +RN_SPM_REACT_FRAMEWORK[sdk=iphoneos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/React.xcframework/ios-arm64/React.framework +RN_SPM_REACT_BINARY[sdk=iphoneos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/React.xcframework/ios-arm64/React.framework/React +RN_SPM_REACT_SEARCH_PATH[sdk=iphoneos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/React.xcframework/ios-arm64 +RN_SPM_REACT_FRAMEWORK[sdk=iphonesimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/React.xcframework/ios-arm64_x86_64-simulator/React.framework +RN_SPM_REACT_BINARY[sdk=iphonesimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/React.xcframework/ios-arm64_x86_64-simulator/React.framework/React +RN_SPM_REACT_SEARCH_PATH[sdk=iphonesimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/React.xcframework/ios-arm64_x86_64-simulator +RN_SPM_REACT_FRAMEWORK[sdk=macosx*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/React.xcframework/ios-arm64_x86_64-maccatalyst/React.framework +RN_SPM_REACT_BINARY[sdk=macosx*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/React.xcframework/ios-arm64_x86_64-maccatalyst/React.framework/React +RN_SPM_REACT_SEARCH_PATH[sdk=macosx*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/React.xcframework/ios-arm64_x86_64-maccatalyst +RN_SPM_REACT_NATIVE_DEPENDENCIES_FRAMEWORK[sdk=appletvos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/tvos-arm64/ReactNativeDependencies.framework +RN_SPM_REACT_NATIVE_DEPENDENCIES_BINARY[sdk=appletvos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/tvos-arm64/ReactNativeDependencies.framework/ReactNativeDependencies +RN_SPM_REACT_NATIVE_DEPENDENCIES_SEARCH_PATH[sdk=appletvos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/tvos-arm64 +RN_SPM_REACT_NATIVE_DEPENDENCIES_FRAMEWORK[sdk=appletvsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/tvos-arm64_x86_64-simulator/ReactNativeDependencies.framework +RN_SPM_REACT_NATIVE_DEPENDENCIES_BINARY[sdk=appletvsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/tvos-arm64_x86_64-simulator/ReactNativeDependencies.framework/ReactNativeDependencies +RN_SPM_REACT_NATIVE_DEPENDENCIES_SEARCH_PATH[sdk=appletvsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/tvos-arm64_x86_64-simulator +RN_SPM_REACT_NATIVE_DEPENDENCIES_FRAMEWORK[sdk=iphoneos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/ios-arm64/ReactNativeDependencies.framework +RN_SPM_REACT_NATIVE_DEPENDENCIES_BINARY[sdk=iphoneos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/ios-arm64/ReactNativeDependencies.framework/ReactNativeDependencies +RN_SPM_REACT_NATIVE_DEPENDENCIES_SEARCH_PATH[sdk=iphoneos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/ios-arm64 +RN_SPM_REACT_NATIVE_DEPENDENCIES_FRAMEWORK[sdk=iphonesimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/ios-arm64_x86_64-simulator/ReactNativeDependencies.framework +RN_SPM_REACT_NATIVE_DEPENDENCIES_BINARY[sdk=iphonesimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/ios-arm64_x86_64-simulator/ReactNativeDependencies.framework/ReactNativeDependencies +RN_SPM_REACT_NATIVE_DEPENDENCIES_SEARCH_PATH[sdk=iphonesimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/ios-arm64_x86_64-simulator +RN_SPM_REACT_NATIVE_DEPENDENCIES_FRAMEWORK[sdk=macosx*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/ios-arm64_x86_64-maccatalyst/ReactNativeDependencies.framework +RN_SPM_REACT_NATIVE_DEPENDENCIES_BINARY[sdk=macosx*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/ios-arm64_x86_64-maccatalyst/ReactNativeDependencies.framework/ReactNativeDependencies +RN_SPM_REACT_NATIVE_DEPENDENCIES_SEARCH_PATH[sdk=macosx*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/ios-arm64_x86_64-maccatalyst +RN_SPM_REACT_NATIVE_DEPENDENCIES_FRAMEWORK[sdk=xros*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/xros-arm64/ReactNativeDependencies.framework +RN_SPM_REACT_NATIVE_DEPENDENCIES_BINARY[sdk=xros*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/xros-arm64/ReactNativeDependencies.framework/ReactNativeDependencies +RN_SPM_REACT_NATIVE_DEPENDENCIES_SEARCH_PATH[sdk=xros*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/xros-arm64 +RN_SPM_REACT_NATIVE_DEPENDENCIES_FRAMEWORK[sdk=xrsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/xros-arm64_x86_64-simulator/ReactNativeDependencies.framework +RN_SPM_REACT_NATIVE_DEPENDENCIES_BINARY[sdk=xrsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/xros-arm64_x86_64-simulator/ReactNativeDependencies.framework/ReactNativeDependencies +RN_SPM_REACT_NATIVE_DEPENDENCIES_SEARCH_PATH[sdk=xrsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/ReactNativeDependencies.xcframework/xros-arm64_x86_64-simulator +RN_SPM_HERMES_FRAMEWORK[sdk=appletvos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/tvos-arm64/hermesvm.framework +RN_SPM_HERMES_BINARY[sdk=appletvos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/tvos-arm64/hermesvm.framework/hermesvm +RN_SPM_HERMES_SEARCH_PATH[sdk=appletvos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/tvos-arm64 +RN_SPM_HERMES_FRAMEWORK[sdk=appletvsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/tvos-arm64_x86_64-simulator/hermesvm.framework +RN_SPM_HERMES_BINARY[sdk=appletvsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/tvos-arm64_x86_64-simulator/hermesvm.framework/hermesvm +RN_SPM_HERMES_SEARCH_PATH[sdk=appletvsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/tvos-arm64_x86_64-simulator +RN_SPM_HERMES_FRAMEWORK[sdk=iphoneos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/ios-arm64/hermesvm.framework +RN_SPM_HERMES_BINARY[sdk=iphoneos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/ios-arm64/hermesvm.framework/hermesvm +RN_SPM_HERMES_SEARCH_PATH[sdk=iphoneos*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/ios-arm64 +RN_SPM_HERMES_FRAMEWORK[sdk=iphonesimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/ios-arm64_x86_64-simulator/hermesvm.framework +RN_SPM_HERMES_BINARY[sdk=iphonesimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/ios-arm64_x86_64-simulator/hermesvm.framework/hermesvm +RN_SPM_HERMES_SEARCH_PATH[sdk=iphonesimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/ios-arm64_x86_64-simulator +RN_SPM_HERMES_FRAMEWORK[sdk=macosx*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/ios-arm64_x86_64-maccatalyst/hermesvm.framework +RN_SPM_HERMES_BINARY[sdk=macosx*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/ios-arm64_x86_64-maccatalyst/hermesvm.framework/Versions/1/hermesvm +RN_SPM_HERMES_SEARCH_PATH[sdk=macosx*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/ios-arm64_x86_64-maccatalyst +RN_SPM_HERMES_FRAMEWORK[sdk=xros*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/xros-arm64/hermesvm.framework +RN_SPM_HERMES_BINARY[sdk=xros*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/xros-arm64/hermesvm.framework/hermesvm +RN_SPM_HERMES_SEARCH_PATH[sdk=xros*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/xros-arm64 +RN_SPM_HERMES_FRAMEWORK[sdk=xrsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/xros-arm64_x86_64-simulator/hermesvm.framework +RN_SPM_HERMES_BINARY[sdk=xrsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/xros-arm64_x86_64-simulator/hermesvm.framework/hermesvm +RN_SPM_HERMES_SEARCH_PATH[sdk=xrsimulator*] = $(SRCROOT)/build/xcframeworks/$(RN_SPM_FLAVOR)/hermes-engine.xcframework/xros-arm64_x86_64-simulator diff --git a/private/helloworld/ios/ReactNativeSPM/Scripts/embed-flavored-frameworks.sh b/private/helloworld/ios/ReactNativeSPM/Scripts/embed-flavored-frameworks.sh new file mode 100644 index 000000000000..bc3fd7c18ebc --- /dev/null +++ b/private/helloworld/ios/ReactNativeSPM/Scripts/embed-flavored-frameworks.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Generated by React Native on every `spm add` and `spm update`. +# Do not edit: changes are overwritten. + +set -euo pipefail + +destination="$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH" +mkdir -p "$destination" + +validate_framework() { + source="$1" + name="$2" + if [ -z "$source" ] || [ ! -d "$source" ]; then + echo "error: React Native SwiftPM framework '$name' is unavailable for configuration '$CONFIGURATION' and SDK '$SDK_NAME': $source" + exit 1 + fi + binary="${name%.framework}" + if [ ! -e "$source/$binary" ] && [ ! -e "$source/Versions/Current/$binary" ]; then + echo "error: React Native SwiftPM framework '$name' is invalid for configuration '$CONFIGURATION': expected $source/$binary or $source/Versions/Current/$binary" + exit 1 + fi +} + +copy_and_sign() { + source="$1" + name="$2" + /usr/bin/rsync -a --delete "$source/" "$destination/$name/" + if [ "${CODE_SIGNING_ALLOWED:-YES}" != "NO" ]; then + identity="${EXPANDED_CODE_SIGN_IDENTITY:--}" + if [ "$identity" = "-" ]; then + /usr/bin/codesign --force --sign - --timestamp=none --preserve-metadata=identifier,entitlements,flags "$destination/$name" + else + /usr/bin/codesign --force --sign "$identity" --preserve-metadata=identifier,entitlements,flags "$destination/$name" + fi + fi +} + +validate_framework "${RN_SPM_REACT_FRAMEWORK:-}" "React.framework" +validate_framework "${RN_SPM_REACT_NATIVE_DEPENDENCIES_FRAMEWORK:-}" "ReactNativeDependencies.framework" +validate_framework "${RN_SPM_HERMES_FRAMEWORK:-}" "hermesvm.framework" +copy_and_sign "${RN_SPM_REACT_FRAMEWORK:-}" "React.framework" +copy_and_sign "${RN_SPM_REACT_NATIVE_DEPENDENCIES_FRAMEWORK:-}" "ReactNativeDependencies.framework" +copy_and_sign "${RN_SPM_HERMES_FRAMEWORK:-}" "hermesvm.framework" diff --git a/private/helloworld/ios/ReactNativeSPM/Scripts/sync-autolinking.sh b/private/helloworld/ios/ReactNativeSPM/Scripts/sync-autolinking.sh new file mode 100644 index 000000000000..48f25f6a1648 --- /dev/null +++ b/private/helloworld/ios/ReactNativeSPM/Scripts/sync-autolinking.sh @@ -0,0 +1,226 @@ +#!/bin/bash +# Generated by React Native on every `spm add` and `spm update`. +# Do not edit: changes are overwritten. + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Resolve a node binary and the react-native package dir at BUILD TIME. +# --------------------------------------------------------------------------- +NODE_BINARY="${NODE_BINARY:-}" +if [ -z "$NODE_BINARY" ]; then + # Source RN's standard app-local node-path files. They reference vars that + # may be unset and may return non-zero, so relax nounset AND errexit while + # sourcing — a buggy user .xcode.env must degrade to PATH-based node + # resolution below, not silently abort every build. + set +eu + if [ -f "$SRCROOT/.xcode.env" ]; then + . "$SRCROOT/.xcode.env" + fi + if [ -f "$SRCROOT/.xcode.env.local" ]; then + . "$SRCROOT/.xcode.env.local" + fi + set -eu + NODE_BINARY="${NODE_BINARY:-}" +fi +if [ -z "$NODE_BINARY" ]; then + NODE_BINARY="$(command -v node 2>/dev/null || true)" +fi + +# Resolve react-native's dir FROM THE APP (require.resolve), not a +# generation-time baked path — the baked path goes stale in pnpm / hoisted +# stores. Fall back to the baked path if resolution fails or the resolved dir +# has no setup-apple-spm.js. +RN_DIR="" +if [ -n "$NODE_BINARY" ]; then + RN_DIR="$(cd "$SRCROOT" && "$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json'))" 2>/dev/null || true)" +fi +if [ -z "$RN_DIR" ] || [ ! -f "$RN_DIR/scripts/setup-apple-spm.js" ]; then + RN_DIR="../../../node_modules/react-native" +fi + +STAMP="$SRCROOT/build/generated/autolinking/.spm-sync-stamp" +PLUGIN_MISMATCH="$SRCROOT/build/generated/autolinking/.spm-plugin-mismatch" +PLUGIN_FRAMEWORKS="$SRCROOT/build/generated/autolinking/.spm-plugin-flavored-frameworks.json" +MARKER="${PROJECT_FILE_PATH:-}/.spm-injected.json" +STALE=0 + +# Find project root (where package.json lives — may be an ancestor of SRCROOT) +PROJECT_ROOT="$SRCROOT" +while [ "$PROJECT_ROOT" != "/" ] && [ ! -f "$PROJECT_ROOT/package.json" ]; do + PROJECT_ROOT="$(dirname "$PROJECT_ROOT")" +done +if [ ! -f "$PROJECT_ROOT/package.json" ]; then + PROJECT_ROOT="$SRCROOT" +fi + +# Check 1: dependency inputs (covers app projects after any package manager +# install), and the marker, which records the frameworks the project links. +for INPUT in \ + "$PROJECT_ROOT/package.json" \ + "$PROJECT_ROOT/react-native.config.js" \ + "$MARKER"; do + if [ -f "$INPUT" ] && [ "$INPUT" -nt "$STAMP" ]; then + STALE=1 + break + fi +done + +# Check workspace lockfiles and package-manager metadata. These cover package +# managers that do not reliably bump node_modules mtimes, and Yarn PnP projects +# that do not have node_modules at all. +if [ "$STALE" -eq 0 ]; then + DIR="$PROJECT_ROOT" + while [ "$DIR" != "/" ]; do + for INPUT in \ + "$DIR/package-lock.json" \ + "$DIR/npm-shrinkwrap.json" \ + "$DIR/yarn.lock" \ + "$DIR/pnpm-lock.yaml" \ + "$DIR/bun.lock" \ + "$DIR/bun.lockb" \ + "$DIR/.pnp.cjs" \ + "$DIR/.pnp.loader.mjs"; do + if [ -f "$INPUT" ] && [ "$INPUT" -nt "$STAMP" ]; then + STALE=1 + break + fi + done + if [ "$STALE" -eq 1 ]; then + break + fi + DIR="$(dirname "$DIR")" + done +fi + +# Check node_modules mtime. In monorepos, node_modules may be hoisted to any +# ancestor between the app package and the workspace root. +if [ "$STALE" -eq 0 ]; then + DIR="$PROJECT_ROOT" + while [ "$DIR" != "/" ]; do + NM_DIR="$DIR/node_modules" + if [ -d "$NM_DIR" ] && [ "$NM_DIR" -nt "$STAMP" ]; then + STALE=1 + break + fi + DIR="$(dirname "$DIR")" + done +fi + +# Also check the app root directly when SRCROOT is not the package root. +if [ "$STALE" -eq 0 ] && [ "$SRCROOT" != "$PROJECT_ROOT" ]; then + if [ -d "$SRCROOT/node_modules" ] && [ "$SRCROOT/node_modules" -nt "$STAMP" ]; then + STALE=1 + fi +fi + +# Check 1.5: watched paths (mixed dirs AND files). Dirs catch add/remove of +# source files in spm.modules and autolinked deps (dir mtime updates on both); +# files catch edits to a dep's checked-in Package.swift / plugin manifests that +# would not bump any parent dir mtime. A path that has VANISHED (renamed/moved +# module root) forces a re-sync so the autolinker surfaces the real, actionable +# config error rather than the build failing later on dangling-symlink noise. +WATCH_FILE="$SRCROOT/build/generated/autolinking/.spm-sync-watch-paths" +if [ "$STALE" -eq 0 ] && [ -f "$WATCH_FILE" ]; then + while IFS= read -r P; do + [ -z "$P" ] && continue + if [ -d "$P" ]; then + # .swiftpm holds Xcode's own per-user scheme state, which it rewrites + # during a build — reading it as a change makes every IDE build re-sync. + if [ -n "$(find "$P" -name .swiftpm -prune -o -newer "$STAMP" -print -quit 2>/dev/null)" ]; then + STALE=1 + break + fi + elif [ -f "$P" ]; then + if [ "$P" -nt "$STAMP" ]; then + STALE=1 + break + fi + else + STALE=1 + break + fi + done < "$WATCH_FILE" +fi + +# Watched paths that did not exist at the last sync. One that APPEARS (e.g. a +# plugin's precompiled output folder) forces a re-sync. +ABSENT_FILE="$SRCROOT/build/generated/autolinking/.spm-sync-watch-absent" +if [ "$STALE" -eq 0 ] && [ -f "$ABSENT_FILE" ]; then + while IFS= read -r P; do + [ -z "$P" ] && continue + if [ -e "$P" ]; then + STALE=1 + break + fi + done < "$ABSENT_FILE" +fi + +# Check 2: codegen spec files changed via git (covers monorepo after git pull) +if [ "$STALE" -eq 0 ] && [ -f "$STAMP" ]; then + STAMP_TIME=$(stat -f %m "$STAMP" 2>/dev/null || stat -c %Y "$STAMP" 2>/dev/null || echo 0) + LATEST_SPEC_COMMIT=$(git -C "$SRCROOT" log -1 --format=%ct -- '*.js' '*.ts' 2>/dev/null || echo 0) + if [ "$LATEST_SPEC_COMMIT" -gt "$STAMP_TIME" ]; then + STALE=1 + fi +fi + +if [ ! -f "$STAMP" ]; then + STALE=1 +fi + +# The last sync found a plugin framework mismatch and kept its errors. Until an +# input changes, report them again instead of re-running sync: the scheme +# pre-action and this build phase would otherwise both run it. spm add/update +# rewrite the plugin manifest without a sync, so that counts as a change too. +if [ -f "$PLUGIN_MISMATCH" ]; then + if [ "$STALE" -eq 0 ] && \ + [ ! "$MARKER" -nt "$PLUGIN_MISMATCH" ] && \ + [ ! "$PLUGIN_FRAMEWORKS" -nt "$PLUGIN_MISMATCH" ]; then + cat "$PLUGIN_MISMATCH" >&2 + exit 1 + fi + STALE=1 +fi + +# Re-sync codegen + autolinking when a dependency input changed. Runtime +# framework slots and Xcode linker settings are only changed by spm update. +if [ "$STALE" -eq 1 ]; then + echo "SPM sync inputs changed — re-syncing (codegen + autolinking)..." + + WITH_ENVIRONMENT="$RN_DIR/scripts/xcode/with-environment.sh" + + if [ -f "$WITH_ENVIRONMENT" ]; then + # with-environment.sh references PODS_ROOT and $1, which may be unset. + # Temporarily disable nounset to avoid failures when sourcing. + export PODS_ROOT="${PODS_ROOT:-$SRCROOT}" + set +u + . "$WITH_ENVIRONMENT" + set -u + fi + + cd "$SRCROOT" + # `|| RC=$?` so a non-zero exit is CAPTURED rather than aborting the phase + # under `set -e` — the whole point is to branch on the code below (2 = fail + # the build; other non-zero = warn but don't break). + RC=0 + if [ -n "$NODE_BINARY" ] && [ -f "$RN_DIR/scripts/setup-apple-spm.js" ]; then + # Direct, dependency-free dispatch (no `npx react-native`, which needs + # @react-native-community/cli). + "$NODE_BINARY" "$RN_DIR/scripts/setup-apple-spm.js" sync || RC=$? + elif command -v npx >/dev/null 2>&1; then + npx react-native spm sync || RC=$? + else + echo "warning: node/npx not found — skipping SPM sync" + fi + if [ "$RC" -eq 2 ]; then + # Exit 2 = a problem only a terminal command fixes (e.g. a dependency + # without Package.swift, or plugin frameworks the project does not link). + # Sync already printed `error:` lines that name the command, so Xcode + # shows them. Fail the build. + exit 1 + elif [ "$RC" -ne 0 ]; then + echo "warning: SPM sync failed — build may use stale codegen/autolinking" + fi +fi + diff --git a/private/helloworld/lib/filesystem.js b/private/helloworld/lib/filesystem.js index 5ef9df90a691..126a93396015 100644 --- a/private/helloworld/lib/filesystem.js +++ b/private/helloworld/lib/filesystem.js @@ -10,8 +10,6 @@ import debug from 'debug'; import {execSync, spawn} from 'node:child_process'; -import {existsSync} from 'node:fs'; -import path from 'node:path'; const logWatchman = debug('helloworld:cli:watchman'); @@ -49,15 +47,3 @@ export async function pauseWatchman(command: () => Promise) { } } } - -export function getExistingPath( - folder: string, - paths: ReadonlyArray, -): string | null { - for (const p of paths) { - if (existsSync(path.join(folder, p))) { - return p; - } - } - return null; -} diff --git a/private/helloworld/lib/ios.js b/private/helloworld/lib/ios.js index 4652497ac23a..9a3b2d0e00b5 100644 --- a/private/helloworld/lib/ios.js +++ b/private/helloworld/lib/ios.js @@ -12,8 +12,6 @@ import type {XcodeBuildSettings} from './xcode'; import type {Result} from 'execa'; import execa from 'execa'; -import fs from 'node:fs'; -import path from 'node:path'; export type IOSDevice = { lastBootedAt: Date, @@ -133,8 +131,8 @@ export function getXcodeBuildSettings( const {stdout} = execa.sync( 'xcodebuild', [ - '-workspace', - 'HelloWorld.xcworkspace', + '-project', + 'HelloWorld.xcodeproj', '-scheme', 'HelloWorld', '-configuration', @@ -148,10 +146,3 @@ export function getXcodeBuildSettings( ); return JSON.parse(stdout); } - -export function hasPodInstalled(iosPath: string, pkg: string): boolean { - const containsPackage = new RegExp(`\n - ${pkg} \\(.+\\):?\n`); - return containsPackage.test( - fs.readFileSync(path.join(iosPath, 'Podfile.lock'), 'utf8'), - ); -} diff --git a/private/helloworld/lib/xcode.js b/private/helloworld/lib/xcode.js index a47a485e2cf4..e640ed7b2aab 100644 --- a/private/helloworld/lib/xcode.js +++ b/private/helloworld/lib/xcode.js @@ -11,7 +11,7 @@ // For the complete list: // // xcodebuild \ -// -workspace HelloWorld.xcworkspace \ +// -project HelloWorld.xcodeproj \ // -scheme HelloWorld \ // -configuration Debug \ // -sdk iphonesimulator \ From 54ca50fe947f45b557b1043f1964bad2a02dd150 Mon Sep 17 00:00:00 2001 From: Christian Falch Date: Tue, 3 Nov 2026 12:58:09 +0100 Subject: [PATCH 2/2] Check the Xcode project, not the Podfile, after `spm add --deintegrate` `spm add --deintegrate` keeps the Podfile since #59368, so the RNTester check now looks for CocoaPods references in RNTesterPods.xcodeproj, as the SwiftPM workflows on main do. --- .github/actions/test-ios-rntester/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/test-ios-rntester/action.yml b/.github/actions/test-ios-rntester/action.yml index 5e1725abe0ff..2e9fd33d2b4f 100644 --- a/.github/actions/test-ios-rntester/action.yml +++ b/.github/actions/test-ios-rntester/action.yml @@ -60,11 +60,11 @@ runs: echo "::error::spm add did not inject SwiftPM: RNTesterPods.xcodeproj/.spm-injected.json is missing" exit 1 fi - if [[ -f Podfile ]] && grep -q 'use_react_native!' Podfile; then - echo "::error::spm add --deintegrate left use_react_native! in the Podfile" + if grep -q 'Pods-' RNTesterPods.xcodeproj/project.pbxproj; then + echo "::error::spm add --deintegrate left CocoaPods references in RNTesterPods.xcodeproj" exit 1 fi - echo "SwiftPM injected in place; Podfile de-integrated." + echo "SwiftPM injected in place; CocoaPods de-integrated." - name: Build RNTester shell: bash run: |