|
31 | 31 | * every R9 textual Fabric header against ReactNativeHeaders. |
32 | 32 | * c. A Swift TU: `import React` + `RCTBridge.moduleRegistry` — the Expo |
33 | 33 | * Swift case, proving the R9 modular header is module-visible. |
| 34 | + * d. Swift C++-interop TUs that import React plus every importable module |
| 35 | + * declared by the composed ReactNativeHeaders module map, then the |
| 36 | + * shipped inspector umbrella as an isolated probe module, forcing |
| 37 | + * ClangImporter to instantiate imported C++ value-type special members. |
34 | 38 | * |
35 | 39 | * Usage: |
36 | 40 | * node scripts/ios-prebuild/headers-verify.js [--flavor Debug|Release] |
@@ -63,6 +67,18 @@ const FOLLY_DEFINES = [ |
63 | 67 | ]; |
64 | 68 | const SIM_TARGET = 'arm64-apple-ios15.0-simulator'; |
65 | 69 |
|
| 70 | +// This Objective-C app-bootstrap module is not importable with Swift C++ |
| 71 | +// interop: its __cplusplus-only factory conformances cross-import React and |
| 72 | +// ReactCommon module members, which ClangImporter rejects for hidden |
| 73 | +// declarations and for re-reading the unguarded RCTComponentViewProtocol.h. |
| 74 | +// Any new exclusion must have its own explicit justification comment. |
| 75 | +const SWIFT_CXX_INTEROP_EXCLUDED_MODULES = new Set(['React_RCTAppDelegate']); |
| 76 | +const SWIFT_CXX_INTEROP_PROBE_MODULE = 'ReactNativeHeaders_CxxInteropProbe'; |
| 77 | +const SWIFT_CXX_INTEROP_PROBE_TYPES = [ |
| 78 | + 'facebook::react::jsinspector_modern::tracing::TraceRecordingState', |
| 79 | + 'facebook::react::jsinspector_modern::tracing::HostTracingProfile', |
| 80 | +]; |
| 81 | + |
66 | 82 | function log(msg /*: string */) { |
67 | 83 | console.log(`[headers-verify] ${msg}`); |
68 | 84 | } |
@@ -303,12 +319,49 @@ func _headersVerifyProbe(_ bridge: RCTBridge) { |
303 | 319 | `; |
304 | 320 | } |
305 | 321 |
|
| 322 | +/** Swift C++-interop fixture: every module shipped by ReactNativeHeaders. */ |
| 323 | +function renderSwiftCxxInteropFixture( |
| 324 | + moduleNames /*: Array<string> */, |
| 325 | +) /*: string */ { |
| 326 | + return [ |
| 327 | + 'import React', |
| 328 | + ...moduleNames.map(name => `import ${name}`), |
| 329 | + '', |
| 330 | + ].join('\n'); |
| 331 | +} |
| 332 | + |
| 333 | +function renderSwiftCxxInteropProbeFixture() /*: string */ { |
| 334 | + return `import ${SWIFT_CXX_INTEROP_PROBE_MODULE} |
| 335 | +func _headersVerifyCxxValueProbe( |
| 336 | + _ state: borrowing facebook.react.jsinspector_modern.tracing.TraceRecordingState |
| 337 | +) { |
| 338 | + _ = state.mode |
| 339 | +} |
| 340 | +`; |
| 341 | +} |
| 342 | + |
| 343 | +function parseModuleNames(moduleMapPath /*: string */) /*: Array<string> */ { |
| 344 | + const moduleMap = fs.readFileSync(moduleMapPath, 'utf8'); |
| 345 | + // Dotted submodule names may not be standalone-importable if the composed |
| 346 | + // module map ever grows explicit submodules. |
| 347 | + return Array.from( |
| 348 | + moduleMap.matchAll( |
| 349 | + /^\s*(?:explicit\s+)?(?:framework\s+)?module\s+([A-Za-z_][A-Za-z0-9_.]*)\s*\{/gm, |
| 350 | + ), |
| 351 | + match => match[1], |
| 352 | + ); |
| 353 | +} |
| 354 | + |
306 | 355 | function xcrun(args /*: Array<string> */, what /*: string */) { |
307 | 356 | try { |
308 | | - execFileSync('xcrun', args, {stdio: ['ignore', 'pipe', 'pipe']}); |
| 357 | + execFileSync('xcrun', args, { |
| 358 | + stdio: ['ignore', 'pipe', 'pipe'], |
| 359 | + maxBuffer: 64 * 1024 * 1024, |
| 360 | + }); |
309 | 361 | } catch (e) { |
| 362 | + const stdout = e.stdout != null ? String(e.stdout) : ''; |
310 | 363 | const stderr = e.stderr != null ? String(e.stderr) : String(e); |
311 | | - throw new Error(`${what} FAILED:\n${stderr}`); |
| 364 | + throw new Error(`${what} FAILED:\n${stdout}${stderr}`); |
312 | 365 | } |
313 | 366 | } |
314 | 367 |
|
@@ -405,6 +458,129 @@ function runCompileGates( |
405 | 458 | 'Swift gate (import React + RCTBridge.moduleRegistry)', |
406 | 459 | ); |
407 | 460 | log('compile: Swift moduleRegistry fixture OK.'); |
| 461 | + |
| 462 | + const moduleMap = path.join(rnhHeaders, 'module.modulemap'); |
| 463 | + const moduleNames = parseModuleNames(moduleMap); |
| 464 | + if (moduleNames.length === 0) { |
| 465 | + throw new Error( |
| 466 | + `No modules declared in composed module map: ${moduleMap}`, |
| 467 | + ); |
| 468 | + } |
| 469 | + const importableModuleNames = moduleNames.filter( |
| 470 | + name => !SWIFT_CXX_INTEROP_EXCLUDED_MODULES.has(name), |
| 471 | + ); |
| 472 | + // React_RCTAppDelegate is the only composed module that reaches the |
| 473 | + // inspector C++ graph, but it cannot itself be imported in C++-interop |
| 474 | + // mode (see the exclusion comment above). Wrap the shipped inspector |
| 475 | + // umbrella in a temporary module so ClangImporter still checks that graph. |
| 476 | + const cxxInteropProbeHeaders = path.join(tmp, 'rnh-cxx-interop-probe'); |
| 477 | + fs.cpSync(rnhHeaders, cxxInteropProbeHeaders, { |
| 478 | + recursive: true, |
| 479 | + filter: source => path.basename(source) !== 'module.modulemap', |
| 480 | + }); |
| 481 | + const cxxInteropProbeHeader = path.join(tmp, 'cxx-interop-probe.h'); |
| 482 | + const cxxInteropCopyProbes = SWIFT_CXX_INTEROP_PROBE_TYPES.map( |
| 483 | + (typeName, index) => |
| 484 | + `inline void probeCopy${index}(const ${typeName} &value) {\n` + |
| 485 | + ` instantiateCopy(value);\n` + |
| 486 | + `}\n`, |
| 487 | + ).join(''); |
| 488 | + fs.writeFileSync( |
| 489 | + cxxInteropProbeHeader, |
| 490 | + `#include <jsinspector-modern/ReactCdp.h>\n` + |
| 491 | + `#include <type_traits>\n` + |
| 492 | + `namespace rn_headers_verify {\n` + |
| 493 | + `template <typename T> void instantiateCopy(const T &value) {\n` + |
| 494 | + ` if constexpr (std::is_copy_constructible_v<T>) {\n` + |
| 495 | + ` T copy(value);\n` + |
| 496 | + ` (void)copy;\n` + |
| 497 | + ` }\n` + |
| 498 | + `}\n` + |
| 499 | + cxxInteropCopyProbes + |
| 500 | + `}\n`, |
| 501 | + ); |
| 502 | + const cxxInteropProbeModuleMap = path.join( |
| 503 | + tmp, |
| 504 | + 'module-cxx-interop-probe.modulemap', |
| 505 | + ); |
| 506 | + fs.writeFileSync( |
| 507 | + cxxInteropProbeModuleMap, |
| 508 | + `module ${SWIFT_CXX_INTEROP_PROBE_MODULE} {\n` + |
| 509 | + ` header ${JSON.stringify(cxxInteropProbeHeader)}\n` + |
| 510 | + ` export *\n` + |
| 511 | + `}\n`, |
| 512 | + ); |
| 513 | + const swiftCxxInterop = path.join(tmp, 'gate-swift-cxx-interop.swift'); |
| 514 | + fs.writeFileSync( |
| 515 | + swiftCxxInterop, |
| 516 | + renderSwiftCxxInteropFixture(importableModuleNames), |
| 517 | + ); |
| 518 | + xcrun( |
| 519 | + [ |
| 520 | + 'swiftc', |
| 521 | + '-typecheck', |
| 522 | + '-cxx-interoperability-mode=default', |
| 523 | + '-sdk', |
| 524 | + sdk, |
| 525 | + '-target', |
| 526 | + SIM_TARGET, |
| 527 | + '-module-cache-path', |
| 528 | + path.join(tmp, 'mc-swift-cxx-interop'), |
| 529 | + '-F', |
| 530 | + reactSlice, |
| 531 | + '-I', |
| 532 | + rnhHeaders, |
| 533 | + '-I', |
| 534 | + depsHeaders, |
| 535 | + '-Xcc', |
| 536 | + '-std=c++20', |
| 537 | + '-Xcc', |
| 538 | + '-w', |
| 539 | + '-Xcc', |
| 540 | + `-fmodule-map-file=${moduleMap}`, |
| 541 | + ...FOLLY_DEFINES.flatMap(flag => ['-Xcc', flag]), |
| 542 | + swiftCxxInterop, |
| 543 | + ], |
| 544 | + 'Swift C++-interop gate (React + importable ReactNativeHeaders modules)', |
| 545 | + ); |
| 546 | + |
| 547 | + const swiftCxxInteropProbe = path.join( |
| 548 | + tmp, |
| 549 | + 'gate-swift-cxx-interop-probe.swift', |
| 550 | + ); |
| 551 | + fs.writeFileSync(swiftCxxInteropProbe, renderSwiftCxxInteropProbeFixture()); |
| 552 | + xcrun( |
| 553 | + [ |
| 554 | + 'swiftc', |
| 555 | + '-typecheck', |
| 556 | + '-cxx-interoperability-mode=default', |
| 557 | + '-sdk', |
| 558 | + sdk, |
| 559 | + '-target', |
| 560 | + SIM_TARGET, |
| 561 | + '-module-cache-path', |
| 562 | + path.join(tmp, 'mc-swift-cxx-interop-probe'), |
| 563 | + '-I', |
| 564 | + cxxInteropProbeHeaders, |
| 565 | + '-I', |
| 566 | + depsHeaders, |
| 567 | + '-Xcc', |
| 568 | + '-std=c++20', |
| 569 | + '-Xcc', |
| 570 | + '-w', |
| 571 | + '-Xcc', |
| 572 | + `-fmodule-map-file=${cxxInteropProbeModuleMap}`, |
| 573 | + ...FOLLY_DEFINES.flatMap(flag => ['-Xcc', flag]), |
| 574 | + swiftCxxInteropProbe, |
| 575 | + ], |
| 576 | + 'Swift C++-interop gate (inspector value types)', |
| 577 | + ); |
| 578 | + log( |
| 579 | + `compile: Swift C++ interop React + ${importableModuleNames.length} ` + |
| 580 | + `ReactNativeHeaders modules + inspector probes ` + |
| 581 | + `[${SWIFT_CXX_INTEROP_PROBE_TYPES.join(', ')}] OK ` + |
| 582 | + `(${moduleNames.length - importableModuleNames.length} excluded).`, |
| 583 | + ); |
408 | 584 | } finally { |
409 | 585 | fs.rmSync(tmp, {recursive: true, force: true}); |
410 | 586 | } |
|
0 commit comments