Skip to content

[RN][iOS] Update SwiftPM to build from source - #52909

Draft
cipolleschi wants to merge 42 commits into
mainfrom
cipolleschi/use-spm-for-rntester
Draft

[RN][iOS] Update SwiftPM to build from source#52909
cipolleschi wants to merge 42 commits into
mainfrom
cipolleschi/use-spm-for-rntester

Conversation

@cipolleschi

@cipolleschi cipolleschi commented Jul 29, 2025

Copy link
Copy Markdown
Contributor

Summary:

30/07/2025

Prepare SwiftPM to build RNTester and HelloWorld from source.
The first step has been to create a new iOS app (which won't be committed in the end) and to update the Package.swift file.

I had to split the React-FabricComponent in subpackages because the automatically generated modulemap of React-Fabric and React-FabricComponent where overlapping, and the build was failing.

Once fixed that, I had to add a couple of hardcoded search paths in the porjects:

  • one to point to the react-native/.build/headers folder
  • one to point to the folder containing the Yoga.h header.

31/07/2025

To create the Packages for ReactCodegen and ReactAppDependencyProvider, I first needed a project with them.
To get there, I started by integrating the SwiftPM build from source in the HelloWorld app, which is configured to have codegen already.

HelloWorld was failing due to Swift incompatibility.
Swift incompatibility was due to 2 reasons:

  1. it was doing an @import React, but we don't have a React module. We have a React_Core module.
  2. The React target was importing also all the files in the Cxx* folders, and those were used to create the modulemap. This implied that a lot of files which included C++ code will be part of the modulemap and thus exposed to Swift. And Swift can't build with C++ references. In the Podspec, those files headers are private headers, see here, thus they are not part of the modulemap.

Fix @import React

This got fixed by renaming the reactCore module's name from React-Core to just React

Fix C++ headers being visible to Swift

To fix this, I created a script in the react-native/scripts/swiftpm folder to create symlinks in a includes/React folder inside the react-native/React folder.
For the time being, I'm using an hardcoded list of headers, taken from the same React-umbrella.h file we use for the prebuilds.
The includes folder is gitignored.

unfortunately, the includes folder is not the default way to build prebuilds yet. So, I introduced a BUILD_FROM_SOURCE variable that can be toggled in the Package.swift file to decide how to build.

Current state

This brought HelloWorld to build the React module properly and to being able to import it in the app.
Hello world is still failing because it can't build the ReactAppDependencyProvider and the ReactCodegen packages.

01/08/2025

I managed to have HelloWorld building and running using only SwiftPM! 🎉 🎉 🎉

The process is still very manual and I need to work on automating it as it is quite complicated.
To build and run Helloworld, I had to first create a Pacake.swift file for ReactCodegen and for ReactAppDependencyProvider, make sure that the packages can be built in isolation, connect the packages with the app, fix the Xcodeproject.

Create the Package.swift for React-Codegenerated code

The code that is generated by codegen has a structure that is not aligned with the iOS standard.
In fact, all the code is generated inside the build/generated/ios folder and it is flat.
However, SwiftPM requires for each target to live in its own folder.

The changes that I have to apply are:

  1. move the RCTAppDependecyProvider.h RCTAppDependecyProvider.m and the ReactAppDependencyProvider.podspec in a ReactAppDependencyProvider folder.
  2. mode all the other files in a ReactCodegen folder.
  3. create the following Package.swift file in the build/generated/ios folder
Package.swift file
Details
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "React-GeneratedCode",
    platforms: [.iOS(.v15), .macCatalyst(SupportedPlatform.MacCatalystVersion.v13)],
    products: [
        // Products define the executables and libraries a package produces, making them visible to other packages.
        .library(
            name: "ReactCodegen",
            targets: ["ReactCodegen"]),
        .library(
            name: "ReactAppDependencyProvider",
            targets: ["ReactAppDependencyProvider"]),
    ],
    dependencies: [
      .package(name: "React", path: "../../../../../../packages/react-native/")
    ],
    targets: [
        // Targets are the basic building blocks of a package, defining a module or a test suite.
        // Targets can depend on other targets in this package and products from dependencies.
        .target(
            name: "ReactCodegen",
            dependencies: ["React"],
            path: "ReactCodegen",
            exclude: ["ReactCodegen.podspec"],
            publicHeadersPath: ".",
            cSettings: [
              .headerSearchPath("headers")
            ],
            cxxSettings: [
              .headerSearchPath("headers"),
              .unsafeFlags(["-std=c++20"]),
            ],
            linkerSettings: [
              .linkedFramework("Foundation")
            ]
        ),
        .target(
          name: "ReactAppDependencyProvider",
          dependencies: ["ReactCodegen"],
          path: "ReactAppDependencyProvider",
          exclude: ["ReactAppDependencyProvider.podspec"],
          publicHeadersPath: ".",
          cSettings: [
            .headerSearchPath("headers"),
          ],
          cxxSettings: [
            .headerSearchPath("headers"),
            .unsafeFlags(["-std=c++20"]),
          ],
          linkerSettings: [
            .linkedFramework("Foundation")
          ]
        )
    ]
)

Notice that the path to react-native is hardcoded locally to Helloworld, and it would have to be computed when generating it.

