Skip to content

Commit 0c1aff7

Browse files
committed
Stop reading podspec header search paths during SwiftPM sync
Every SwiftPM sync read the podspec of each npm dependency to collect its HEADER_SEARCH_PATHS. The read can start a `pod ipc spec` process. Its result never reached a generated manifest: each npm dependency either ships its own Package.swift or fails as a missing manifest before the wrapper is generated, and app-local modules never carry header search paths. Remove the read, the SpmTarget and SynthPackageSpec fields, and the wrapper code that emitted them.
1 parent 3b4abb3 commit 0c1aff7

3 files changed

Lines changed: 50 additions & 98 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
@@ -2386,4 +2386,47 @@ describe('autolinkingDepToSpmTarget', () => {
23862386
),
23872387
).toThrow(/expandSpmDependencies/);
23882388
});
2389+
2390+
// Every npm dep is self-managed or reported missing, so nothing would
2391+
// consume header search paths read from its podspec — and the read can
2392+
// spawn `pod ipc spec`.
2393+
it('does not read the podspec of a dep that declares HEADER_SEARCH_PATHS', () => {
2394+
const sourceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'spm-hsp-'));
2395+
try {
2396+
fs.writeFileSync(
2397+
path.join(sourceDir, 'Consumer.podspec'),
2398+
"Pod::Spec.new do |s|\n s.name = 'Consumer'\n" +
2399+
" s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '\"$(PODS_TARGET_SRCROOT)/common/cpp\"' }\nend\n",
2400+
);
2401+
const readPodspecCached = jest.fn(() => ({
2402+
headerSearchPaths: ['$(PODS_TARGET_SRCROOT)/common/cpp'],
2403+
}));
2404+
let isolated;
2405+
jest.isolateModules(() => {
2406+
jest.doMock('../read-podspec', () => ({
2407+
...jest.requireActual('../read-podspec'),
2408+
readPodspecCached,
2409+
}));
2410+
isolated = require('../generate-spm-autolinking');
2411+
});
2412+
2413+
const target = isolated.autolinkingDepToSpmTarget(
2414+
'react-native-consumer',
2415+
{
2416+
name: 'react-native-consumer',
2417+
root: sourceDir,
2418+
platforms: {ios: {sourceDir}},
2419+
swiftName: 'ReactNativeConsumer',
2420+
},
2421+
'/out',
2422+
new Map(),
2423+
);
2424+
2425+
expect(readPodspecCached).not.toHaveBeenCalled();
2426+
expect(target).not.toHaveProperty('headerSearchPaths');
2427+
} finally {
2428+
jest.dontMock('../read-podspec');
2429+
fs.rmSync(sourceDir, {recursive: true, force: true});
2430+
}
2431+
});
23892432
});

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

