- name
- build-compilation-dependencies
- description
- Build system overview for react-native-audio-api across all platforms — CMakeLists.txt, android/build.gradle, RNAudioAPI.podspec, prebuilt external libraries, and the standalone C++ test build. Use this skill when adding a new source file, modifying CMakeLists or podspec, debugging compilation errors, integrating a new dependency, or understanding why includes work differently in tests vs the app. Trigger phrases: "add source file", "CMakeLists", "podspec", "build.gradle", "prebuilt binaries", "FFmpeg disabled", "pod install", "new architecture", "compile error", "undefined symbol", "SIMD", "worklets build flag", "C++ tests", "conditional compilation", "include path", "gradle build fails", "link error", "yarn catalog", "catalog:", "add a dependency", "bump a dependency", "yarn workspaces", "npmMinimalAgeGate", "shared dependency version".
# Skill: Build, Compilation & Dependencies
For deep CMake/Gradle/podspec analysis see [build-details.md](build-details.md).
---
## Repository Build Overview
```
react-native-audio-api/
├── package.json # Yarn 4 workspaces root
├── packages/react-native-audio-api/ # Main library
│ ├── android/
│ │ ├── build.gradle # Android build config (Gradle)
│ │ ├── CMakeLists.txt # Android CMake root (delegates to subdirectory)
│ │ └── src/main/cpp/audioapi/
│ │ └── CMakeLists.txt # Actual Android C++ build target
│ ├── common/cpp/audioapi/ # Shared C++ (used by all platforms)
│ │ ├── decoding/ # Decoder factory, backends, SeekDecoderDaemon, AudioDecoding
│ │ ├── encoding/ # AudioEncoder interface, EncoderCapabilities, OS encoder/remux selector headers, AudioFileConcatenator
│ │ ├── libs/ # Third-party wrappers (FFmpeg, miniaudio, pffft, …)
│ │ └── external/ # Prebuilt binaries per platform
│ │ ├── android/ # .a static libs (Opus, Ogg, Vorbis, OpenSSL)
│ │ ├── iphoneos/ # iOS device .a libs
│ │ ├── iphonesimulator/ # iOS sim .a libs
│ │ ├── macosx/ # macOS .a libs
│ │ ├── ffmpeg_ios/ # FFmpeg .xcframeworks (iOS only)
│ │ ├── include/ # Headers for Opus/Ogg/Vorbis/OpenSSL
│ │ └── include_ffmpeg/ # Headers for FFmpeg
│ ├── common/cpp/test/
│ │ ├── CMakeLists.txt # Standalone test build (no Android/iOS)
│ │ ├── run-tests.sh / filters.sh # smoke|extended|full (+ categories, CI path filters)
│ │ └── src/ # Google Test files
│ ├── RNAudioAPI.podspec # CocoaPods spec for iOS
│ └── scripts/
│ ├── download-prebuilt-binaries.sh # Downloads externals from GitHub Releases
│ ├── rnaa_utils.rb # Ruby helpers for podspec (path resolution, worklets check)
│ └── validate-worklets-version.js
└── apps/
└── fabric-example/
└── ios/
└── Podfile # Consumer Podfile (new arch enabled)
```
---
## Yarn Workspaces & Dependency Catalog
Dependency versions live in a single catalog in the root `.yarnrc.yml`, referenced from
manifests as `"<dep>": "catalog:"`. Bump once there and every workspace follows.
Needs Yarn >= 4.10 (repo is on 4.18).
**What belongs in the catalog** — anything that must move in lockstep, which is broader
than "used more than once":
- used by two or more workspaces, **or**
- version-locked to something already catalogued even if only one workspace uses it —
`react-test-renderer` must equal `react`; `@react-native-community/cli*` and
`@babel/runtime` move with `react-native` / `@babel/core`.
A dependency used in exactly one workspace with no such coupling stays a literal range:
it has no second copy to drift from, and catalogue membership is meant to *signal*
"keep this in lockstep". There is no CI check enforcing this — review is the enforcement,
which is why membership has to carry meaning.
**Workflow when touching dependencies:**
1. Changing the version of a catalogued dep → edit the catalog entry, not the manifest.
2. Adding a dep → check the catalog first. Already there? Use `"catalog:"`. Not there but
lockstep-coupled or now used twice? Add the entry and switch both call sites over.
Three kinds of dependency must **never** be given `catalog:`:
| Never catalogue | Why |
|---|---|
| `peerDependencies` | Public API surface of the published packages, deliberately loose (`"*"`). Pinning narrows what consumers may install. |
| Runtime `dependencies` of published packages (`semver`, `chalk`, `commander`, `fs-extra`) | The release pipeline packs with `npm pack` (`scripts/create-package.sh`, `npm-custom-node-generator-build.yml`) and npm does not understand `catalog:`. It publishes the literal string, producing an uninstallable package. Only `yarn pack` / `yarn npm publish` perform the swap. |
| `workspace:*` ranges | Not version ranges — nothing to centralise. |
Other constraints:
- **`resolutions` does not accept `catalog:`** — install fails with `typescript@catalog: isn't supported by any available resolver`. Root `package.json` pins `typescript` literally; keep it in sync with the catalog entry. It is *not* redundant with the catalog: it forces `@commitlint/load`'s optional `typescript` peer (`^4.6.4 || ^5.2.2`) onto our version, and catalogs only rewrite workspace manifests, never transitive dependencies.
- **`packages/audiodocs` is a separate Yarn 1 project** with its own lockfile and `node_modules`. It is not in root `workspaces`, so the catalog does not reach it.
- **`yarn pack` is not a drop-in for `npm pack` here.** It drops `development/react/` (a nested `package.json` listed in `files[]`) and strips the executable bit from 29 prebuilt FFmpeg binaries. Do not switch the release pipeline to it without re-verifying the tarball contents.
Supply-chain gate: `npmMinimalAgeGate: '1w'` refuses versions published in the last 7 days.
Bypass one command with `yarn add --no-time-gate` / `yarn up --no-time-gate`, or exempt
specific packages permanently via `npmPreapprovedPackages`.
---
## C++ Header File Extensions
In `common/cpp/audioapi/`: `.hpp` = header-only templates; `.h` = non-template (usually with a `.cpp`). Vendored code is excluded.
---
## Prebuilt Binaries
External libraries (Opus, Ogg, Vorbis, OpenSSL, FFmpeg) are **not compiled from source** — they are downloaded as prebuilt `.a` / `.so` / `.xcframework` archives from:
```
https://github.com/software-mansion-labs/rn-audio-libs/releases/download/<TAG>/
```
Current tag: **v3.0.0** (see `scripts/download-prebuilt-binaries.sh`).
The download script is triggered automatically:
- **iOS**: by podspec `prepare_command` during `pod install` **and** by a `script_phase` (`[CP-User] Download RNAudioAPI prebuilt binaries`, `:before_headers`, always-out-of-date) on every Xcode build — downloads `ffmpeg_ios`, `iphoneos`, `iphonesimulator`, `macosx`. `prepare_command` ensures all vendored `.xcframework`s exist when CocoaPods generates `[CP] Copy XCFrameworks` (required for correct FFmpeg linking). The build-time phase handles CI setups that cache `Pods/` independently from `node_modules/` — it is a fast no-op when binaries are already present. **It must use `:before_headers`, not `:before_compile`** — CocoaPods inserts its own `[CP] Copy XCFrameworks` phase right after `Headers`; `:before_compile` would land *after* it. The phase's `output_files` declares both the `-force_load` static libs and the four FFmpeg xcframeworks. The whole phase is skipped only when *both* `DISABLE_AUDIOAPI_STATIC_EXTERNAL_LIBS` and `DISABLE_AUDIOAPI_FFMPEG` are set.
- **Android**: by `downloadPrebuiltBinaries` Gradle task, which runs before `preBuild` — downloads `android`, `jniLibs`
The script is idempotent — it skips any archive whose destination directory already exists, so the per-build invocations are fast no-ops once binaries are present.
Downloaded artifacts land in:
- `common/cpp/audioapi/external/android/<ABI>/` — `.a` static libs for Android ABIs
- `android/src/main/jniLibs/<ABI>/` — FFmpeg `.so` shared libs for Android (loaded at runtime)
- `common/cpp/audioapi/external/ffmpeg_ios/` — FFmpeg `.xcframework` files for iOS
- `common/cpp/audioapi/external/iphoneos/` / `iphonesimulator/` / `macosx/` — Opus/Ogg/etc `.a`
**These directories are gitignored.** If they're missing, run `pod install` (iOS) or Gradle build (Android) to re-download them. Do not commit them.
---
## Android Build — high-level summary
### Files
- `android/build.gradle` — Gradle library config
- `android/fix-prefab.gradle` — prefab publication workaround (ported from react-native-worklets)
- `android/CMakeLists.txt` — Android CMake root (SIMD detection, RN version flags, delegates to subdirectory)
- `android/src/main/cpp/audioapi/CMakeLists.txt` — actual build target (sources, prebuilt libs, include paths)
- `common/cpp/audioapi/EXTENSION_API.md` — stable C++ extension contract for dependent native modules
- `common/cpp/audioapi/compatibility/StableAPI.h` — single public C++ compatibility header for extensions
### Key behaviors
- Feature flags (`newArchEnabled`, `disableAudioapiFFmpeg`) are read from app's `gradle.properties` and forwarded to both CMake and Kotlin `BuildConfig`
- DSP sources always compiled with `-O3` regardless of overall build type
- Sources gathered with `GLOB_RECURSE CONFIGURE_DEPENDS` — CMake re-runs automatically when files are added/removed
- 16KB page size alignment enabled for Android 15+
- **Extension API (prefab)**: single public C++ header `<audioapi/compatibility/StableAPI.h>`; prefab publishes transitive headers needed to compile it (`prepareAudioApiHeadersForPrefabs`); `fix-prefab.gradle` ensures the `.so` is in prefab metadata. Contract: `EXTENSION_API.md`
- CMake exposes `COMMON_CPP_DIR` and `ANDROID_CPP_DIR` as **PUBLIC** include dirs so prefab consumers resolve `<audioapi/...>`
For full per-line analysis see [build-details.md](build-details.md#android-androidcmakeliststxt-root--detailed-analysis).
---
## iOS Build (CocoaPods) — high-level summary
### Files
- `RNAudioAPI.podspec` — library spec with subspecs
- `scripts/rnaa_utils.rb` — Ruby helpers called by podspec
- `apps/fabric-example/ios/Podfile` — consumer
### Key behaviors
- Four subspecs split compilation: `audioapi` (core C++), `audioapi/ios` (ObjC++), `audioapi/audioapi_dsp` (DSP with `-O3`), `audioapi/miniaudio_impl` (compiled as `-x objective-c++`)
- Static prebuilt libs linked with `-force_load` to prevent dead-stripping
- FFmpeg xcframeworks listed in `s.ios.vendored_frameworks` — CocoaPods handles embedding and signing
- `Accelerate` framework linked, enabling `HAVE_ACCELERATE=1` for vDSP SIMD on iOS
- Header search paths split: `pod_target_xcconfig` (library compilation) vs `xcconfig` (app consumers)
- `rnaa_utils.rb` resolves dynamic paths at `pod install` time (not hardcoded)
- JSI internals use distinct names (`HostObject`, `RuntimeInstanceCache`, `RuntimeObserver`) instead of generic names like `JsiHostObject` / `RuntimeAwareCache` / `RuntimeLifecycleMonitor` — `react-native-skia` publishes headers with those generic names and CocoaPods can resolve the wrong file when both libraries are in the same app
For full per-line analysis see [build-details.md](build-details.md#ios-rnaudioapipodspec--detailed-analysis).
---
## Building the Apps
### iOS (fabric-example)
```bash
# From the monorepo root first:
yarn install
# Then install pods — must be done from the ios/ directory:
cd apps/fabric-example/ios
pod install
# Run the app (from repo root):
yarn workspace fabric-example ios
# or open Xcode:
open apps/fabric-example/ios/FabricExample.xcworkspace
```
**When to re-run `pod install`**:
- After `yarn install` (any dependency change)
- After changing `RNAudioAPI.podspec`
- After adding/removing iOS source files that need to be picked up
- After changing `rnaa_utils.rb` or `scripts/validate-worklets-version.js`
- When prebuilt binaries need to be re-downloaded (podspec `prepare_command` runs on `pod install`)
**Disable FFmpeg on iOS**:
```bash
DISABLE_AUDIOAPI_FFMPEG=1 pod install
```
### Android (fabric-example)
```bash
yarn workspace fabric-example android
# or open in Android Studio:
open apps/fabric-example/android
```
**Disable FFmpeg on Android**: set in `android/gradle.properties`:
```
disableAudioapiFFmpeg=true
```
**Clean CMake cache** (fixes most mysterious native build failures):
```bash
yarn workspace react-native-audio-api clean # or manually:
rm -rf packages/react-native-audio-api/android/.cxx
```
---
## Local Full Validation
CI intentionally skips native Android/iOS builds (expensive). Use the tiered local validation script for pre-PR checks:
```bash
yarn validate:fast # CI parity (format, lint, typecheck, enum sync, build, C++ + JS tests)
yarn validate:cpp # C++ smoke
yarn validate:cpp-extended # C++ extended (all categories)
yarn validate:android # yarn workspace … build:android
yarn validate:ios # yarn workspace … build:ios (macOS only)
yarn validate:full # --fast + C++ extended + --android + --ios
```
Script: [`scripts/validate.sh`](../../../scripts/validate.sh) at monorepo root.
### What CI covers vs what validation tiers add
| Layer | CI (`ci.yml` + `tests.yml`) | Local tiers |
|---|---|---|
| TS build (`bob build`) | Yes | `--fast` |
| C++ smoke (`run-tests.sh`) | Yes | `--fast` |
| C++ coverage (`run-coverage.sh`, smoke, Clang) | Yes (`cpp-coverage` artifact) | `yarn test:cpp:coverage` |
| Jest | Yes | `--fast` |
| Extended C++ by category (e.g. graph) | Path change or manual dispatch in `tests.yml` | `--cpp-extended` |
| HostObjects (26 JSI `.cpp` files) | **No** | `--android` + `--ios` |
| Android JNI C++ + Kotlin | **No** | `--android` |
| iOS ObjC++ | **No** | `--ios` |
| Prebuilt libs (Opus, FFmpeg, etc.) | **No** | `--android` / `--ios` (via `download-prebuilt-binaries.sh`) |
| TurboModule codegen, worklets linking | **No** | `--android` / `--ios` |
The C++ test build excludes HostObjects, `AudioContext.cpp`, `FFmpegDecoding.cpp`, and worklet nodes — see [C++ Tests](#c-tests-standalone-build) below. Passing `yarn test` alone does not prove native layers compile.
### Shared prebuild phase (before native tiers)
Before `--android` or `--ios`, the script runs once:
1. `yarn install --immutable` + `yarn build`
2. [`download-prebuilt-binaries.sh`](../../../packages/react-native-audio-api/scripts/download-prebuilt-binaries.sh) `{android|ios}` (must run from `packages/react-native-audio-api/scripts/`)
3. `yarn workspace react-native-audio-api test:cpp`
Android (NDK) and iOS (Clang) cannot share object files — reuse is at the prebuild/download level. If `ccache` is installed, `validate.sh` wraps host `CC`/`CXX`; only the host C++ tests (`test:cpp`) honor that — Gradle/NDK and `xcodebuild` use their own toolchains and are unaffected.
### Platform skip behavior
- `--ios` on Linux → skip with message (exit 0)
- `--android` without `ANDROID_HOME` → fail on explicit `--android`; skip with warning inside `--full`
- `--full` includes C++ extended (all categories) after `--fast`’s smoke, so local full covers C++ full + native builds
### Which tier to run
See the decision table in [post-work-checks](../post-work-checks/SKILL.md).
---
عرض على GitHub