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
1 change: 1 addition & 0 deletions .claude/skills/build-compilation-dependencies/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ Keep it in sync with the podspec; how each podspec feature maps:
- **SwiftPM's manifest cache ignores files the manifest reads.** `~/Library/Caches/org.swift.swiftpm/manifests/manifest.db` is keyed by `Package.swift` contents + path + tools version + env + SwiftPM version (SwiftPM `ManifestLoader.CacheKey`), so a changed `package.json` section is invisible until `rm -rf ~/Library/Caches/org.swift.swiftpm/manifests` (Xcode ▸ File ▸ Packages ▸ Reset Package Caches) — verified 2026-10-19: an identical manifest kept FFmpeg on after the option flipped off; appending a comment to the manifest made it re-evaluate. When testing option changes, bust the cache or change the manifest text; a fresh DerivedData is not enough.
- **Never replace a published asset in place.** SwiftPM remembers each binary target's checksum per URL and refuses a changed artifact ("has changed checksum; this is a potential security risk"), so anyone who already resolved the tag breaks. Repacking always changes checksums (zip timestamps) — publish under a new tag instead.
- To test unpublished assets, rewrite a packed copy's binary targets to `binaryTarget(path: "<dir>/X.xcframework.zip")` (local zips work) and install it with `npm install --ignore-scripts` (a directory dependency otherwise runs `prepare`).
- **react-native-audio-worklets** has its own `Package.swift` (package/product `ReactNativeAudioWorklets`, the name 0.87 derives; `swiftpmConfig` repeats it and lists `react-native-audio-api` / `react-native-worklets` as `dependencies`). It depends on the sibling autolinked packages `../ReactNativeAudioApi` and `../RNWorklets` (worklets names itself `RNWorklets` via the `spm` block in its `react-native.config.js`; Package.swift since worklets 0.13.0). Our RNAudioAPI target exposes no headers, so the worklets target reaches `<audioapi/...>` with `.unsafeFlags(["-I", "<packageDirectory>/../ReactNativeAudioApi/common/cpp"])` — the SwiftPM counterpart of the podspec's `Headers/Public/RNAudioAPI` path (`headerSearchPath` cannot leave the package). It must define `DEBUG`/`NDEBUG` per configuration like RNAudioAPI, for the same reason: React Native's headers change class layouts on `NDEBUG`. (Our own headers only use `NDEBUG` inside inline function bodies, e.g. `BaseAudioContext::assertDriverMutexHeld`, which changes behaviour, not layout.) Verified 2026-10-19: Debug + Release on 0.88.0, Release on 0.87.1, and at runtime on the iOS 27 simulator (`NativeAudioWorkletsModule` resolves, `install()` returns true, AudioContext running).
- Verify in throwaway apps on the oldest (0.87.x) and newest supported RN: `@react-native-community/cli init … --version <exact x.y.z>` (a bare `0.87` finds no template), install an `npm pack --ignore-scripts` tarball under a unique file name, `cd ios && npx react-native spm --deintegrate --yes` (both versions pick `add`/`update` themselves), then `xcodebuild -project <App>.xcodeproj`.

---
Expand Down
5 changes: 5 additions & 0 deletions packages/audiodocs/docs/other/swift-package-manager.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ Native iOS apps. To find out what it requires and how to switch an app to it, se
other library. There is no extra setup step. Swift Package Manager downloads the prebuilt
Opus / Ogg / Vorbis (and, if enabled, FFmpeg) libraries while it resolves packages.

## Worklets

[`react-native-audio-worklets`](../worklets/introduction.mdx) ships a `Package.swift` as well. It
builds against `react-native-worklets`, which supports Swift Package Manager from version 0.13.0.

Comment thread
mdydek marked this conversation as resolved.
## Build options

