Skip to content
Open
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
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -464,3 +464,39 @@ jobs:
# - name: Build example for macOS Catalyst
# working-directory: apps/example/ios
# run: xcodebuild -workspace example.xcworkspace -scheme ReactTestApp -destination 'platform=macOS,arch=arm64,variant=Mac Catalyst' -configuration Debug build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO

build-test-ios-spm:
if: ${{ github.repository == 'shopify/react-native-skia' }}
runs-on: macos-latest-xlarge
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5.0.0
with:
submodules: recursive

- name: Setup
uses: ./.github/actions/setup
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

# spm-example sits outside the yarn workspace so its React Native can
# differ from the one apps/example pins. It ships no lockfile; react and
# react-native are pinned exactly in its package.json.
- name: Install spm-example dependencies
working-directory: spm-example
run: npm install

# The .xcodeproj is committed already injected, so this refreshes the
# injection, runs codegen and downloads the React Native artifacts. It
# exits 2 if a linked library ships no Package.swift.
- name: Set up Swift Package Manager
working-directory: spm-example/ios
run: npx react-native spm update

- name: Build spm-example for iOS
working-directory: spm-example/ios
run: |
xcodebuild -project SpmExample.xcodeproj -scheme SpmExample \
-configuration Debug -sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO build
12 changes: 12 additions & 0 deletions packages/skia/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,18 @@ SwiftPM support requires **React Native 0.87 or newer** β€” earlier releases shi
no `scripts/spm`. `apps/example` is on an older version, so it cannot exercise
this path.

The harness is `spm-example/` at the repo root, deliberately outside the yarn
workspace so its React Native does not collide with the workspace's. Its
[README](../../spm-example/README.md) covers the details; the short form is:

```sh
cd spm-example && npm install
cd ios && npx react-native spm update
xcodebuild -project SpmExample.xcodeproj -scheme SpmExample \
-configuration Debug -sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' CODE_SIGNING_ALLOWED=NO build
```

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
Expand Down
4 changes: 4 additions & 0 deletions spm-example/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: '@react-native',
};
78 changes: 78 additions & 0 deletions spm-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# OSX
#
.DS_Store

# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
**/.xcode.env.local

# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
.cxx/
*.keystore
!debug.keystore
.kotlin/

# node.js
#
node_modules/
# Not committed: package.json pins react and react-native exactly, and CI
# installs with `npm install`.
package-lock.json
npm-debug.log
yarn-error.log

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/

**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output

# Bundle artifact
*.jsbundle

# Ruby / CocoaPods
**/Pods/
/vendor/bundle/

# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*

# testing
/coverage

# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
5 changes: 5 additions & 0 deletions spm-example/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
arrowParens: 'avoid',
singleQuote: true,
trailingComma: 'all',
};
1 change: 1 addition & 0 deletions spm-example/.watchmanconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
20 changes: 20 additions & 0 deletions spm-example/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import {StyleSheet, View} from 'react-native';
import {Canvas, Circle, Fill} from '@shopify/react-native-skia';

export default function App() {
return (
<View style={styles.container}>
<Canvas style={styles.canvas}>
<Fill color="#1c1c22" />
<Circle cx={160} cy={160} r={110} color="#f5c518" />
<Circle cx={230} cy={130} r={60} color="#e8484f" />
</Canvas>
</View>
);
}

const styles = StyleSheet.create({
container: {flex: 1, alignItems: 'center', justifyContent: 'center'},
canvas: {width: 320, height: 320},
});
60 changes: 60 additions & 0 deletions spm-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# SpmExample

A minimal React Native app that consumes `@shopify/react-native-skia` through
**Swift Package Manager** instead of CocoaPods. It exists to prove
`packages/skia/Package.swift` actually builds and renders, and it is what the
`build-test-ios-spm` CI job runs.

iOS only, and Ganesh only. CocoaPods remains the supported default for this
library; see the Swift Package Manager section of
[`packages/skia/CONTRIBUTING.md`](../packages/skia/CONTRIBUTING.md) for the
design notes.

