Skip to content

feat: add support for dynamic frameworks#2943

Open
Saadnajmi wants to merge 11 commits into
microsoft:mainfrom
Saadnajmi:fix/folly-sanitizeleak-dynamic-frameworks
Open

feat: add support for dynamic frameworks#2943
Saadnajmi wants to merge 11 commits into
microsoft:mainfrom
Saadnajmi:fix/folly-sanitizeleak-dynamic-frameworks

Conversation

@Saadnajmi

@Saadnajmi Saadnajmi commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1969

Summary

Add RNTester coverage for USE_FRAMEWORKS=dynamic and fix the first set of build issues it exposed.

  • Include folly/memory/SanitizeLeak.cpp in RCT-Folly. The declarations were already used by Folly headers, but the implementation was not part of the pod target.
  • Configure the multi-platform RNTester Podfile before podspec evaluation so framework header paths include the platform suffix.
  • Set SUPPORTED_PLATFORMS on RNTester's generated pod targets. This keeps Xcode from selecting multiple platform variants of the same framework and avoids duplicate Hermes build outputs.

The RNTester-specific workaround stays in packages/rn-tester/Podfile; this PR does not change the shared ReactCommon podspecs or CocoaPods helpers.

RCTUIKit

RCTUIKit now lives under ReactApple/Libraries/RCTUIKit, alongside the other Apple-specific libraries. The CocoaPods package name remains React-RCTUIKit, while the canonical CocoaPods/SwiftPM module is RCTUIKit.

The canonical import is:

#import <RCTUIKit/RCTUIKit.h>

The two existing imports used by React Native and downstream packages remain supported:

#import <React/RCTUIKit.h>
#import <React/RCTPlatformDisplayLink.h>

The latter is used by Reanimated/Worklets on macOS. Prebuild header staging exposes both the canonical and compatibility paths.

CI

RNTester now builds a static/dynamic linkage matrix for macOS, iOS, and visionOS across both architectures. Static builds remain required. Dynamic pod-install and build steps are nonblocking for now so they can act as a canary while the remaining framework issues are worked through.

Validation

  • Static RNTester iOS build passes locally.
  • Dynamic RNTester avoids duplicate Hermes outputs and resolves RCTUIKit without missing-header, module-cycle, or non-modular-header errors.
  • CI passes all static RNTester jobs.
  • SwiftPM platform builds and XCFramework composition pass in the macOS prebuild workflow.

The Folly `SanitizeLeak.cpp` file provides implementations for
`annotate_object_leaked_impl`, `annotate_object_collected_impl`,
and `annotate_object_count_leaked_uncollected_impl`. These symbols
are declared in `SanitizeLeak.h` and referenced by
`small_vector.h`, but the .cpp was not listed in the podspec's
source_files.

With static libraries this is benign — the linker skips unreferenced
symbols. With `USE_FRAMEWORKS=dynamic` the linker must resolve every
symbol, causing an "undefined symbols for architecture x86_64" error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Saadnajmi Saadnajmi requested a review from a team as a code owner April 22, 2026 22:34
@Saadnajmi Saadnajmi marked this pull request as draft April 22, 2026 22:56
Add a linkage: [static, dynamic] dimension to the RNTester build matrix
so macOS, iOS and visionOS are each exercised with USE_FRAMEWORKS=dynamic
in addition to the default static libraries. USE_FRAMEWORKS is unset for
static builds so the Podfile keeps its default linkage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Saadnajmi Saadnajmi changed the title fix: add missing SanitizeLeak.cpp to RCT-Folly podspec feat: support and test dynamic frameworks Jul 7, 2026
@Saadnajmi Saadnajmi changed the title feat: support and test dynamic frameworks feat: add support for dynamic frameworks Jul 7, 2026
@Saadnajmi Saadnajmi marked this pull request as ready for review July 7, 2026 07:41
Saadnajmi and others added 2 commits July 7, 2026 15:41
Several fixes that let the multi-platform (iOS/macOS/visionOS) RNTester
workspace get much further building with USE_FRAMEWORKS=dynamic:

- Set SUPPORTED_PLATFORMS per pod target from its SDKROOT. Without it,
  the per-platform framework targets (hermes.framework etc.) all look
  like candidates for a given -framework flag, so Xcode pulls the wrong
  ones in and hermes-engine's identical script-phase outputs collide
  with 'Multiple commands produce'.
- Set $RN_PLATFORMS before podspec evaluation (not in post_install), so
  add_dependency() generates per-platform-suffixed framework header
  search paths (React-debug-iOS vs React-debug-macOS). Previously the
  suffix was missing, breaking headers like react/debug/react_native_assert.h.
- Generate suffixed framework header search paths in the React-cxxreact,
  ReactCommon and React-NativeModulesApple podspecs (also corrects a
  React_featureflags.framework path that pointed at the React-debug dir).
- Expose react/renderer/components/view/platform/macos on React-Fabric's
  framework header search paths; the fork compiles those macOS view files
  on all platforms and they include react/renderer/components/view/KeyEvent.h.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Dynamic frameworks are a work in progress on this fork, so let those
