Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions packages/skia/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,43 @@ The npm packages this library consumes (`react-native-skia-android`, `react-nati

Back in this repo, bump the prebuilt binary versions in `packages/skia/package.json` (`react-native-skia-android` and `react-native-skia-apple-*`) to the versions you just published, run `yarn`, and re-run `pod install` in the example app so it consumes the released binaries. Drop the throwaway `node_modules` and `libs/ios/.version` edits from step 3.

### Swift Package Manager (preview)

CocoaPods stays the default. `Package.swift` is additive: SwiftPM ignores the
podspec, and CocoaPods ignores `Package.swift`.

SwiftPM support requires **React Native 0.87 or newer** β€” earlier releases ship
no `scripts/spm`. `apps/example` is on an older version, so it cannot exercise
this path.

Autolinking references the library through a symlink at
`<app>/ios/build/generated/autolinking/libs/ReactNativeSkia`, and SwiftPM
resolves the manifest's relative paths against that symlink rather than against
`packages/skia`. The two React Native package paths are therefore identical for
every standard app. The target name is pinned in `react-native.config.js`;
without it a future React Native release would derive it from the podspec
instead and change the header import prefix.

Skia's Apple sources still gate on `RCT_NEW_ARCH_ENABLED` and
`RCT_REMOVE_LEGACY_ARCH`. CocoaPods forces both project-wide; the SwiftPM path
defines neither, so `Package.swift` defines them itself.

#### Binaries

The manifest links the `react-native-skia-apple-ios` npm package, the same one
the CocoaPods build uses, so no network is needed once dependencies are
installed. It is resolved by path, from either a sibling in `node_modules` or
this monorepo's root, and the manifest fails with an explanatory message when
neither exists β€” which is what an `--omit=optional` install looks like.

Fetching the binaries from a released Swift package instead is future work; it
becomes useful only once the binary npm packages are no longer dependencies. See
[wcandillon/react-native-skia-binaries](https://github.com/wcandillon/react-native-skia-binaries).

After changing which binaries a checkout uses, delete
`ios/<App>.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved`
β€” a stale pin silently keeps the previous source.

### Publishing

- Run the commands in the [Building](#building) section
Expand Down
117 changes: 117 additions & 0 deletions packages/skia/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// swift-tools-version: 6.0
//
// SwiftPM manifest for @shopify/react-native-skia. iOS, Ganesh.
// Additive: the CocoaPods podspec remains the supported default.
//
// React Native 0.87+ references a library that ships its own Package.swift
// through a symlink at <app>/ios/build/generated/autolinking/libs/<name>, and
// resolves the relative paths below against that symlink. They are the same
// for every standard app, because the symlink location is.

import Foundation
import PackageDescription

// Skia's xcframeworks are ~200MB and are not carried in this package. They come
// from the react-native-skia-apple-ios npm package, which @shopify/react-native-skia
// depends on, so a checkout that has installed its dependencies builds offline.
// Xcode passes the symlink as the package directory, hence resolvingSymlinks.
let packageRoot = URL(fileURLWithPath: Context.packageDirectory)
.resolvingSymlinksInPath().path

let binariesPath = [
"../../react-native-skia-apple-ios", // consumer: sibling in node_modules
"../../node_modules/react-native-skia-apple-ios", // this monorepo
]
.map { "\(packageRoot)/\($0)" }
.first { FileManager.default.fileExists(atPath: "\($0)/Package.swift") }

guard let binariesPath else {
// Reached when the package was skipped at install time (`--omit=optional`,
// or a non-Apple `os` filter), which SwiftPM would otherwise report as an
// unresolvable dependency path.
fatalError(
"""
react-native-skia-apple-ios was not found next to @shopify/react-native-skia. \
It ships the prebuilt Skia binaries this package links against. Reinstall \
dependencies without --omit=optional, on macOS.
""")
}

let package = Package(
name: "ReactNativeSkia",
platforms: [.iOS(.v15)],
products: [
// Autolinking looks this name up verbatim; react-native.config.js pins it.
.library(name: "ReactNativeSkia", targets: ["ReactNativeSkia"])
],
dependencies: [
.package(name: "React-GeneratedCode", path: "../../../ios"),
.package(name: "ReactNative", path: "../../../../xcframeworks"),
.package(path: binariesPath),
],
targets: [
.target(
name: "ReactNativeSkia",
dependencies: [
.product(name: "ReactHeaders", package: "ReactNative"),
.product(name: "ReactNativeHeaders", package: "ReactNative"),
.product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"),
.product(name: "ReactAppHeaders", package: "React-GeneratedCode"),
.product(name: "react-native-skia-apple-ios", package: "react-native-skia-apple-ios"),
],
// apple/ and cpp/ have no common ancestor below the package root, and
// .headerSearchPath cannot escape the target path.
path: ".",
exclude: [
"cpp/rnskia/RNDawnWindowContext.cpp", // Graphite only
"cpp/rnskia/RNDawnInterop.cpp", // Graphite only
],
// Explicit, so SwiftPM never walks node_modules, lib, android, or the
// headers-only cpp/skia β€” which carries x86 AVX skcms sources that
// cannot build for arm64.
sources: [
"apple",
"cpp/api",
"cpp/jsi",
"cpp/rnskia",
"cpp/utils",
// libskottie.a needs skjson and Apple builds no libskjson archive.
// CocoaPods compiles this via its cpp/**/*.cpp glob.
"cpp/skia/modules/jsonreader/SkJSONReader.cpp",
],
cxxSettings: [
// Replaces the podspec's recursive cpp/** glob, which expands to 90.
.headerSearchPath("cpp"), // "api/…", "jsi/…", "utils/…"
.headerSearchPath("cpp/skia"), // "include/core/…", "modules/…", "src/…"
.headerSearchPath("cpp/rnskia"), // apple/ uses bare "RNSkView.h"
.headerSearchPath("cpp/utils"), // apple/ uses bare "RNSkLog.h"

// CocoaPods forces both project-wide; the SwiftPM path defines neither.
// Skia's Apple sources still gate on them: without them RNSkiaModule's
// legacy branch fails to compile and -getTurboModule: is dropped, so
// the JSI bindings never install.
.define("RCT_NEW_ARCH_ENABLED", to: "1"),
.define("RCT_REMOVE_LEGACY_ARCH", to: "1"),

.define("SK_METAL", to: "1"),
.define("SK_GANESH", to: "1"),
.define("SK_IMAGE_READ_PIXELS_DISABLE_LEGACY_API", to: "1"),
.define("SK_DISABLE_LEGACY_SHAPER_FACTORY", to: "1"),

// React's prebuilt C++ ABI is NDEBUG-gated, and Skia derives
// SK_RELEASE from it. Omitting either breaks the Release link.
.define("DEBUG", .when(configuration: .debug)),
.define("NDEBUG", .when(configuration: .release)),
],
linkerSettings: [
.linkedFramework("MetalKit"),
.linkedFramework("AVFoundation"),
.linkedFramework("AVKit"),
.linkedFramework("CoreMedia"),
]
)
],
// React Native's headers need C++20, and so does Skia m152: SkMathPriv.h
// calls std::countl_zero, std::countr_zero and std::popcount.
cxxLanguageStandard: .cxx20
)
6 changes: 6 additions & 0 deletions packages/skia/include/ReactNativeSkia.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once

// Umbrella header for the SwiftPM target's publicHeadersPath. Nothing outside
// this package includes Skia or RNSkia headers β€” React Native resolves the
// Fabric component and TurboModule by class name β€” so the public surface is
// deliberately empty.
3 changes: 3 additions & 0 deletions packages/skia/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
"cpp/**/*.{h,cpp}",
"apple/**",
"react-native-skia.podspec",
"Package.swift",
"include/**",
"react-native.config.js",
"dist/**",
"libs/.graphite"
],
Expand Down
5 changes: 5 additions & 0 deletions packages/skia/react-native.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Pins the SwiftPM target name. Without it the name is derived β€” today from the
// npm name (ReactNativeSkia), but an in-flight React Native change derives it
// from the podspec instead (react-native-skia). The name is also the header
// import prefix, so it must not move.
module.exports = {spm: {name: 'ReactNativeSkia'}};
Loading