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
23 changes: 23 additions & 0 deletions .changeset/chilly-trains-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
"cmake-rn": minor
---

Add support for building projects declaring multiple shared object libraries into Node-API addons.

Each addon is emitted next to the sources it was built from, so that a project
declaring many addons produces the same layout as building each of them on its
own. Both the location and the name of an artifact are derived from the target
that produced it:

- `--out` supports a new `{targetSourceDir}` placeholder, expanding to the source
directory of the target being emitted, and now defaults to
`{targetSourceDir}/build/{configuration}`. This resolves to the same path as
before, unless `--build` is pointed outside of the source directory.
- The artifact is named after the target's `OUTPUT_NAME` rather than the CMake
target name. These are the same unless `OUTPUT_NAME` is set explicitly, which is
how a project can give its targets the unique names CMake requires without
affecting the name of the addon.

Also adds `--concurrency`, limiting how many build tasks run at once. It defaults
to the available parallelism, or to 1 when `--verbose` is enabled, since
interleaved output from concurrent builds is hard to read.
15 changes: 15 additions & 0 deletions .changeset/gentle-pandas-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"cmake-rn": minor
---

Let a consumer override the Android `ANDROID_STL` CMake cache variable via
the existing `-D`/`--define` option (e.g. `--define ANDROID_STL=c++_static`).
It still defaults to `c++_shared`, matching what React Native itself uses,
but an addon that must match a prebuilt third-party dependency's STL, or one
that's genuinely self-contained, can now ask for a different value.

This also fixes an ordering bug where a `--define` targeting any of the
Android platform's own default CMake variables (including `ANDROID_STL`) was
silently discarded: our hardcoded defaults were appended to the CMake
command line _after_ the user-provided `-D` arguments, and CMake resolves a
cache variable set multiple times via `-D` to its last occurrence.
10 changes: 10 additions & 0 deletions .changeset/real-emus-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"gyp-to-cmake": minor
---

Add --namespaced-targets to allow a root project to add many sub-projects.

CMake requires target names to be unique across a project tree, so sub-projects
that each declare an `addon` target cannot be added to a single root project. This
prefixes the target name with the project name, while setting `OUTPUT_NAME` so the
artifact keeps the name a `require` resolves against.
10 changes: 10 additions & 0 deletions .changeset/release-debug-logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"react-native-node-api": patch
---

Stop emitting `log_debug`'s per-addon diagnostic chatter (library
found/loaded, symbol resolution, ...) in release builds. It is now compiled
out in `NDEBUG` builds (CMake's `Release`/`MinSizeRel`/`RelWithDebInfo`
configurations, and Xcode's default `Release` configuration), mirroring React
Native's own dev/release logging split. `log_warning` and `log_error` are
unaffected and keep firing in every build type.
5 changes: 5 additions & 0 deletions .changeset/silly-cobras-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"cmake-rn": minor
---

Add a `--code-signing-allowed` flag to `cmake-rn`. `CODE_SIGNING_ALLOWED=NO` remains the default (needed for the free-standing dynamic libraries we produce), but a consumer who needs signed binaries in the XCFramework can now pass `--code-signing-allowed` to opt in.
5 changes: 5 additions & 0 deletions .changeset/wet-carrots-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ferric-cli": patch
---

Add `--dts-only` flag to `ferric build`, generating just the TypeScript declaration file and JS entrypoint without cross-compiling any Android/Apple binaries. It still runs a real host `cargo build` (napi-rs has no lighter typegen-only mode), so it's meant for regenerating a checked-in declarations fixture rather than for environments without a Rust toolchain.
27 changes: 27 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,33 @@ Guidance specific to Claude Code sessions that open pull requests against this
repository (including automated/scheduled sessions). See the root `CLAUDE.md`
and `AGENTS.md` for everything else.

## Keep inline comments very brief — put the color in the PR description

Default to **no comment at all**. Write one only when it carries knowledge a
reader cannot get from the code itself plus a `git blame` pointing at the PR
that introduced it. When you do write one, keep it to a line or two.

Rationale, rejected alternatives, benchmark numbers, "we tried X and it
didn't work", links to upstream issues, and anything that reads as a history
lesson belong in the **PR description** (and, where user-facing, the
changeset) — not in the source. Those places are where a reader who has
already found the line via `git blame` will end up anyway, and they don't
have to be maintained as the code around them changes.

Concretely, do not write comments that:

- restate what the next line already says;
- explain why an alternative implementation was _not_ chosen;
- narrate the change (`// now compiled out in release builds`) — that is a
commit message, and it goes stale the moment the code moves;
- document a well-known toolchain fact (e.g. what `NDEBUG` means) that a
reader can look up.

Comments that _do_ earn their place: a non-obvious constraint the compiler or
platform imposes, a workaround with the exact condition that makes it
removable (see the upstream-fix guidance in `AGENTS.md`), or a subtle
invariant a future edit could silently break.

## Attach CI labels when you open a PR

`.github/workflows/check.yml`'s `pull_request` trigger only fires on
Expand Down
70 changes: 48 additions & 22 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,18 @@ jobs:
uses: hendrikmuhs/ccache-action@v1.2.24
with:
key: ${{ github.job }}-${{ runner.os }}
# Set up JDK and Android SDK only because we need weak-node-api, to build ferric-example and to run the linting
# TODO: Remove this once we have a way to run linting without building the native code
- name: Set up JDK 17
uses: actions/setup-java@v6
with:
java-version: "17"
distribution: "temurin"
- name: Setup Android SDK
uses: android-actions/setup-android@v4
with:
packages: tools platform-tools ndk;${{ env.NDK_VERSION }}
- run: rustup target add x86_64-linux-android
- run: pnpm install
- run: pnpm run build
# Bootstrap weak-node-api and ferric-example to get types
# TODO: Solve this by adding an option to ferric to build only types or by committing the types into the repo as a fixture for an "init" command
- run: pnpm --filter weak-node-api run bootstrap
- run: pnpm --filter @react-native-node-api/ferric-example run bootstrap
# Generate the TypeScript/C++ declarations that other packages' type-checking
# depends on, without building any native binaries: weak-node-api's
# "prebuild:prepare" only copies headers and runs codegen (needs clang-format,
# set up above, but no JDK/Android SDK/NDK). ferric-example's declarations are
# committed as a fixture instead (see packages/ferric-example/.gitignore) —
# napi-rs's dts generation has no way to run without a real `cargo build`
# (confirmed: it leaves a populated target/ directory), so unlike
# weak-node-api's codegen it can't be reproduced here without reintroducing a
# native build into the fastest-feedback job. See #414.
- run: pnpm --filter weak-node-api run prebuild:prepare
- run: pnpm run lint
env:
DEBUG: eslint:eslint
Expand Down Expand Up @@ -372,6 +366,9 @@ jobs:
- name: Build ferric-example for all architectures
run: pnpm run build --android
working-directory: packages/ferric-example
- name: Verify committed ferric-example TypeScript declarations are up to date
run: git diff --exit-code -- ferric_example.d.ts ferric_example.js
working-directory: packages/ferric-example
- name: Run tests (Android)
timeout-minutes: 75
uses: reactivecircus/android-emulator-runner@v2
Expand Down Expand Up @@ -439,18 +436,28 @@ jobs:
# Build Ferric example for all Apple architectures
- run: pnpm exec ferric --apple
working-directory: packages/ferric-example
- name: Verify committed ferric-example TypeScript declarations are up to date
run: git diff --exit-code -- ferric_example.d.ts ferric_example.js
working-directory: packages/ferric-example
- name: Inspect the structure of the prebuilt binary
run: lipo -info ferric_example.apple.node/*/libferric_example.framework/libferric_example > lipo-info.txt
run: |
lipo -info ferric_example.apple.node/*/libferric_example.framework/libferric_example > lipo-output.txt
otool -L ferric_example.apple.node/*/libferric_example.framework/libferric_example > otool-output.txt
working-directory: packages/ferric-example
- name: Upload lipo info
- name: Upload lipo output
uses: actions/upload-artifact@v7
with:
name: lipo-output
path: packages/ferric-example/lipo-output.txt
- name: Upload otool output
uses: actions/upload-artifact@v7
with:
name: lipo-info
path: packages/ferric-example/lipo-info.txt
name: otool-output
path: packages/ferric-example/otool-output.txt
- name: Verify Apple triplet builds
run: |
# Create expected fixture content
cat > expected-lipo-info.txt << 'EOF'
cat > expected-lipo-output.txt << 'EOF'
Architectures in the fat file: ferric_example.apple.node/ios-arm64_x86_64-simulator/libferric_example.framework/libferric_example are: x86_64 arm64
Architectures in the fat file: ferric_example.apple.node/macos-arm64_x86_64/libferric_example.framework/libferric_example are: x86_64 arm64
Architectures in the fat file: ferric_example.apple.node/tvos-arm64_x86_64-simulator/libferric_example.framework/libferric_example are: x86_64 arm64
Expand All @@ -460,5 +467,24 @@ jobs:
Non-fat file: ferric_example.apple.node/xros-arm64/libferric_example.framework/libferric_example is architecture: arm64
EOF
# Compare with expected fixture (will fail if files differ)
diff expected-lipo-info.txt lipo-info.txt
diff expected-lipo-output.txt lipo-output.txt
# Verify every binary depends on the weak-node-api framework.
# otool -L prints one header line per file, or one per architecture
# when the file is fat, so the number of headers is exactly the number
# of weak-node-api dependencies we expect. Deriving it beats
# hard-coding a count, which silently rots whenever a triplet is added
# or dropped.
# macOS frameworks use the versioned bundle layout, so their install
# name is .../weak-node-api.framework/Versions/<version>/weak-node-api
# where the embedded platforms get the flat
# .../weak-node-api.framework/weak-node-api — hence the optional
# "Versions/<version>/" in the pattern.
SLICE_COUNT=$(grep -c "^ferric_example\.apple\.node/.*:$" otool-output.txt || true)
WEAK_NODE_API_COUNT=$(grep -cE "@rpath/weak-node-api\.framework/(Versions/[^/]+/)?weak-node-api" otool-output.txt || true)
echo "Found $WEAK_NODE_API_COUNT weak-node-api dependencies across $SLICE_COUNT binaries"
if [ "$SLICE_COUNT" -eq 0 ] || [ "$WEAK_NODE_API_COUNT" -ne "$SLICE_COUNT" ]; then
echo "Expected $SLICE_COUNT dependencies on the weak-node-api framework (one per binary), found $WEAK_NODE_API_COUNT"
cat otool-output.txt
exit 1
fi
working-directory: packages/ferric-example
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ packages/node-addon-examples/examples
packages/node-tests/node
packages/node-tests/tests
packages/node-tests/*.generated.js

# Committed napi-rs codegen fixture (see packages/ferric-example/.gitignore) — left
# in napi-rs's own output formatting so `pnpm run build:types` reproduces it exactly
# and the CI drift check (see .github/workflows/check.yml) doesn't false-positive on
# formatting alone.
packages/ferric-example/ferric_example.d.ts
packages/ferric-example/ferric_example.js
Loading
Loading