matrix jobs report status without failing the build (matching upstream's
continue-on-error dynamic-frameworks job). Static builds stay required.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Saadnajmi Saadnajmi marked this pull request as draft July 8, 2026 06:50
Saadnajmi and others added 3 commits July 8, 2026 00:15
RCTUIKit lives in the separate React-RCTUIKit pod (module React_RCTUIKit)
but is imported everywhere as <React/RCTUIKit.h>. In static builds both
React-Core and React-RCTUIKit use header_dir "React", so their headers
pool into one Pods/Headers/Public/React dir and the include resolves.
Under use_frameworks! each pod builds its own framework module, that
shared dir vanishes, and <React/RCTUIKit.h> can no longer resolve.

Make React_RCTUIKit RCTUIKit's canonical namespace and re-export it from
React-Core as modular aliases:

- React-RCTUIKit.podspec header_dir "React" -> "React_RCTUIKit".
- RCTUIKit's internal sibling imports <React/RCTUI*.h> -> <React_RCTUIKit/...>
  so the pod references its own module (breaks a React -> React_RCTUIKit ->
  React module cycle under frameworks).
- Add React-Core forwarding headers (React/RCTUIKitForwarding/*.h) that
  #import <React_RCTUIKit/...>. Because React_RCTUIKit is a real module,
  these are modular includes, so <React/RCTUIKit.h> resolves without the
  non-modular-include-in-framework-module error a loose header would hit.
  All ~195 existing <React/RCTUIKit.h> imports keep working unchanged.
- SwiftPM prebuild: link React/RCTUIKit into React_RCTUIKit as well (in
  addition to React) so the new <React_RCTUIKit/...> imports resolve; the
  forwarding dir is excluded from the reactCore SPM target.

Verified: static RNTester iOS build passes; dynamic build resolves RCTUIKit
with zero cyclic / non-modular / not-found errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move continue-on-error from the job to the pod-install and build steps for
dynamic linkage. The jobs still run for coverage (real result visible in the
step logs), but a dynamic build failure no longer shows the check as failed.
Static builds remain strict and required.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep React_RCTUIKit as RCTUIKit's canonical module, but replace the 16
React-Core forwarding headers with only the two compatibility entry points
used outside the module:

- <React/RCTUIKit.h>, the public umbrella used throughout RN and by major
  third-party packages (WebView, Gesture Handler, SVG, Skia, Expo, etc.).
- <React/RCTPlatformDisplayLink.h>, used directly by Reanimated/Worklets
  for their macOS display-link implementation.

RCTUIKit's implementation now uses local sibling imports, avoiding the
React -> React_RCTUIKit -> React module cycle without requiring a second
SwiftPM header alias. This removes the RCTUIKitForwarding directory and
reverts the no-longer-needed Package.swift/setup.js changes.

Verified: static RNTester iOS build succeeds; dynamic build has zero
RCTUIKit not-found, cyclic-module, or non-modular-header errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Saadnajmi and others added 2 commits July 10, 2026 14:27
Move the UIKit/AppKit compatibility layer and its podspec from React/ into
ReactApple/Libraries/RCTUIKit, matching ReactApple's ownership convention
for Apple-framework libraries. Preserve the existing React-RCTUIKit pod and
React_RCTUIKit module names so this commit is path-only from a consumer's
perspective.

Update CocoaPods discovery, SwiftPM target path, prebuild header staging,
React-Core exclusions, and macOS prebuild cache inputs. The two existing
<React/...> compatibility headers remain in React-Core.

Verified: static RNTester iOS build succeeds; dynamic build reports zero
RCTUIKit missing-header, cyclic-module, or non-modular-header errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename the canonical CocoaPods and SwiftPM module from React_RCTUIKit to
RCTUIKit, matching Apple framework casing and the new ReactApple ownership.
Keep the React-RCTUIKit pod name unchanged for dependency compatibility.

Update the two established React compatibility entry points to forward to
<RCTUIKit/...>, and stage headers under both React (legacy) and RCTUIKit
(canonical) in the prebuild header layout. Document the canonical import in
the library README.

Verified: static RNTester iOS build succeeds; dynamic builds expose
RCTUIKit.framework/Headers/RCTUIKit.h with zero RCTUIKit missing-header,
cyclic-module, or non-modular-header errors. Direct SwiftPM validation is
deferred to required prebuild CI because local binary artifacts are absent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Saadnajmi Saadnajmi marked this pull request as ready for review July 11, 2026 00:27
Remove the broad ReactCommon podspec and shared CocoaPods utility changes from
this PR. They restructured upstream-owned code to make the nonblocking dynamic
canary progress further, but are not required for the core fixes.

Keep the multi-platform framework workaround local to RNTester: declare
RN_PLATFORMS before podspec evaluation and set SUPPORTED_PLATFORMS on generated
pod targets in its post_install hook. This still prevents Hermes duplicate-output
collisions without adding fork behavior to shared React Native CocoaPods code.

Also add the required macOS diff tags to the Folly source addition, moved
RCTUIKit podspec/README, RNTester block, and moved pod path.

Verified: static RNTester iOS succeeds; dynamic has no duplicate-output or
RCTUIKit missing/cyclic/non-modular errors (remaining gaps stay nonblocking).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread packages/react-native/Package.swift Outdated
Comment thread packages/rn-tester/Podfile
After moving RCTUIKit out of the React target, excludedPaths matches upstream
again and no longer needs a macOS diff tag.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

React-Native-MacOS does not support use_frameworks!, :linkage => :dynamic

1 participant