Skip to content

feat: add support for dynamic frameworks#2943

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

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

Conversation

@Saadnajmi

@Saadnajmi Saadnajmi commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Improve support for building RNTester with dynamic frameworks (USE_FRAMEWORKS=dynamic), and add CI coverage that exercises it without blocking (matching upstream).

The core fix (folly)

Add folly/memory/SanitizeLeak.cpp to RCT-Folly.podspec's source_files. The file ships in the Folly source tree (fetched by CocoaPods) but was never compiled, so annotate_object_leaked_impl / annotate_object_collected_impl (declared in SanitizeLeak.h, referenced from small_vector.h) were undefined. With static libraries the linker drops them; with dynamic frameworks every symbol must resolve, so the build fails at link time. This is also an upstream bug — facebook/react-native marks its test_ios_rntester_dynamic_frameworks job continue-on-error for the same reason.

RCTUIKit: give it its own module namespace

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't resolve.

  • React-RCTUIKit now uses header_dir "React_RCTUIKit"; its internal sibling imports become <React_RCTUIKit/...> (which also breaks a React → React_RCTUIKit → React module cycle under frameworks).
  • React-Core vends modular <React/RCTUI*.h> forwarding headers that #import <React_RCTUIKit/...> — so all ~195 existing <React/RCTUIKit.h> imports keep working, with no consumer changes and no -Wnon-modular-include error.
  • SwiftPM prebuild links RCTUIKit under React_RCTUIKit too (in addition to React), so the new imports resolve there as well.

Additional dynamic-frameworks build fixes

  • SUPPORTED_PLATFORMS per pod target (from SDKROOT) — without it the per-platform framework targets collide with hermes-engine's "Multiple commands produce".
  • Set $RN_PLATFORMS before podspec evaluation so add_dependency() generates per-platform-suffixed framework header search paths (React-debug-iOS vs React-debug-macOS).
  • 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 (fork compiles those macOS view files on all platforms; they include KeyEvent.h).

CI

Add a linkage: [static, dynamic] dimension to the RNTester build matrix (macOS/iOS/visionOS × oldarch/newarch). Dynamic-frameworks jobs are continue-on-error (upstream-aligned) — they report status without blocking; static builds stay required.

Verified locally

  • Static RNTester iOS build passes (required).
  • Dynamic build resolves RCTUIKit with zero cyclic / non-modular / not-found errors.

Remaining (tracked, non-blocking on the dynamic canary)

A few dynamic-only header-search-path gaps (folly/dynamic.h, RCTFabricSurface, ComponentDescriptors) and a genuinely macOS issue (HostPlatformViewEventEmitter.cpp uses AppKit-only MouseEvent types while compiling on iOS). These keep the dynamic jobs red, which is why they're continue-on-error.

Test plan

  • RNTester static builds pass on macOS, iOS, visionOS (required)
  • Prebuild macOS Core (SwiftPM) passes
  • Dynamic-frameworks jobs run and report status (non-blocking)

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 2 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>
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.

1 participant