Lines changed: 7 additions & 84 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
CXX_API_DEFINES,
@@ -831,49 +831,6 @@ function expandSpmSourceGlobs(
831831
* `spm.name`). Every name this function emits comes from there — see
832832
* requireSwiftName.
833833
*/
834-
/**
835-
* Read the dep's podspec (if any) and extract its declared
836-
* `pod_target_xcconfig` HEADER_SEARCH_PATHS, substituted relative to the dep
837-
* source dir. Returns paths suitable for an SPM `.headerSearchPath()`
838-
* directive whose target.path is the dep root (entries are NOT yet prefixed
839-
* with the synth wrapper's `root/` — the emission site adds that prefix
840-
* because the wrapper's target.path is `.`, not the source dir).
841-
*
842-
* Without these, path-style angle includes like
843-
* `<react/renderer/components/safeareacontext/X.h>` (used by
844-
* react-native-safe-area-context, reanimated, screens, etc.) fail to resolve
845-
* because the headers live under the dep's `common/cpp/` rather than under
846-
* the framework-imported xcframework headers.
847-
*/
848-
function extractPodspecHeaderSearchPaths(
849-
sourceDir /*: string */,
850-
) /*: Array<string> */ {
851-
const podspecPath /*: ?string */ = findPodspecs(sourceDir)[0];
852-
if (podspecPath == null) return [];
853-
854-
let model;
855-
try {
856-
model = readPodspecCached(podspecPath);
857-
} catch {
858-
return [];
859-
}
860-
861-
const out /*: Array<string> */ = [];
862-
for (const raw of model.headerSearchPaths) {
863-
const substituted = raw
864-
.replace(/\$\(PODS_TARGET_SRCROOT\)/g, '.')
865-
.replace(/\$\{PODS_TARGET_SRCROOT\}/g, '.');
866-
// Drop entries still containing unresolved Xcode tokens — emitting them
867-
// verbatim would surface as clang "no such file" failures.
868-
if (/\$[({]/.test(substituted)) continue;
869-
const cleaned = substituted.replace(/^\.\//, '').replace(/^\//, '');
870-
if (cleaned.length > 0 && !out.includes(cleaned)) {
871-
out.push(cleaned);
872-
}
873-
}
874-
return out;
875-
}
876-
877834
/**
878835
* The Swift name expandSpmDependencies resolved for `npmName`, or a hard error:
879836
* re-deriving one here would emit a reference nothing in the graph matches.
@@ -924,17 +881,13 @@ function autolinkingDepToSpmTarget(
924881
? spmDeps.map(n => requireSwiftName(n, swiftNameByNpm.get(n)))
925882
: undefined;
926883

927-
const headerSearchPaths = extractPodspecHeaderSearchPaths(sourceDir);
928-
929884
return {
930885
name: targetName,
931886
path: relSourcePath,
932887
exclude: [],
933888
publicHeadersPath: inferPublicHeadersPath(sourceDir),
934889
resources,
935890
spmTargetDependencies,
936-
headerSearchPaths:
937-
headerSearchPaths.length > 0 ? headerSearchPaths : undefined,
938891
};
939892
}
940893

@@ -1144,12 +1097,6 @@ function generateSynthPackageSwift(spec /*: SynthPackageSpec */) /*: string */ {
11441097
const exclude /*: Array<string> */ = spec.exclude ?? [];
11451098
const sources /*: ?Array<string> */ = spec.sources;
11461099
const publicHeadersPath /*: ?string */ = spec.publicHeadersPath ?? null;
1147-
// Per-dep header search paths from the podspec's
1148-
// pod_target_xcconfig HEADER_SEARCH_PATHS — already prefixed by the caller
1149-
// with the synth wrapper's `root/` so they resolve relative to target.path.
1150-
// Emitted as `.headerSearchPath()` directives, which SPM accepts on
1151-
// cSettings / cxxSettings without needing absolute paths.
1152-
const headerSearchPaths /*: Array<string> */ = spec.headerSearchPaths ?? [];
11531100
const spmDependencies /*: Array<{swiftName: string}> */ =
11541101
spec.spmDependencies ?? [];
11551102
const hasReactDep /*: boolean */ = spec.hasReactDep !== false;
@@ -1233,25 +1180,11 @@ function generateSynthPackageSwift(spec /*: SynthPackageSpec */) /*: string */ {
12331180
packageDeps.length > 0
12341181
? ` dependencies: [\n ${packageDeps.join(',\n ')},\n ],\n`
12351182
: '';
1236-
// `.headerSearchPath(...)` entries from the podspec — first-class
1237-
// directives keep SPM's diagnostics meaningful (clang reports the
1238-
// dep-relative path on miss). React headers need no paths at all.
1239-
const headerSearchPathList = headerSearchPaths.map(
1240-
p => `.headerSearchPath("${p}")`,
1241-
);
1242-
const cSettingsLine =
1243-
headerSearchPathList.length > 0
1244-
? `\n cSettings: [${headerSearchPathList.join(', ')}],`
1245-
: '';
1246-
// ALWAYS emit the config-gated C++ defines (even with no header paths): a
1247-
// Fabric C++ target must compile with the same NDEBUG state as the prebuilt
1248-
// React.framework, else DebugStringConvertible's vtable / ShadowNode layout
1249-
// diverges and a Release build fails to link. Mirrors Package.swift.
1250-
const cxxEntries = [
1251-
...reactCxxDefines(spec.cxxApiDefines),
1252-
...headerSearchPathList,
1253-
];
1254-
const cxxSettingsLine = `\n cxxSettings: [${cxxEntries.join(', ')}],`;
1183+
// ALWAYS emit the config-gated C++ defines: a Fabric C++ target must
1184+
// compile with the same NDEBUG state as the prebuilt React.framework, else
1185+
// DebugStringConvertible's vtable / ShadowNode layout diverges and a
1186+
// Release build fails to link. Mirrors Package.swift.
1187+
const cxxSettingsLine = `\n cxxSettings: [${reactCxxDefines(spec.cxxApiDefines).join(', ')}],`;
12551188

12561189
return `// swift-tools-version: 6.0
12571190
// AUTO-GENERATED by scripts/generate-spm-autolinking.js – do not edit manually.
@@ -1269,7 +1202,7 @@ ${packageDepsBlock} targets: [
12691202
.target(
12701203
name: "${swiftName}",
12711204
dependencies: [${targetDeps.join(', ')}],
1272-
path: "${targetPath}",${excludeLine}${sourcesLine}${publicHeadersLine}${resourcesLine}${cSettingsLine}${cxxSettingsLine}
1205+
path: "${targetPath}",${excludeLine}${sourcesLine}${publicHeadersLine}${resourcesLine}${cxxSettingsLine}
12731206
linkerSettings: [.linkedFramework("UIKit"), .linkedFramework("Foundation"), .linkedFramework("CoreGraphics")]
12741207
),
12751208
],
@@ -1729,15 +1662,6 @@ function main(argv /*:: ?: Array<string> */) /*: void */ {
17291662
const prefixedSources /*: ?Array<string> */ =
17301663
rawSources.length > 0 ? rawSources.map(withRoot) : null;
17311664

1732-
// Podspec HEADER_SEARCH_PATHS were captured relative to the dep's source
1733-
// dir. The wrapper exposes the source dir under `root/` (target.path is
1734-
// `.`, the wrapper dir), so each entry must be prefixed with `root/` so
1735-
// clang sees the real subtree.
1736-
const prefixedHeaderSearchPaths /*: ?Array<string> */ =
1737-
target.headerSearchPaths != null && target.headerSearchPaths.length > 0
1738-
? target.headerSearchPaths.map(withRoot)
1739-
: null;
1740-
17411665
const synthContent = generateSynthPackageSwift({
17421666
swiftName: target.name,
17431667
iosDeploymentTarget: args.iosDeploymentTarget,
@@ -1749,7 +1673,6 @@ function main(argv /*:: ?: Array<string> */) /*: void */ {
17491673
// header farm at <outputDir>/headers is folded into it).
17501674
publicHeadersPath: 'include',
17511675
resources: prefixedResources,
1752-
headerSearchPaths: prefixedHeaderSearchPaths,
17531676
spmDependencies: (target.spmTargetDependencies ?? []).map(swiftName => ({
17541677
swiftName,
17551678
})),

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,6 @@ export type SpmTarget = {
9090
// .js/.podspec/.md siblings, etc. can never sneak in. Null/empty means
9191
// fall back to SPM's default scan of target.path.
9292
sources?: ?Array<string>,
93-
// Header search paths declared by the dep's podspec
94-
// (pod_target_xcconfig HEADER_SEARCH_PATHS), already substituted relative
95-
// to the dep's source dir. Path-style includes like
96-
// `<react/renderer/components/safeareacontext/X.h>` resolve through these.
97-
// Emitted as `.headerSearchPath(...)` directives in the target's
98-
// cSettings / cxxSettings.
99-
headerSearchPaths?: ?Array<string>,
10093
};
10194
10295
// Routing metadata kept alongside an SpmTarget in main(). Lives in a wrapper
@@ -377,13 +370,6 @@ export type SynthPackageSpec = {
377370
// supply absolute paths (tests). Production uses siblingSynthAbsolutePaths.
378371
siblingPackageBaseRelative?: string,
379372
siblingSynthAbsolutePaths?: {[swiftName: string]: string},
380-
// Header search paths from the dep's podspec `pod_target_xcconfig`
381-
// HEADER_SEARCH_PATHS, with `$(PODS_TARGET_SRCROOT)` substituted and the
382-
// synth wrapper's `root/` prefix applied. Emitted as `.headerSearchPath()`
383-
// directives on cSettings / cxxSettings so path-style includes like
384-
// `<react/renderer/components/safeareacontext/X.h>` resolve through the
385-
// dep's own `common/cpp/` subtree.
386-
headerSearchPaths?: ?Array<string>,
387373
iosDeploymentTarget?: ?string,
388374
// React Native C++ stable API macros defined in the synth target.
389375
cxxApiDefines?: ReadonlyArray<string>,

0 commit comments

Comments
 (0)