Skip to content

Commit abe1c5f

Browse files
committed
Stop reading podspec header search paths during SwiftPM sync (#59365)
Summary: In the SwiftPM pipeline, every sync reads the podspec of each npm dependency to collect its `HEADER_SEARCH_PATHS`. (That can even start a `pod ipc spec` process for each dependency), and the result is never used. This change removes the read and the code that carried its result: - `extractPodspecHeaderSearchPaths` and its call in `autolinkingDepToSpmTarget` (`generate-spm-autolinking.js`) - the `headerSearchPaths` field on `SpmTarget` and `SynthPackageSpec` (`spm-types.js`) - the `root/` prefix step in `main()` and the `.headerSearchPath(...)` output in `generateSynthPackageSwift` The podspec reader (`read-podspec.js`) and the scaffolder (`scaffold-package-swift.js`) still use header search paths. They do not change. This PR does not depend on other open PRs. ## Changelog: [INTERNAL] [REMOVED] - Stop reading unused podspec header search paths during SwiftPM sync Pull Request resolved: #59365 Test Plan: - New test in `generate-spm-autolinking-test.js`: an npm dependency whose podspec sets `HEADER_SEARCH_PATHS` must not cause a call to the podspec reader. Before the change it failed with `Expected number of calls: 0, Received number of calls: 1`. After the change it passes. - `node_modules/.bin/jest --no-cache -i packages/react-native/scripts/spm`: 23 of 23 suites and 1129 of 1129 tests pass. - Generated output for RNTester, before and after, with `node packages/react-native/scripts/spm/generate-spm-autolinking.js --app-root packages/rn-tester --react-native-root packages/react-native --autolinking-json <file> --output <dir>`: - With RNTester's npm dependencies, both runs stop with the same missing-manifest error and write no files. - With only RNTester's app-local modules, both runs write the same 7 `Package.swift` files. `diff -r` finds no differences. - ESLint and Prettier pass on the changed files. Flow finds no errors in them. I did not run a full `yarn flow-check`, because it fails locally on unrelated vendored files. Reviewed By: andrewdacenko Differential Revision: D126936104 Pulled By: cipolleschi fbshipit-source-id: 178520a0fe5037aa646c20d2b9ffafd178b4888f (cherry picked from commit 4bba13d) Conflict resolution for 0.88-stable: kept 0.88's REACT_CXX_CONFIG_DEFINES for cxxSettings instead of main's reactCxxDefines(spec.cxxApiDefines), which is not on 0.88.
1 parent 99d4ed3 commit abe1c5f

3 files changed

Lines changed: 59 additions & 104 deletions

File tree

‎packages/react-native/scripts/spm/__tests__/generate-spm-autolinking-test.js‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2287,4 +2287,47 @@ describe('autolinkingDepToSpmTarget', () => {
22872287
),
22882288
).toThrow(/expandSpmDependencies/);
22892289
});
2290+
2291+
// Every npm dep is self-managed or reported missing, so nothing would
2292+
// consume header search paths read from its podspec — and the read can
2293+
// spawn `pod ipc spec`.
2294+
it('does not read the podspec of a dep that declares HEADER_SEARCH_PATHS', () => {
2295+
const sourceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'spm-hsp-'));
2296+
try {
2297+
fs.writeFileSync(
2298+
path.join(sourceDir, 'Consumer.podspec'),
2299+
"Pod::Spec.new do |s|\n s.name = 'Consumer'\n" +
2300+
" s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '\"$(PODS_TARGET_SRCROOT)/common/cpp\"' }\nend\n",
2301+
);
2302+
const readPodspecCached = jest.fn(() => ({
2303+
headerSearchPaths: ['$(PODS_TARGET_SRCROOT)/common/cpp'],
2304+
}));
2305+
let isolated;
2306+
jest.isolateModules(() => {
2307+
jest.doMock('../read-podspec', () => ({
2308+
...jest.requireActual('../read-podspec'),
2309+
readPodspecCached,
2310+
}));
2311+
isolated = require('../generate-spm-autolinking');
2312+
});
2313+
2314+
const target = isolated.autolinkingDepToSpmTarget(
2315+
'react-native-consumer',
2316+
{
2317+
name: 'react-native-consumer',
2318+
root: sourceDir,
2319+
platforms: {ios: {sourceDir}},
2320+
swiftName: 'ReactNativeConsumer',
2321+
},
2322+
'/out',
2323+
new Map(),
2324+
);
2325+
2326+
expect(readPodspecCached).not.toHaveBeenCalled();
2327+
expect(target).not.toHaveProperty('headerSearchPaths');
2328+
} finally {
2329+
jest.dontMock('../read-podspec');
2330+
fs.rmSync(sourceDir, {recursive: true, force: true});
2331+
}
2332+
});
22902333
});

‎packages/react-native/scripts/spm/generate-spm-autolinking.js‎

Lines changed: 16 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ const {
7171
MIN_IOS_VERSION_SUPPORTED,
7272
sanitizeIosDeploymentTarget,
7373
} = require('./ios-deployment-target');
74-
const {findPodspecs, readPodspecCached} = require('./read-podspec');
74+
const {findPodspecs} = require('./read-podspec');
7575
const {
7676
AUTOLINKED_PACKAGE_NAME,
7777
REACT_CODEGEN_PACKAGE_NAME,
@@ -801,58 +801,6 @@ function expandSpmSourceGlobs(
801801
return micromatch(walkSourceFiles(sourcePath), patterns).sort();
802802
}
803803

804-
/**
805-
* Converts an autolinking.json dependency to an SPM target spec.
806-
* Returns null if the dependency doesn't have iOS support.
807-
*
808-
* `swiftNameByNpm` maps each autolinked dep's npm name to its resolved Swift
809-
* name (populated by expandSpmDependencies from the dep's podspec and
810-
* `spm.name`). Every name this function emits comes from there — see
811-
* requireSwiftName.
812-
*/
813-
/**
814-
* Read the dep's podspec (if any) and extract its declared
815-
* `pod_target_xcconfig` HEADER_SEARCH_PATHS, substituted relative to the dep
816-
* source dir. Returns paths suitable for an SPM `.headerSearchPath()`
817-
* directive whose target.path is the dep root (entries are NOT yet prefixed
818-
* with the synth wrapper's `root/` — the emission site adds that prefix
819-
* because the wrapper's target.path is `.`, not the source dir).
820-
*
821-
* Without these, path-style angle includes like
822-
* `<react/renderer/components/safeareacontext/X.h>` (used by
823-
* react-native-safe-area-context, reanimated, screens, etc.) fail to resolve
824-
* because the headers live under the dep's `common/cpp/` rather than under
825-
* the framework-imported xcframework headers.
826-
*/
827-
function extractPodspecHeaderSearchPaths(
828-
sourceDir /*: string */,
829-
) /*: Array<string> */ {
830-
const podspecPath /*: ?string */ = findPodspecs(sourceDir)[0];
831-
if (podspecPath == null) return [];
832-
833-
let model;
834-
try {
835-
model = readPodspecCached(podspecPath);
836-
} catch {
837-
return [];
838-
}
839-
840-
const out /*: Array<string> */ = [];
841-
for (const raw of model.headerSearchPaths) {
842-
const substituted = raw
843-
.replace(/\$\(PODS_TARGET_SRCROOT\)/g, '.')
844-
.replace(/\$\{PODS_TARGET_SRCROOT\}/g, '.');
845-
// Drop entries still containing unresolved Xcode tokens — emitting them
846-
// verbatim would surface as clang "no such file" failures.
847-
if (/\$[({]/.test(substituted)) continue;
848-
const cleaned = substituted.replace(/^\.\//, '').replace(/^\//, '');
849-
if (cleaned.length > 0 && !out.includes(cleaned)) {
850-
out.push(cleaned);
851-
}
852-
}
853-
return out;
854-
}
855-
856804
/**
857805
* The Swift name expandSpmDependencies resolved for `npmName`, or a hard error:
858806
* re-deriving one here would emit a reference nothing in the graph matches.
@@ -869,6 +817,15 @@ function requireSwiftName(
869817
return resolved;
870818
}
871819

820+
/**
821+
* Converts an autolinking.json dependency to an SPM target spec.
822+
* Returns null if the dependency doesn't have iOS support.
823+
*
824+
* `swiftNameByNpm` maps each autolinked dep's npm name to its resolved Swift
825+
* name (populated by expandSpmDependencies from the dep's podspec and
826+
* `spm.name`). Every name this function emits comes from there — see
827+
* requireSwiftName.
828+
*/
872829
function autolinkingDepToSpmTarget(
873830
depName /*: string */,
874831
dep /*: AutolinkedDep */,
@@ -903,17 +860,13 @@ function autolinkingDepToSpmTarget(
903860
? spmDeps.map(n => requireSwiftName(n, swiftNameByNpm.get(n)))
904861
: undefined;
905862

906-
const headerSearchPaths = extractPodspecHeaderSearchPaths(sourceDir);
907-
908863
return {
909864
name: targetName,
910865
path: relSourcePath,
911866
exclude: [],
912867
publicHeadersPath: inferPublicHeadersPath(sourceDir),
913868
resources,
914869
spmTargetDependencies,
915-
headerSearchPaths:
916-
headerSearchPaths.length > 0 ? headerSearchPaths : undefined,
917870
};
918871
}
919872

@@ -1123,12 +1076,6 @@ function generateSynthPackageSwift(spec /*: SynthPackageSpec */) /*: string */ {
11231076
const exclude /*: Array<string> */ = spec.exclude ?? [];
11241077
const sources /*: ?Array<string> */ = spec.sources;
11251078
const publicHeadersPath /*: ?string */ = spec.publicHeadersPath ?? null;
1126-
// Per-dep header search paths from the podspec's
1127-
// pod_target_xcconfig HEADER_SEARCH_PATHS — already prefixed by the caller
1128-
// with the synth wrapper's `root/` so they resolve relative to target.path.
1129-
// Emitted as `.headerSearchPath()` directives, which SPM accepts on
1130-
// cSettings / cxxSettings without needing absolute paths.
1131-
const headerSearchPaths /*: Array<string> */ = spec.headerSearchPaths ?? [];
11321079
const spmDependencies /*: Array<{swiftName: string}> */ =
11331080
spec.spmDependencies ?? [];
11341081
const hasReactDep /*: boolean */ = spec.hasReactDep !== false;
@@ -1212,22 +1159,11 @@ function generateSynthPackageSwift(spec /*: SynthPackageSpec */) /*: string */ {
12121159
packageDeps.length > 0
12131160
? ` dependencies: [\n ${packageDeps.join(',\n ')},\n ],\n`
12141161
: '';
1215-
// `.headerSearchPath(...)` entries from the podspec — first-class
1216-
// directives keep SPM's diagnostics meaningful (clang reports the
1217-
// dep-relative path on miss). React headers need no paths at all.
1218-
const headerSearchPathList = headerSearchPaths.map(
1219-
p => `.headerSearchPath("${p}")`,
1220-
);
1221-
const cSettingsLine =
1222-
headerSearchPathList.length > 0
1223-
? `\n cSettings: [${headerSearchPathList.join(', ')}],`
1224-
: '';
1225-
// ALWAYS emit the config-gated C++ defines (even with no header paths): a
1226-
// Fabric C++ target must compile with the same NDEBUG state as the prebuilt
1227-
// React.framework, else DebugStringConvertible's vtable / ShadowNode layout
1228-
// diverges and a Release build fails to link. Mirrors Package.swift.
1229-
const cxxEntries = [...REACT_CXX_CONFIG_DEFINES, ...headerSearchPathList];
1230-
const cxxSettingsLine = `\n cxxSettings: [${cxxEntries.join(', ')}],`;
1162+
// ALWAYS emit the config-gated C++ defines: a Fabric C++ target must
1163+
// compile with the same NDEBUG state as the prebuilt React.framework, else
1164+
// DebugStringConvertible's vtable / ShadowNode layout diverges and a
1165+
// Release build fails to link. Mirrors Package.swift.
1166+
const cxxSettingsLine = `\n cxxSettings: [${REACT_CXX_CONFIG_DEFINES.join(', ')}],`;
12311167

12321168
return `// swift-tools-version: 6.0
12331169
// AUTO-GENERATED by scripts/generate-spm-autolinking.js – do not edit manually.
@@ -1245,7 +1181,7 @@ ${packageDepsBlock} targets: [
12451181
.target(
12461182
name: "${swiftName}",
12471183
dependencies: [${targetDeps.join(', ')}],
1248-
path: "${targetPath}",${excludeLine}${sourcesLine}${publicHeadersLine}${resourcesLine}${cSettingsLine}${cxxSettingsLine}
1184+
path: "${targetPath}",${excludeLine}${sourcesLine}${publicHeadersLine}${resourcesLine}${cxxSettingsLine}
12491185
linkerSettings: [.linkedFramework("UIKit"), .linkedFramework("Foundation"), .linkedFramework("CoreGraphics")]
12501186
),
12511187
],
@@ -1705,15 +1641,6 @@ function main(argv /*:: ?: Array<string> */) /*: void */ {
17051641
const prefixedSources /*: ?Array<string> */ =
17061642
rawSources.length > 0 ? rawSources.map(withRoot) : null;
17071643

1708-
// Podspec HEADER_SEARCH_PATHS were captured relative to the dep's source
1709-
// dir. The wrapper exposes the source dir under `root/` (target.path is
1710-
// `.`, the wrapper dir), so each entry must be prefixed with `root/` so
1711-
// clang sees the real subtree.
1712-
const prefixedHeaderSearchPaths /*: ?Array<string> */ =
1713-
target.headerSearchPaths != null && target.headerSearchPaths.length > 0
1714-
? target.headerSearchPaths.map(withRoot)
1715-
: null;
1716-
17171644
const synthContent = generateSynthPackageSwift({
17181645
swiftName: target.name,
17191646
iosDeploymentTarget: args.iosDeploymentTarget,
@@ -1725,7 +1652,6 @@ function main(argv /*:: ?: Array<string> */) /*: void */ {
17251652
// header farm at <outputDir>/headers is folded into it).
17261653
publicHeadersPath: 'include',
17271654
resources: prefixedResources,
1728-
headerSearchPaths: prefixedHeaderSearchPaths,
17291655
spmDependencies: (target.spmTargetDependencies ?? []).map(swiftName => ({
17301656
swiftName,
17311657
})),

‎packages/react-native/scripts/spm/spm-types.js‎

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,6 @@ export type SpmTarget = {
8787
// .js/.podspec/.md siblings, etc. can never sneak in. Null/empty means
8888
// fall back to SPM's default scan of target.path.
8989
sources?: ?Array<string>,
90-
// Header search paths declared by the dep's podspec
91-
// (pod_target_xcconfig HEADER_SEARCH_PATHS), already substituted relative
92-
// to the dep's source dir. Path-style includes like
93-
// `<react/renderer/components/safeareacontext/X.h>` resolve through these.
94-
// Emitted as `.headerSearchPath(...)` directives in the target's
95-
// cSettings / cxxSettings.
96-
headerSearchPaths?: ?Array<string>,
9790
};
9891
9992
// Routing metadata kept alongside an SpmTarget in main(). Lives in a wrapper
@@ -373,13 +366,6 @@ export type SynthPackageSpec = {
373366
// supply absolute paths (tests). Production uses siblingSynthAbsolutePaths.
374367
siblingPackageBaseRelative?: string,
375368
siblingSynthAbsolutePaths?: {[swiftName: string]: string},
376-
// Header search paths from the dep's podspec `pod_target_xcconfig`
377-
// HEADER_SEARCH_PATHS, with `$(PODS_TARGET_SRCROOT)` substituted and the
378-
// synth wrapper's `root/` prefix applied. Emitted as `.headerSearchPath()`
379-
// directives on cSettings / cxxSettings so path-style includes like
380-
// `<react/renderer/components/safeareacontext/X.h>` resolve through the
381-
// dep's own `common/cpp/` subtree.
382-
headerSearchPaths?: ?Array<string>,
383369
iosDeploymentTarget?: ?string,
384370
};
385371

0 commit comments

Comments
 (0)