[`enableFFmpeg` and `disableStaticExternalLibs`](./prebuilt-libraries-support.mdx#setting-the-options)
Expand Down
86 changes: 86 additions & 0 deletions packages/react-native-audio-worklets/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// swift-tools-version: 6.0
import Foundation
import PackageDescription

let packageDirectory = Context.packageDirectory

func readPackageVersion() -> String {
let url = URL(fileURLWithPath: packageDirectory).appendingPathComponent("package.json")
guard let data = try? Data(contentsOf: url),
let packageJSON = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
let version = packageJSON["version"] as? String
else {
fatalError("[RNAudioWorklets] Cannot read the version from \(url.path)")
}
return version
}

/// React Native autolinks every library as a sibling under
/// `<app>/ios/build/generated/autolinking/libs/<SwiftName>`, which is also where the `.package`
/// dependencies below point. react-native-audio-api's target exposes no headers (a module over
/// every engine header is not wanted), so its `<audioapi/...>` headers are reached by path, the
/// SwiftPM counterpart of the podspec's `Headers/Public/RNAudioAPI` search path.
let audioAPIHeadersPath = URL(fileURLWithPath: packageDirectory)
.deletingLastPathComponent()
.appendingPathComponent("ReactNativeAudioApi/common/cpp")
.path

let headerSearchPaths = ["common/cpp", "ios"]
let defines: [(name: String, value: String)] = [
("RCT_NEW_ARCH_ENABLED", "1"),
("AUDIO_WORKLETS_VERSION", readPackageVersion()),
]

// React Native's headers change class layouts on NDEBUG (without it `DebugStringConvertible`
// gains virtual methods) and the prebuilt React framework is built with NDEBUG in Release, so
// DEBUG/NDEBUG follow the configuration exactly, as in RNAudioAPI and React Native's own targets.
let cSettings: [CSetting] =
headerSearchPaths.map { .headerSearchPath($0) } + defines.map { .define($0.name, to: $0.value) } + [
.define("DEBUG", to: "1", .when(configuration: .debug)),
.define("NDEBUG", .when(configuration: .release)),
.unsafeFlags(["-I", audioAPIHeadersPath]),
]
let cxxSettings: [CXXSetting] =
headerSearchPaths.map { .headerSearchPath($0) } + defines.map { .define($0.name, to: $0.value) } + [
.define("DEBUG", to: "1", .when(configuration: .debug)),
.define("NDEBUG", .when(configuration: .release)),
.unsafeFlags(["-I", audioAPIHeadersPath]),
]

let reactHeaders: [Target.Dependency] = [
.product(name: "ReactHeaders", package: "ReactNative"),
.product(name: "ReactNativeHeaders", package: "ReactNative"),
.product(name: "ReactNativeDependenciesHeaders", package: "ReactNative"),
.product(name: "ReactAppHeaders", package: "React-GeneratedCode"),
]

// The package and product name is what React Native 0.87 derives from the npm name; 0.88 reads it
// from `swiftpmConfig.name` in package.json, which repeats it.
let package = Package(
name: "ReactNativeAudioWorklets",
platforms: [.iOS(.v15)],
products: [
.library(name: "ReactNativeAudioWorklets", targets: ["RNAudioWorklets"])
],
dependencies: [
.package(name: "ReactNative", path: "../../../../xcframeworks"),
.package(name: "React-GeneratedCode", path: "../../../ios"),
.package(name: "ReactNativeAudioApi", path: "../ReactNativeAudioApi"),
.package(name: "RNWorklets", path: "../RNWorklets"),
],
targets: [
.target(
name: "RNAudioWorklets",
dependencies: reactHeaders + [
.product(name: "ReactNativeAudioApi", package: "ReactNativeAudioApi"),
.product(name: "RNWorklets", package: "RNWorklets"),
],
path: ".",
sources: ["common/cpp/audioworklets", "ios/audioworklets"],
publicHeadersPath: "ios/spm/include",
cSettings: cSettings,
cxxSettings: cxxSettings
)
],
cxxLanguageStandard: .cxx20
)
2 changes: 2 additions & 0 deletions packages/react-native-audio-worklets/ios/spm/include/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// The RNAudioWorklets SwiftPM target exposes no headers, but SwiftPM fails to resolve the package
// when a target's public headers directory is missing, so Package.swift points publicHeadersPath here.
11 changes: 10 additions & 1 deletion packages/react-native-audio-worklets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@
"android/",
"CMakeLists.txt",
"RNAudioWorklets.podspec",
"Package.swift",
"react-native.config.js",
"!**/__tests__",
"!**/__mocks__",
"!**/.*",
"!**/node_modules"
"!**/node_modules",
"ios/spm/include/.gitkeep"
],
"scripts": {
"test": "jest",
Expand Down Expand Up @@ -143,6 +145,13 @@
]
]
},
"swiftpmConfig": {
"name": "ReactNativeAudioWorklets",
"dependencies": [
"react-native-audio-api",
"react-native-worklets"
]
},
"codegenConfig": {
"name": "rnaudioworklets",
"type": "modules",
Expand Down
Loading