These manual changes needs to be ported to codegen, as codegen is the part of the infrastructure responsible for generating all these files.

Swift Packages should build in isolation. The search path should be relative to the package.

This is the part that concerns me the most, as it might affect how we build React Native apps also with prebuilds.
A Swift PM package should be able to build in isolation.
Packages that depends on other packages needs visibility over the public headers of those packages. However, when building all the packages from source, the headers of a dependency are not automatically visible to the package that depends on them.
There are several questions and post on SwiftPM about this problem:

Unfortunately, headerSearchPaths in SwiftPM must be relative to the package itself, or SwiftPM will not build.
This forces us to replicate the headers that we need inside a headers folder of the packages (so ReactCodegen/headers and ReactAppDependencyProvider/headers) and set the headerSerachPath approprietely.

This can be a big problem for third party libraries: if react-native-reanimated exposes a Package.swift file, they will also have to "add" a headers folder and "add" symlinks to the react-native headers.

Solution: we can probably fix this issue with the script that prepare an iOS project. When we run autolinking, we can inspect the dependencies of the library and create hard links to the required headers in all the dependencies.
We would have to verify how time consuming this process would be.

Building and running.

At this point, I only had to turn on the build from source and adjust the REACT_NATIVE_PATH build setting because it was computed starting from the Pods folder, which does not exists anymore, instead of using the $(PROJECT_DIR) folder

04/08/2025

I updated codegen to properly generate the files in the ReactCodegen and in the ReactAppDependencyProvider folders.
I also add the codegen code to generate the Package.swift for ReactCodegen and ReactAppDependencyProvider.

We are missing the code that creates links to the header files.

14/08/2024

These couple of days, I worked on automating the process to prepare an app to be built with SPM.
I created various scripts to do so.

What's left to do is to properly integrate Xcode with the Package.swift files

15/08/2025

I implemented a step to modify the Xcodeproj. What is missing is a bit of cleanup and making sure not to duplicate the entries we already have.

18/08/2025

Fixed the generation of the Xcodeproj so that we don't use a json format.

20/08/2025

Started working with the integration in RNTester

  • updated how we get the iosFolder path, to support setups where there is no ios folder, such as... RNTester
  • fixed platform-specific headers which were still nested
  • fixed yoga headers

There are some other headers-related failures:

  • RCTDeprecation which is not part of react-native
  • RCTRequired not visible to SwiftPM

TODO:

  • 0 - Write the Package.swift for ReactCodegen and ReactAppDependencyProvider (we probably need a single Package.swift with two targets. This has to be generated by Codegen. We probably need to add the $(PROJECT_DIR)/build/generated/ios folder in the header search path of the project.)
  • 1 - Apply the same changes to HelloWorld
    • 1.a - HelloWorld has @import React in the AppDelegate that will probably fail due to 4 below.
    • 1.b - Automate the changes to build and run HelloWorld in codegen.
    • 1.c - add script to create links of the header files in the codegen folders.
    • 1.d - Automatically integrate with the Package.swift files
    • 1.e - in the script that update the xcode project we need to add a step to deintegrate the targets, to avoid duplicating them.
  • 2 - Move the changes to RNTester
  • 3 - Create Package.swift for RCTPushNotification
  • 4 - Fix why SwiftTest.swift doesn't work (@import React doesn't work. @import React_Core fails for C++ symbols. We probably will have to split the reactCore package in subpackages.)
  • 5 - Create Package.swift for the additional modules in RNTester
    • 5.1 MyNativeView
    • 5.2 NativeCxxModuleExample
    • 5.3 NativeModuleExample
    • 5.4 RCTTest.podspec (if needed)

Changelog:

[iOS][Changed] - Prepare SwiftPM to build from source

Test Plan:

Build locally

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jul 29, 2025
@cipolleschi
cipolleschi force-pushed the cipolleschi/use-spm-for-rntester branch 2 times, most recently from a751861 to dee403e Compare July 31, 2025 16:36
@cipolleschi
cipolleschi force-pushed the cipolleschi/use-spm-for-rntester branch 3 times, most recently from 0d6ca8e to 0b5026f Compare September 4, 2025 14:30
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D81765871.

1 similar comment
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D81765871.

@cipolleschi
cipolleschi force-pushed the cipolleschi/use-spm-for-rntester branch from 2750c31 to 482dda6 Compare September 9, 2025 14:15
@facebook-github-bot

Copy link
Copy Markdown
Contributor

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this in D81765871.

@react-native-bot

Copy link
Copy Markdown
Collaborator

This PR is stale because it has been open for 180 days with no activity. It will be closed in 7 days unless you comment on it or remove the "Stale" label.

@react-native-bot

Copy link
Copy Markdown
Collaborator

This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

@react-native-bot react-native-bot added Stale There has been a lack of activity on this issue and it may be closed soon. and removed Stale There has been a lack of activity on this issue and it may be closed soon. labels Mar 9, 2026
@react-native-bot

Copy link
Copy Markdown
Collaborator

This PR is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

@react-native-bot react-native-bot added the Stale There has been a lack of activity on this issue and it may be closed soon. label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. p: Facebook Partner: Facebook Partner Stale There has been a lack of activity on this issue and it may be closed soon.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants