5959 * [advanced] --skip-codegen Skip the react-native codegen step.
6060 * [advanced] --config-command <json> Override the autolinking config command.
6161 *
62+ * Environment ([add/update]; recorded in .spm-injected.json so Xcode builds
63+ * reuse them, re-run `update` without one to turn it off):
64+ * RN_STRICT_API=1 Enforce React Native's C++ stable API: defines
65+ * the RN_STRICT_API macro for the app target, its
66+ * spm.modules packages and app codegen.
67+ * RN_ALLOW_FRAMEWORKS=1 Silence the stable API's "for frameworks"
68+ * warning there: defines RN_ALLOW_FRAMEWORKS.
69+ *
6270 * Steps performed (add/update):
6371 * 1. react-native codegen → build/generated/ios/ + install SPM codegen template
6472 * 2. generate-spm-autolinking-config.js → build/generated/autolinking/autolinking.json
@@ -99,6 +107,7 @@ const {
99107 findInjectedXcodeproj,
100108 injectSpmIntoExistingXcodeproj,
101109 readArtifactsVersionOverride,
110+ readCxxApiDefines,
102111 readMarker,
103112 readPinnedConfigCommand,
104113 removeSpmInjection,
@@ -112,6 +121,7 @@ const {
112121 scaffoldAll,
113122} = require ( './spm/scaffold-package-swift' ) ;
114123const {
124+ CXX_API_DEFINES ,
115125 RemoteVersionError,
116126 buildPerAppHeaderTree,
117127 defaultCacheDir,
@@ -420,17 +430,24 @@ function runCodegenStep(
420430 appRoot /*: string */ ,
421431 reactNativeRoot /*: string */ ,
422432 skipCodegen /*: boolean */ ,
433+ cxxApiDefines /*: ReadonlyArray<string> */ ,
423434) /*: void */ {
424435 if ( skipCodegen ) {
425436 // Output dir may already exist from a previous run; still refresh the
426437 // SPM template so cache-slot changes propagate.
427438 log ( 'Skipping codegen (--skip-codegen)' ) ;
428- installSpmCodegenTemplate ( appRoot , reactNativeRoot , { log} ) ;
439+ installSpmCodegenTemplate ( appRoot , reactNativeRoot , { log} , { cxxApiDefines } ) ;
429440 return ;
430441 }
431442 log ( 'Running react-native codegen...' ) ;
432443 try {
433- runCodegenAndInstallTemplate ( projectRoot , appRoot , reactNativeRoot , { log} ) ;
444+ runCodegenAndInstallTemplate (
445+ projectRoot ,
446+ appRoot ,
447+ reactNativeRoot ,
448+ { log} ,
449+ { cxxApiDefines} ,
450+ ) ;
434451 } catch {
435452 logError ( 'Codegen failed. Continuing anyway...' ) ;
436453 }
@@ -832,6 +849,7 @@ async function setupXcodeproj(
832849 appRoot /*: string */ ,
833850 reactNativeRoot /*: string */ ,
834851 action /*: string */ ,
852+ cxxApiDefines /*: ReadonlyArray<string> */ ,
835853) /*: Promise<void> */ {
836854 const target = resolveInjectionTarget ( args , appRoot ) ;
837855 if ( target . error != null ) {
@@ -909,6 +927,7 @@ async function setupXcodeproj(
909927 // generate-spm-xcodeproj.js).
910928 artifactsVersionOverride : args . version ?? null ,
911929 configCommand : resolveConfigCommandToPin ( args ) ,
930+ cxxApiDefines,
912931 } ) ;
913932 if ( result . status !== 'injected' ) {
914933 logError ( `SPM injection failed: ${ result . reason } ` ) ;
@@ -968,6 +987,22 @@ function logNextSteps(
968987 log ( 'To remove SPM later: `npx react-native spm deinit`' ) ;
969988}
970989
990+ // The React Native C++ stable API macros this run defines for the app target,
991+ // its spm.modules packages and app codegen (CXX_API_DEFINES). `add`/`update`
992+ // read each from the env var of the same name (=1; unset turns it off, as with
993+ // `pod install`) and record the result in the injection marker; every other
994+ // action, including the build-time `sync`, reuses the recorded macros because
995+ // Xcode builds inherit neither the env vars nor the shell that set them.
996+ function resolveCxxApiDefines (
997+ action /*: string */ ,
998+ appRoot /*: string */ ,
999+ ) /*: Array<string> */ {
1000+ if ( action === 'add' || action === 'update' ) {
1001+ return CXX_API_DEFINES . filter ( name => process . env [ name ] === '1' ) ;
1002+ }
1003+ return readCxxApiDefines ( appRoot ) ;
1004+ }
1005+
9711006// The autolinking config command for this run: an explicit `--config-command`
9721007// first, then the value a previous `add`/`update` pinned into the injection
9731008// marker. undefined means "no explicit command", which is what makes
@@ -1160,6 +1195,18 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
11601195 const version = determineVersion ( args , reactNativeRoot , appRoot ) ;
11611196 log ( `React Native version: ${ version } ` ) ;
11621197
1198+ const cxxApiDefines = resolveCxxApiDefines ( action , appRoot ) ;
1199+ if ( cxxApiDefines . length > 0 ) {
1200+ log (
1201+ `C++ stable API: defining ${ cxxApiDefines . join ( ', ' ) } in the app ` +
1202+ 'target, its spm.modules packages and app codegen' ,
1203+ ) ;
1204+ }
1205+ const cxxApiDefineArgs = cxxApiDefines . flatMap ( name => [
1206+ '--cxx-api-define' ,
1207+ name ,
1208+ ] ) ;
1209+
11631210 // Resolve remote SPM mode ONCE up front. remotePackageConfig throws
11641211 // RemoteVersionError when remote mode is active but no usable RN version can
11651212 // be derived (e.g. the monorepo '1000.0.0' placeholder with no override).
@@ -1184,7 +1231,7 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
11841231 // "Artifacts already present in ...".
11851232
11861233 if ( action === 'codegen' ) {
1187- runCodegenStep ( projectRoot , appRoot , reactNativeRoot , false ) ;
1234+ runCodegenStep ( projectRoot , appRoot , reactNativeRoot , false , cxxApiDefines ) ;
11881235 return ;
11891236 }
11901237
@@ -1198,6 +1245,7 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
11981245 reactNativeRoot ,
11991246 '--ios-deployment-target' ,
12001247 iosDeploymentTarget ,
1248+ ...cxxApiDefineArgs ,
12011249 ] ) ;
12021250 } catch ( e ) {
12031251 if ( e instanceof MissingManifestError ) {
@@ -1240,7 +1288,13 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
12401288 ) ;
12411289 }
12421290
1243- runCodegenStep ( projectRoot , appRoot , reactNativeRoot , args . skipCodegen ) ;
1291+ runCodegenStep (
1292+ projectRoot ,
1293+ appRoot ,
1294+ reactNativeRoot ,
1295+ args . skipCodegen ,
1296+ cxxApiDefines ,
1297+ ) ;
12441298
12451299 // A manifest scaffolded before the app's deployment target changed (or by a
12461300 // pre-v20 scaffolder) would pin a floor SwiftPM then refuses to link against.
@@ -1269,6 +1323,7 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
12691323 reactNativeRoot ,
12701324 '--ios-deployment-target' ,
12711325 iosDeploymentTarget ,
1326+ ...cxxApiDefineArgs ,
12721327 ] ) ;
12731328 } catch ( e ) {
12741329 if ( e instanceof MissingManifestError ) {
@@ -1310,7 +1365,7 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
13101365 }
13111366
13121367 // (Re)install the static codegen Package.swift template once build/generated/ios exists.
1313- installSpmCodegenTemplate ( appRoot , reactNativeRoot , { log} ) ;
1368+ installSpmCodegenTemplate ( appRoot , reactNativeRoot , { log} , { cxxApiDefines } ) ;
13141369
13151370 // Build the per-app generated-headers farm (vended as the ReactAppHeaders
13161371 // SPM target inside the codegen package). React core headers need no trees
@@ -1327,7 +1382,7 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
13271382 // Xcodeproj setup: in-place injection into the existing project (the only
13281383 // strategy — no rename, no from-scratch; git is the safety net).
13291384 try {
1330- await setupXcodeproj ( args , appRoot , reactNativeRoot , action ) ;
1385+ await setupXcodeproj ( args , appRoot , reactNativeRoot , action , cxxApiDefines ) ;
13311386 } catch ( e ) {
13321387 logError ( `xcodeproj setup failed: ${ e . message } ` ) ;
13331388 if ( process . exitCode == null ) {
@@ -1356,6 +1411,7 @@ module.exports = {
13561411 resolveAppIosDeploymentTarget,
13571412 resolveConfigCommandToPin,
13581413 resolveExplicitConfigCommand,
1414+ resolveCxxApiDefines,
13591415 shouldAutoDeintegrate,
13601416 ensureBothArtifactFlavors,
13611417} ;
0 commit comments