Skip to content

Commit d576b40

Browse files
chrfalchmeta-codesync[bot]
authored andcommitted
SwiftPM scaffold: emit resources before publicHeadersPath (#59494)
Summary: The SwiftPM scaffolder (`npx react-native spm scaffold`) writes a `Package.swift` for a library from its podspec. When the podspec has `s.resources` and the library has public headers, the scaffolder writes `publicHeadersPath:` before `resources:` in the target. Swift requires the parameters in their declared order, so package resolution fails: ``` error: argument 'resources' must precede argument 'publicHeadersPath' ``` This change: 1. Writes `resources:` before `publicHeadersPath:` in the scaffolded target (`scaffold-package-swift.js`). 2. Raises `SCAFFOLDER_VERSION` from 20 to 21. `spm scaffold` then writes the manifest again for libraries that still have a manifest from an older scaffolder version. `spm update` does not do this. The autolinker (`generate-spm-autolinking.js`) writes the same two arguments in the same wrong order, at two places. Neither place can fail today, so this PR leaves them unchanged: - `generateAutolinkedPackageSwift` writes inline targets, but `main()` passes none. - `generateSynthPackageSwift` runs, but only for `spmModules` targets, and those never set `resources`. So it never writes the `resources:` line today. A note on privacy manifests: this work started from the belief that scaffolded libraries lose the `PrivacyInfo.xcprivacy` file that their podspec lists in `resource_bundles`. That is not the case. The scaffolded target uses `path: "."`, so SwiftPM finds the file by itself and copies it into the app. #57305 is a different fix: it covers React Native's own privacy manifests in the prebuilt `React.framework`. This PR does not depend on other open PRs. ## Changelog: [IOS] [FIXED] - SwiftPM: scaffolded `Package.swift` writes `resources:` before `publicHeadersPath:`, so libraries with podspec resources and public headers resolve Pull Request resolved: #59494 Test Plan: Unit tests (from the repository root): ``` yarn jest packages/react-native/scripts/spm ``` - A new test checks that `resources:` comes before `publicHeadersPath:`. It failed before the fix (`Expected: < 1471, Received: 1509`) and passes after it. - All 25 test suites and 1188 tests pass. - `eslint --max-warnings 0` and `prettier --check` are clean on both changed files. End to end, with a new React Native 0.88.0 app and `react-native-device-info` 15.0.2, converted with `npx react-native spm add --deintegrate`: 1. For this test only, I added `s.resources = ['ios/PrivacyInfo.xcprivacy']` to the library's podspec. Without the fix, `spm scaffold` wrote a manifest that failed with the error above. 2. With this fix applied to the app's `node_modules/react-native/scripts/spm/scaffold-package-swift.js`: - `npx react-native spm scaffold` wrote version 21, with `resources:` before `publicHeadersPath:`. - An `xcodebuild` Debug build for the iOS simulator gave `BUILD SUCCEEDED`. - The app contains `RNDeviceInfo_RNDeviceInfo.bundle/PrivacyInfo.xcprivacy`, and it is byte-identical to the library's file. The build gave no duplicate-resource warning. Not covered: the device-info build also needed a hand edit to `publicHeadersPath: "ios/RNDeviceInfo"`. The scaffolder writes `"ios"`, and Xcode 27.1 RC rejects that header layout. That is a separate scaffolder issue, and a separate PR will fix it. Reviewed By: christophpurrer Differential Revision: D127581976 Pulled By: cipolleschi fbshipit-source-id: c6a958d6cabcc73ef98b5f1bce9d65b15052159a
1 parent a185da2 commit d576b40

2 files changed

Lines changed: 32 additions & 3 deletions

File tree

‎packages/react-native/scripts/spm/__tests__/scaffold-package-swift-test.js‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,33 @@ end
853853
expect(content).toContain(SCAFFOLDER_MARKER);
854854
});
855855

856+
it('emits resources before publicHeadersPath, the order SwiftPM requires', () => {
857+
fs.mkdirSync(path.join(depRoot, 'ios'));
858+
fs.writeFileSync(path.join(depRoot, 'ios', 'Foo.h'), '');
859+
fs.writeFileSync(
860+
path.join(depRoot, 'react-native-foo.podspec'),
861+
`
862+
Pod::Spec.new do |s|
863+
s.name = "react-native-foo"
864+
s.version = "1.0"
865+
s.source_files = "ios/**/*.{h,m,mm}"
866+
s.resources = ["ios/Foo.bundle"]
867+
s.dependency "React-Core"
868+
end
869+
`,
870+
);
871+
scaffoldPackageSwiftForDep(makeDep(), makeCtx());
872+
const content = fs.readFileSync(
873+
path.join(depRoot, 'Package.swift'),
874+
'utf8',
875+
);
876+
const resourcesAt = content.indexOf('resources: [.copy("ios/Foo.bundle")]');
877+
const publicHeadersAt = content.indexOf('publicHeadersPath: "ios"');
878+
expect(resourcesAt).toBeGreaterThan(-1);
879+
expect(publicHeadersAt).toBeGreaterThan(-1);
880+
expect(resourcesAt).toBeLessThan(publicHeadersAt);
881+
});
882+
856883
it('skips (does not write) a mixed-language dep — Swift + ObjC(++) cannot share one SPM target', () => {
857884
// react-native-screens shape: a single source glob mixing .swift and .mm.
858885
const podspecPath = path.join(depRoot, 'react-native-foo.podspec');
@@ -1542,7 +1569,7 @@ describe('SCAFFOLDER_VERSION', () => {
15421569
});
15431570

15441571
it('is at the version the current emitter output requires', () => {
1545-
expect(SCAFFOLDER_VERSION).toBe(20);
1572+
expect(SCAFFOLDER_VERSION).toBe(21);
15461573
});
15471574

15481575
it('emitter writes the current version to the file', () => {

‎packages/react-native/scripts/spm/scaffold-package-swift.js‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ const {log, warn} = makeLogger('scaffold-package-swift');
110110
// under NDEBUG). Bumped so existing scaffolds regenerate with the defines.
111111
// v20: the platform floor is the app's iOS deployment target in string form
112112
// (the `.v15` enum cannot express a dependency minimum like 16.4).
113-
const SCAFFOLDER_VERSION = 20;
113+
// v21: `resources:` is emitted before `publicHeadersPath:`, the argument order
114+
// `.target(...)` requires (the reverse order fails to resolve).
115+
const SCAFFOLDER_VERSION = 21;
114116
const SCAFFOLDER_VERSION_LINE_RE = /^\/\/ AUTO-SCAFFOLDED-VERSION: (\d+)$/m;
115117

116118
const AUTOGEN_MARKER =
@@ -778,7 +780,7 @@ let package = Package(
778780
${packageDepsBlock} targets: [
779781
.target(
780782
name: "${spec.swiftName}",${targetDepsLine}
781-
path: ".",${sourcesLine}${publicHeadersLine}${resourcesLine}
783+
path: ".",${sourcesLine}${resourcesLine}${publicHeadersLine}
782784
cSettings: ${cSettings},
783785
cxxSettings: ${cxxSettings},
784786
linkerSettings: [${linkerEntries.join(', ')}]

0 commit comments

Comments
 (0)