Skip to content

Commit 47fad09

Browse files
chrfalchmeta-codesync[bot]
authored andcommitted
feat(iOS) swiftpm support in iOS (#57332)
Summary: ⚠️ This is a stacked PR — review/merge the base PRs first Builds on, and should land after (bottom-up): 1. #57285 — Remove the Clang VFS overlay / modularize React headers (base of the stack). <React/…> via the framework module map; lowercase namespaces (react/, yoga/, jsi/, …) via the ReactNativeHeaders module map. 2. #57305 — Prebuilt artifact resources + ReactNativeHeaders.xcframework. Ships the prebuilt core artifacts (incl. ReactNativeHeaders in the tarball + embedded React.framework resources) and the CocoaPods React-Core-prebuilt facades. 3. #57440 — Self-serving prebuilt ReactNativeDependencies + dependency-only facades. The deps pod becomes the single header authority for the third-party namespaces (RCT-Folly/glog/boost/…); community `s.dependency "RCT-Folly"` resolves to local facades instead of trunk source pods. 4. #57442 — ReactNativeDependenciesHeaders sidecar + pure-RN ReactNativeHeaders (immediate base). The deps prebuild emits a headers-only LIBRARY-type sidecar (the binary deps xcframework is framework-type — invisible to SwiftPM binaryTargets), ReactNativeHeaders drops the third-party namespaces, and both headers artifacts publish standalone to Maven. The whole stack is rebased onto current main (2026-07-06). This PR consumes the final **five-artifact set**: React, ReactNativeHeaders (pure-RN), ReactNativeDependencies, ReactNativeDependenciesHeaders, hermes-engine. Adds npx react-native spm and the package-generation tooling that turns an app into a SwiftPM-integrated RN app using the base stack's prebuilt XCFrameworks. CocoaPods stays supported — additive, opt-in, no Ruby toolchain. Integration is injected into the existing .xcodeproj in place (nothing generated/renamed/replaced), recorded in .spm-injected.json so it reverses exactly. ### What this PR adds - The spm CLI (add/update/deinit/scaffold + hidden sync/codegen/download), zero-arg auto-resolution, --deintegrate for CocoaPods→SwiftPM. - SwiftPM package generation (scripts/spm/): autolinking→Package.swift, Codegen→React-GeneratedCode, core XCFramework binary targets, artifact download/cache, surgical pbxproj inject/remove. - ReactNativeDependenciesHeaders wired as the 5th binaryTarget: the headers-only companions (ReactNativeHeaders from the core tarball, the deps sidecar from the deps tarball) are staged automatically out of their parent tarballs — no --headers-tarball priming; REQUIRED_ARTIFACTS covers all five. - Community-library scaffolding from podspecs (incl. root-level-source podspecs via generated include/<SwiftName>/ shims, sibling wiring for transitive spm.dependencies, self-ingestion guards). - In-place Xcode integration (XCLocalSwiftPackageReference + Sync build phase + scheme pre-action + auto-sync; ${PODS_ROOT}-anchored REACT_NATIVE_PATH replaced on --deintegrate with exact deinit rollback). - rn-tester + helloworld SwiftPM consumption + test library; npm-packaging hygiene; a small RNCoreFacades.podspec_dir fix. ## Documentation - **Tool docs** (usage, quick start, architecture): [`scripts/spm/__doc__/spm-scripts.md`](https://github.com/react/react-native/blob/chrfalch/swift-package-manager/packages/react-native/scripts/spm/__doc__/spm-scripts.md) - **Header-paths contract** (how the five artifacts serve headers): [`scripts/spm/__doc__/spm-header-paths-contract.md`](https://github.com/react/react-native/blob/chrfalch/swift-package-manager/packages/react-native/scripts/spm/__doc__/spm-header-paths-contract.md) - **RFC** (updated): [react-native-community/discussions-and-proposals#994](react-native-community/discussions-and-proposals#994) ## Changelog: [IOS] [ADDED] - `npx react-native spm` command + SwiftPM package-generation tooling (opt-in; CocoaPods stays supported) Pull Request resolved: #57332 Test Plan: 350 scripts/spm unit tests (incl. byte-identical add→deinit round-trip); manual E2E against the five-artifact set: fresh app via `cli init`→`spm add --deintegrate`→build (artifact resources verified in the app), rn-tester in-place migration (RNTesterPods.xcodeproj, test libraries + spmModules), helloworld in-place migration — all BUILD SUCCEEDED. Verified `npx react-native spm <cmd>` from the command line in each journey. ## Scope & limitations iOS, prebuilt-only (no build-from-source yet); full spm.xcframework/spm.source library metadata not yet (app-local spm.modules + scaffolding are); Expo not yet. ## Follow-ups Remote-mode ReactNative Package.swift must vend the ReactNativeDependenciesHeaders product (spm-distribution repo); library self-containment for repo-portable manifests; build-from-source. Reviewed By: mdvacca Differential Revision: D111449548 Pulled By: cipolleschi fbshipit-source-id: 87a873bf5c8be2d60f7893611e16924bfa730a46
1 parent 6aa147f commit 47fad09

67 files changed

Lines changed: 23437 additions & 5 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
},
4444
"workspaces": [
4545
"packages/*",
46+
"packages/react-native-test-library/*",
4647
"private/*",
4748
"!private/helloworld"
4849
],
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Generated SPM artifacts (written by setup-ios-spm.js's earlier in-place
2+
# layout). The current autolinker emits these under the consumer app's
3+
# build/generated/autolinking/ tree instead, so any copy that lands here is
4+
# stale and should not be committed.
5+
Package.swift
6+
Package.resolved
7+
include/
8+
9+
# SwiftPM caches
10+
.build/
11+
.swiftpm/
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <React/RCTBridgeModule.h>
9+
10+
@interface TestLibraryApple : NSObject <RCTBridgeModule>
11+
@end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "TestLibraryApple.h"
9+
10+
// Synth library products are emitted as .library(type: .dynamic, ...), so SPM
11+
// wraps each autolinked dep as a Foo.framework under PackageFrameworks/. That
12+
// gives angle-bracket imports the standard <Module/Header.h> resolution path,
13+
// matching how most React Native libraries already organize their headers.
14+
#import <ReactNativeTestLibraryCommon/TestLibraryCommon.h>
15+
16+
@implementation TestLibraryApple
17+
18+
RCT_EXPORT_MODULE()
19+
20+
RCT_EXPORT_METHOD(
21+
echo : (NSString *)message resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
22+
{
23+
NSString *prefix = [TestLibraryCommon defaultPrefix];
24+
resolve([NSString stringWithFormat:@"%@apple: %@", prefix, message]);
25+
}
26+
27+
@end
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
#
3+
# This source code is licensed under the MIT license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
require "json"
7+
8+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
9+
10+
Pod::Spec.new do |s|
11+
s.name = "TestLibraryApple"
12+
s.version = package["version"]
13+
s.summary = package["description"]
14+
s.homepage = "https://github.com/facebook/react-native"
15+
s.license = "MIT"
16+
s.platforms = min_supported_versions
17+
s.author = "Meta Platforms, Inc. and its affiliates"
18+
s.source = { :git => "https://github.com/facebook/react-native.git", :tag => "#{s.version}" }
19+
s.source_files = "*.{h,m,mm,swift}"
20+
s.requires_arc = true
21+
22+
# TestLibraryApple.mm imports <ReactNativeTestLibraryCommon/TestLibraryCommon.h>.
23+
# CocoaPods resolves it leniently through the shared Public headers dir, but the
24+
# dependency edge must be declared for SwiftPM (the scaffolder wires sibling
25+
# packages from podspec dependencies).
26+
s.dependency "TestLibraryCommon"
27+
28+
install_modules_dependencies(s)
29+
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <this/header/intentionally/does/not/exist.h>
9+
10+
static_assert(
11+
false,
12+
"TestLibraryAppleTests.cpp must not be compiled by the SPM autolinker");
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import type {Greeting} from '../common';
9+
10+
export function greet(g: Greeting): Promise<string>;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
'use strict';
12+
13+
import type {Greeting} from '../common';
14+
15+
import {formatGreeting} from '../common';
16+
import {NativeModules, Platform} from 'react-native';
17+
18+
export function greet(g: Greeting): Promise<string> {
19+
const TestLibraryApple = NativeModules.TestLibraryApple;
20+
if (TestLibraryApple == null) {
21+
return Promise.reject(
22+
new Error(
23+
`react-native-test-library-apple: native module unavailable on ${Platform.OS}. This package is iOS-only; install a platform-specific sibling (e.g. react-native-test-library-android) for cross-platform coverage.`,
24+
),
25+
);
26+
}
27+
return TestLibraryApple.echo(formatGreeting(g));
28+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "react-native-test-library-apple",
3+
"version": "0.87.0-main",
4+
"description": "Apple platform implementation for the React Native autolinking fixture. Depends on react-native-test-library-common; used to validate iOS/macOS autolinking discovery and transitive native dependency resolution.",
5+
"private": true,
6+
"main": "index.js",
7+
"types": "index.d.ts",
8+
"license": "MIT",
9+
"files": [
10+
"index.js",
11+
"index.d.ts",
12+
"react-native.config.js",
13+
"TestLibraryApple.podspec",
14+
"TestLibraryApple.h",
15+
"TestLibraryApple.mm"
16+
],
17+
"keywords": [
18+
"react-native",
19+
"fixture",
20+
"autolinking",
21+
"ios",
22+
"macos"
23+
],
24+
"dependencies": {
25+
"react-native-test-library-common": "0.87.0-main"
26+
},
27+
"peerDependencies": {
28+
"react": "*",
29+
"react-native": "1000.0.0"
30+
}
31+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @noflow
9+
*/
10+
11+
'use strict';
12+
13+
module.exports = {
14+
dependency: {
15+
platforms: {
16+
ios: {},
17+
},
18+
},
19+
spm: {
20+
dependencies: ['react-native-test-library-common'],
21+
},
22+
};

0 commit comments

Comments
 (0)