## Why it sits outside the yarn workspace

Swift Package Manager support requires **React Native 0.87 or newer** β€” earlier
releases ship no `scripts/spm`. `apps/example` is pinned to an older version, so
this app keeps its own `node_modules` and installs with npm. It ships no
lockfile: `react` and `react-native` are pinned exactly in `package.json`.

`@shopify/react-native-skia` is linked from the workspace with
`file:../packages/skia`, so it builds the working tree, not a published release.

## Build and run

```sh
npm install
cd ios
npx react-native spm add --deintegrate # injects the Swift packages into the .xcodeproj

xcodebuild -project SpmExample.xcodeproj -scheme SpmExample \
-configuration Release -sdk iphonesimulator \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath build/dd-release CODE_SIGNING_ALLOWED=NO build
```

Release embeds its own JS bundle, so it needs no Metro. For Debug, run
`npm start` first.

## Gotchas

- **Stale `Package.resolved`.** Xcode caches package pins in
`ios/SpmExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved`.
When switching the Skia binaries between the npm-installed copy and a released
Swift package, delete it first or the old source is silently kept.
- **`spm update` re-adds an absolute `HERMES_CLI_PATH`.** On React Native
0.87.1 the injector writes this machine's own path to `hermesc` into
`project.pbxproj` and `.spm-injected.json`. The build does not need it, so it
is deliberately not committed. Strip it again before committing:
```sh
sed -i '' '/HERMES_CLI_PATH = /d' ios/SpmExample.xcodeproj/project.pbxproj
sed -i '' '/^ "HERMES_CLI_PATH",$/d' ios/SpmExample.xcodeproj/.spm-injected.json
```
react/react-native#58292 proposes removing the write upstream. Until that is
in a React Native release, the strip stays necessary on every commit.
- **The Podfile is not for CocoaPods.** React Native's CLI finds the iOS
project by searching for a Podfile, so an SwiftPM-only app still has to ship
one. It installs nothing and raises if `pod install` is ever run.
- **Two React copies.** `metro.config.js` forces `react` and `react-native` to
this app's own copies. Without that, Skia's components resolve the workspace
root's React and hooks fail.
4 changes: 4 additions & 0 deletions spm-example/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "SpmExample",
"displayName": "SpmExample"
}
3 changes: 3 additions & 0 deletions spm-example/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
};
9 changes: 9 additions & 0 deletions spm-example/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* @format
*/

import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';

AppRegistry.registerComponent(appName, () => App);
6 changes: 6 additions & 0 deletions spm-example/ios/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

# SPM – auto-generated at build time (do not commit)
Package.resolved
build/generated/
build/xcframeworks/
.build/
11 changes: 11 additions & 0 deletions spm-example/ios/.xcode.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This `.xcode.env` file is versioned and is used to source the environment
# used when running script phases inside Xcode.
# To customize your local environment, you can create an `.xcode.env.local`
# file that is not versioned.

# NODE_BINARY variable contains the PATH to the node executable.
#
# Customize the NODE_BINARY variable here.
# For example, to use nvm with brew, add the following line
# . "$(brew --prefix nvm)/nvm.sh" --no-use
export NODE_BINARY=$(command -v node)
11 changes: 11 additions & 0 deletions spm-example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This app builds with Swift Package Manager, not CocoaPods. The Podfile is
# still required: React Native's CLI locates the iOS project by searching for
# one (see @react-native-community/cli-config-apple), and without it
# `react-native spm` and the Xcode autolinking phase both fail with
# "CLI config did not provide project.ios.sourceDir".
#
# Nothing ever installs it β€” only CocoaPods evaluates this file, so the raise
# below turns an accidental `pod install` into a clear message instead of
# silently re-integrating CocoaPods and breaking the Swift package graph.
raise 'SpmExample builds with Swift Package Manager. Do not run `pod install` ' \
'here: it would re-integrate CocoaPods and break the Swift package graph.'
Loading
Loading