Skip to content

feat(🍏): Swift Package Manager support for iOS - #4043

Open
chrfalch wants to merge 3 commits into
Shopify:mainfrom
chrfalch:spm-support
Open

feat(🍏): Swift Package Manager support for iOS#4043
chrfalch wants to merge 3 commits into
Shopify:mainfrom
chrfalch:spm-support

Conversation

@chrfalch

@chrfalch chrfalch commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Motivation

CocoaPods trunk goes read-only on 2 December 2026, and React Native 0.87 ships preview Swift Package Manager support. @shopify/react-native-skia is CocoaPods-only today; this makes it consumable as a Swift package.

Purely additive: SwiftPM ignores the podspec, CocoaPods ignores Package.swift. iOS and Ganesh only. pod install in apps/example still works.

What is here

File
Package.swift one target rooted at path: "."
include/ReactNativeSkia.h umbrella for publicHeadersPath, deliberately empty
react-native.config.js pins the target name, which is also the header import prefix
package.json those three added to files, so consumers receive the manifest

Why the manifest looks the way it does, all documented in CONTRIBUTING.md:

  • One target. .headerSearchPath cannot escape the target path, so an apple/-rooted target would need ../cpp/....
  • The relative package paths are constant. Autolinking references a self-managed library through a symlink under the app's build directory, and SwiftPM resolves relative .package(path:) against that symlink, not against packages/skia.
  • It defines RCT_NEW_ARCH_ENABLED and RCT_REMOVE_LEGACY_ARCH itself. CocoaPods forces both project-wide; React Native's SwiftPM path sets neither, and Skia's Apple sources gate on them in 21 places. Without them -getTurboModule: is dropped and the JSI bindings never install.
  • C++20. Skia m152's SkMathPriv.h calls std::countl_zero, std::countr_zero and std::popcount; the podspec's c++17 pin is already stale.
  • Four header search paths, where the podspec's recursive glob expands to 90.
  • SkJSONReader.cpp is listed explicitlylibskottie.a needs four skjson:: symbols that no Apple archive defines.
  • Remote mode (RN_SPM_REMOTE_URL) is unsupported: it rewrites the React Native package identity, so one committed manifest cannot serve both modes.

Binaries come from the react-native-skia-apple-ios npm package, the same one CocoaPods uses, resolved by path so an installed checkout builds offline. When it is missing, which is what an --omit=optional install looks like, the manifest fails with an explanatory message instead of an opaque resolution error. Fetching them from a released Swift package instead is future work, worth doing only once those npm packages are no longer dependencies; see wcandillon/react-native-skia-binaries#9.

Other Apple platforms

Not a dead end. Skia's sources are already portable: RNSkUIKit.h maps the platform view to UIView or NSView. The react-native-skia-apple-macos and -tvos packages already ship correct manifests.

Three blockers, all upstream:

  1. React Native declares iOS only. Its SwiftPM manifests hardcode platforms: [.iOS(.v15)]. A package cannot declare macOS if its dependencies do not. The SDK plumbing already maps appletvos* and macosx*, so this is a declaration gap, not a redesign.
  2. Both platforms live in forks. react-native-macos is on 0.81.8 and ships no scripts/spm. tvOS runs on react-native-tvos. Each needs SwiftPM support first.
  3. Target names collide. One repository hosts one Swift package, and every platform calls its xcframework libskia. That needs suffixed targets or merged xcframeworks — see feat: publish the Skia binaries as a remote Swift package wcandillon/react-native-skia-binaries#9.

Then this manifest needs three edits: widen platforms:, make the linked frameworks conditional (UIKit or AppKit), and point the binaries probe at the matching package. Android and web are unaffected, since SwiftPM is Apple-only.

Test plan

CI does not exercise this PR on its own; a follow-up adds an example app and the build-test-ios-spm job. With that harness:

  • Red: spm add with no manifest exits 2 naming the library. Green: it resolves as self-managed.
  • Debug and Release build with 0 errors, and the app renders a Skia canvas on the simulator.
  • The remote binaryTarget(url:) download and checksum enforcement were proven against a test release on a fork.
  • The missing-binaries path was checked too: hiding the npm package makes the manifest fail with its own message.
  • No regression: pod install in apps/example succeeds.

Re-verified with matching versions after a clean root install: react-native-skia-apple-ios 152.0.0 against milestone-152 headers, Release build, rendered on an iPhone 17 Pro simulator.

chrfalch and others added 2 commits September 8, 2026 13:02
React Native 0.87 ships preview SwiftPM support, and CocoaPods trunk goes
read-only in December 2026. A library that ships its own Package.swift is
referenced verbatim by autolinking, so this is additive: SwiftPM ignores the
podspec and CocoaPods ignores Package.swift.

Autolinking exposes the library through a symlink under the app's build
directory and resolves the manifest's relative paths against it, so the two
React Native package paths are fixed for every standard app.

Skia's binaries stay out of the package. The manifest prefers the
react-native-skia-apple-ios copy npm already installs for the CocoaPods build
and falls back to the published Swift package, so a checkout that has run
`yarn install` builds without network access.

Two defines the podspec never needed: CocoaPods forces RCT_NEW_ARCH_ENABLED
and RCT_REMOVE_LEGACY_ARCH project-wide, and the SwiftPM path defines neither,
yet Skia's Apple sources still gate on both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Covers the React Native 0.87 floor, how autolinking resolves the manifest's
relative paths, the two architecture defines CocoaPods supplies but SwiftPM
does not, where the binaries come from, and the stale Package.resolved pin
that silently keeps the previous binary source when switching between them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wcandillon
wcandillon self-requested a review September 8, 2026 19:23

@wcandillon wcandillon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR (or it could be an another PR) needs to update the https://github.com/Shopify/react-native-skia/blob/main/.github/workflows/build-skia-graphite.yml to publish artifacts that can be used by SPM? e.g an .xcframework as a .zip file? Is my understanding correct that this would be the very first step? From there we can test the publication of the Package.swift?

@chrfalch

Copy link
Copy Markdown
Contributor Author

No change needed there — build-skia-graphite.yml already publishes what's required.

The binaries repo downloads its …-apple-ios-xcframeworks-m152.tar.gz, extracts the xcframeworks, then zips each one and computes the SHA-256 itself (wcandillon/react-native-skia-binaries#9). So the zipping happens downstream, and the .tar.gz stays the interchange format.

That makes the order: land #9 → cut a Graphite release from it. This PR doesn't depend on either step — it links the binaries from the npm package by path, the same as CocoaPods does today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants