Skip to content

Add support for versioned darwin framework#355

Merged
kraenhansen merged 6 commits intomainfrom
kh/ferric-versioned-darwin-framework
Feb 14, 2026
Merged

Add support for versioned darwin framework#355
kraenhansen merged 6 commits intomainfrom
kh/ferric-versioned-darwin-framework

Conversation

@kraenhansen
Copy link
Collaborator

@kraenhansen kraenhansen commented Feb 11, 2026

Fixes #353 and #280.

Merging this PR will:

  • Add support for versioned framework when building for Darwin using Ferric and cmake-rn.

@kraenhansen kraenhansen self-assigned this Feb 11, 2026
@kraenhansen kraenhansen added Apple 🍎 Anything related to the Apple platform (iOS, macOS, Cocoapods, Xcode, XCFrameworks, etc.) CMake RN Our `cmake` wrapping CLI Host 🏡 Our `react-native-node-api-modules` package Ferric 🦀 MacOS 💻 Anything related to the Apple MacOS platform or React Native MacOS support labels Feb 11, 2026
@kraenhansen
Copy link
Collaborator Author

@cursor please review this

@cursor
Copy link

cursor bot commented Feb 11, 2026

Based on my review of the Pull Request, here's my assessment:

PR Review: Add support for versioned darwin framework

Summary

This PR implements versioned Apple framework bundle support for Darwin/macOS targets, addressing a previously noted TODO. The changes are well-structured and follow Apple's documented framework anatomy conventions.

Changes Reviewed

1. packages/host/src/node/prebuilds/apple.ts (+140 lines)

  • Good refactoring: extracted common logic into writeInfoPlist, writeFrameworkInfoPlist, and updateLibraryInstallName helper functions
  • Changed API from versioned: boolean to kind: "flat" | "versioned" - cleaner, more explicit
  • createVersionedFramework correctly implements the Apple framework bundle structure with Versions/A/, Versions/Current symlink, and Resources/Headers symlinks

2. packages/ferric/src/targets.ts (+60 lines)

  • Added comprehensive Apple target parsing with parseAppleTargetName()
  • Proper type guards (isAppleArchitecture, isAppleOperatingSystem, isAppleVariant)
  • Clean separation of architecture, OS, and variant components

3. packages/ferric/src/build.ts (~25 net lines)

  • Refactored combineAppleLibraries to return OS metadata along with paths
  • Logic now correctly routes darwin targets to versioned frameworks

4. packages/cmake-rn/src/platforms/apple.ts (+1 line)

  • Simple API update from versioned: true to kind: "versioned"

Issues Found

Bug in createFlatFramework (line 98 in apple.ts):

  await updateLibraryInstallName(libraryName, libraryName, frameworkPath);

The first argument should be newLibraryPath (the full path to the binary), not libraryName. Compare with the versioned framework implementation which correctly passes the path:

  await updateLibraryInstallName(
    path.join("Versions", VERSIONED_FRAMEWORK_VERSION, libraryName),
    libraryName,
    frameworkPath,
  );

The updateLibraryInstallName function signature is:

async function updateLibraryInstallName(binaryPath: string, libraryName: string, cwd: string)

So for flat frameworks, line 98 should be:

await updateLibraryInstallName(libraryName, libraryName, frameworkPath);

Wait, looking more carefully - since cwd is set to frameworkPath, and libraryName is just the filename (not a full path), this should actually work because install_name_tool will resolve libraryName relative to frameworkPath. However, this is inconsistent with the versioned framework call which uses a relative path. The behavior is correct but could be clearer.

Minor Suggestions

  1. Consider adding tests for createVersionedFramework - even a basic structure validation would be valuable given the complexity of the symlink setup.

  2. Type inconsistency: In build.ts, paths.length == 1 uses == while other comparisons use ===. Should be consistent:

        } else if (paths.length == 1) {

Overall Assessment

Approve with minor fixes. The implementation is solid and follows Apple conventions correctly. The refactoring improves code organization and the new parseAppleTargetName utility is well-designed.

The main item to address is the == vs === consistency issue. The rest of the code is clean and the changeset is appropriately scoped.

Open in Cursor Open in Web

@kraenhansen
Copy link
Collaborator Author

@cursor are you able to understand why the MacOS test app is failing? I've failed to reproduce this locally.

@cursor
Copy link

cursor bot commented Feb 12, 2026

Based on my analysis, the CI failure is not related to the versioned framework changes in this PR. Here's what I found:

Root Cause

The failure is a Metro bundler module resolution error:

UnableToResolveError: Unable to resolve module @react-native-node-api/node-addon-examples 
from /Users/runner/work/.../apps/macos-test-app/App.tsx

This happens during the "Bundle React Native code and images" build phase, which is pure JavaScript bundling - not native code compilation.

Why it might fail in CI but not locally

Looking at scripts/init-macos-test-app.ts, the macOS test app is dynamically generated and uses relative path dependencies:

"@react-native-node-api/node-addon-examples": path.relative(
  APP_PATH,
  path.join(ROOT_PATH, "packages", "node-addon-examples"),
),

The issue could be:

  1. Package not bootstrapped - The @react-native-node-api/node-addon-examples package requires npm run bootstrap to build its dist folder (see its "main": "dist/index.js")
  2. Metro symlink resolution - The metro.config.js might not properly resolve the symlinked workspace packages in CI
  3. Timing/ordering issue - The package might not be fully prepared before Metro tries to resolve it

Suggested investigation

  1. Check if npm run bootstrap successfully builds the node-addon-examples package before the macOS test runs
  2. Verify the metro.config.js is properly configured to watch/resolve workspace symlinks
  3. Check if there are any ordering issues in the CI workflow between bootstrap and test execution

This is a pre-existing issue with the macOS test app setup, not something introduced by the versioned framework changes in this PR.

Open in Cursor Open in Web

@kraenhansen
Copy link
Collaborator Author

@cursor it did pass on a very recent commit and it fails consistently on this PR https://github.com/callstackincubator/react-native-node-api/actions/runs/21864104696/job/63100603421

@cursor
Copy link

cursor bot commented Feb 12, 2026

Based on my investigation, I have an update on the CI failure:

Findings

The failure is a Metro module resolution error where @react-native-node-api/node-addon-examples cannot be found during the Xcode "Bundle React Native code and images" build phase. However, none of the changes in this PR affect Metro or JavaScript module resolution - all changes are in native framework compilation code (apple.ts, build.ts, targets.ts).

What I observed

  1. Both runs have identical npm install/bootstrap steps - They both install 970 packages in the macOS test app successfully
  2. PR Bump versions for MacOS test app to React Native v0.81.5 #352 passed on a separate branch (kh/macos-test-app-react-native-0.81.5) with the same macOS test setup
  3. The code changes only affect native Apple framework generation, not JavaScript bundling

Possible causes

  1. Race condition or flaky test - The macOS test app is dynamically generated and uses relative file dependencies. Metro's resolution of symlinked packages can be sensitive to timing or cache states.

  2. Metro watchFolders issue - The metro.config.js conditionally adds the repo root as a watchFolder only if config.watchFolders.length === 0. If @rnx-kit/metro-config behaves differently in some conditions, Metro won't see the packages/ directory.

  3. CMake version bump - I noticed you tried bumping CMAKE_VERSION to 4.2.2 in the workflow, though this shouldn't affect Metro bundling.

Recommendation

Try re-running the CI job to see if it's intermittent. If it consistently fails, you might want to:

  1. Add debug logging to the metro.config.js to print watchFolders
  2. Check if the symlinks in apps/macos-test-app/node_modules/@react-native-node-api/ exist after npm install
  3. Consider reverting the CMAKE_VERSION change to isolate variables

Open in Cursor Open in Web

@kraenhansen kraenhansen force-pushed the kh/ferric-versioned-darwin-framework branch from 712d3bc to 5984066 Compare February 14, 2026 20:51
@kraenhansen kraenhansen force-pushed the kh/ferric-versioned-darwin-framework branch from 5984066 to d3ff65c Compare February 14, 2026 20:54
@kraenhansen
Copy link
Collaborator Author

@cursor please have another look

@cursor
Copy link

cursor bot commented Feb 14, 2026

Good news! The CI is currently running and the fix from PR #356 is now included in this branch. The fix addressed exactly what I suspected:

Summary

The Metro module resolution issue has been fixed by PR #356 which:

  • Changed the condition from config.watchFolders.length === 0 to config.projectRoot.endsWith("macos-test-app")
  • This ensures the repo root watchFolder is always added for the macOS test app, regardless of what @rnx-kit/metro-config returns for watchFolders

Current CI Status

The latest CI run (22024111846) is currently in progress:

  • ✓ Lint passed
  • ✓ Unit tests (ubuntu-latest) passed
    • Test app (macOS) - running
    • Test app (iOS) - running
    • Other jobs in progress

The macOS test should now pass since the watchFolders fix is included. You can monitor the run at the CI page.

Open in Cursor Open in Web

@kraenhansen
Copy link
Collaborator Author

@cursor taking a step back from the CI issue, please review the changes this PR introduces now.

@cursor
Copy link

cursor bot commented Feb 14, 2026

PR Review: Add support for versioned darwin framework

Summary

This PR implements versioned Apple framework bundle support for Darwin/macOS targets, resolving a previously noted TODO. The implementation follows Apple's documented Framework Bundle Anatomy.

Changes Reviewed

1. packages/host/src/node/prebuilds/apple.ts (+156 lines, -52 lines)

Excellent refactoring:

  • Extracted reusable helpers: writeInfoPlist, writeFrameworkInfoPlist, updateLibraryInstallName
  • All helpers use object arguments instead of positional strings - much clearer and less error-prone
  • Changed API from versioned?: boolean to kind: "flat" | "versioned" - more explicit
  • createVersionedFramework correctly implements the Apple framework structure:
    • Versions/A/ directory with binary, Resources, Headers
    • Versions/Current symlink pointing to A
    • Top-level symlinks for Resources, Headers, and the binary

2. packages/ferric/src/targets.ts (+60 lines)

Clean implementation of Apple target parsing:

  • parseAppleTargetName() extracts architecture, OS, and variant from target strings
  • Proper type guards: isAppleArchitecture, isAppleOperatingSystem, isAppleVariant
  • Exported types: AppleArchitecture, AppleOperatingSystem, AppleVariant

3. packages/ferric/src/build.ts (~25 net lines)

  • Refactored combineAppleLibraries to return { os, path } objects instead of just paths
  • Uses parseAppleTargetName to determine OS
  • Framework creation now correctly routes darwin targets to versioned frameworks:
kind: library.os === "darwin" ? "versioned" : "flat"

4. packages/cmake-rn/src/platforms/apple.ts (+1 line)

Simple API update from versioned: true to kind: "versioned".

Code Quality

  • Object parameters: All new helper functions use object arguments - good pattern that was requested
  • Documentation: Excellent JSDoc comment on createVersionedFramework with ASCII diagram of structure
  • Type safety: Proper TypeScript types throughout
  • Error handling: Exhaustive check with throw new Error(Unexpected framework kind: ${kind as string}) for invalid kinds
  • Consistency: Fixed == to === in build.ts line 402

Overall Assessment

Approve. The implementation is solid, well-documented, and follows Apple conventions correctly. The refactoring improves code organization significantly, and the new parseAppleTargetName utility is well-designed and reusable.

Open in Cursor Open in Web

@kraenhansen kraenhansen merged commit 7349c34 into main Feb 14, 2026
9 checks passed
@kraenhansen kraenhansen deleted the kh/ferric-versioned-darwin-framework branch February 14, 2026 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Apple 🍎 Anything related to the Apple platform (iOS, macOS, Cocoapods, Xcode, XCFrameworks, etc.) CMake RN Our `cmake` wrapping CLI Ferric 🦀 Host 🏡 Our `react-native-node-api-modules` package MacOS 💻 Anything related to the Apple MacOS platform or React Native MacOS support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Assemble a versioned framework bundle for MacOS

2 participants