Skip to content

Commit 5c0e60f

Browse files
committed
fix(spm): avoid running codegen config and template install twice per sync
sync-spm-autolinking.js regenerated autolinking.json itself, even though setup-apple-spm.js already generates it right before calling the sync. This ran the slow `@react-native-community/cli config` crawl twice, and ran codegen before the refresh — so it could reuse a stale autolinking.json and miss a newly installed native lib's codegen specs. Now the config runs once (in the caller), codegen reuses its fresh output, and the codegen template is installed only once, after the xcframework symlinks are repointed.
1 parent 64a21f1 commit 5c0e60f

3 files changed

Lines changed: 100 additions & 16 deletions

File tree

packages/react-native/scripts/spm/__tests__/spm-utils-test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const {
1616
makeLogger,
1717
readPackageJson,
1818
resolveReactNativeRoot,
19+
runCodegenAndInstallTemplate,
1920
toSwiftName,
2021
} = require('../spm-utils');
2122
const fs = require('fs');
@@ -217,3 +218,73 @@ describe('resolveReactNativeRoot', () => {
217218
).toBe(rnRoot);
218219
});
219220
});
221+
222+
// ---------------------------------------------------------------------------
223+
// runCodegenAndInstallTemplate
224+
// ---------------------------------------------------------------------------
225+
226+
describe('runCodegenAndInstallTemplate', () => {
227+
let tempDir;
228+
let reactNativeRoot;
229+
let appRoot;
230+
let codegenPkgSwift;
231+
232+
beforeEach(() => {
233+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'spm-codegen-test-'));
234+
reactNativeRoot = path.join(tempDir, 'react-native');
235+
appRoot = path.join(tempDir, 'app');
236+
237+
// Minimal fake codegen script (no-op) so the execSync call exits cleanly.
238+
fs.mkdirSync(path.join(reactNativeRoot, 'scripts'), {recursive: true});
239+
fs.writeFileSync(
240+
path.join(reactNativeRoot, 'scripts', 'generate-codegen-artifacts.js'),
241+
'// no-op codegen for tests\n',
242+
);
243+
// Codegen template that installSpmCodegenTemplate renders + writes.
244+
fs.mkdirSync(
245+
path.join(reactNativeRoot, 'scripts', 'codegen', 'templates'),
246+
{
247+
recursive: true,
248+
},
249+
);
250+
fs.writeFileSync(
251+
path.join(
252+
reactNativeRoot,
253+
'scripts',
254+
'codegen',
255+
'templates',
256+
'Package.swift.spm-template',
257+
),
258+
'// template\n',
259+
);
260+
// build/generated/ios must exist for the template to be installed.
261+
fs.mkdirSync(path.join(appRoot, 'build', 'generated', 'ios'), {
262+
recursive: true,
263+
});
264+
codegenPkgSwift = path.join(
265+
appRoot,
266+
'build',
267+
'generated',
268+
'ios',
269+
'Package.swift',
270+
);
271+
});
272+
273+
afterEach(() => {
274+
fs.rmSync(tempDir, {recursive: true, force: true});
275+
});
276+
277+
it('installs the codegen template by default', () => {
278+
runCodegenAndInstallTemplate(appRoot, appRoot, reactNativeRoot);
279+
expect(fs.existsSync(codegenPkgSwift)).toBe(true);
280+
});
281+
282+
it('skips the template install when installTemplate is false', () => {
283+
runCodegenAndInstallTemplate(appRoot, appRoot, reactNativeRoot, undefined, {
284+
installTemplate: false,
285+
});
286+
// The SPM sync re-points the xcframework symlinks and installs the template
287+
// itself afterwards, so this in-codegen install must be suppressed.
288+
expect(fs.existsSync(codegenPkgSwift)).toBe(false);
289+
});
290+
});

packages/react-native/scripts/spm/spm-utils.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ function runCodegenAndInstallTemplate(
330330
appRoot /*: string */,
331331
reactNativeRoot /*: string */,
332332
logger /*: {log: (msg: string) => void} */ = {log() {}},
333+
opts /*: {installTemplate?: boolean} */ = {},
333334
) /*: void */ {
334335
const codegenScript = path.join(
335336
reactNativeRoot,
@@ -345,7 +346,13 @@ function runCodegenAndInstallTemplate(
345346
`node "${codegenScript}" -p "${projectRoot}" -t ios` +
346347
(projectRoot !== appRoot ? ` -o "${appRoot}"` : '');
347348
execSync(codegenArgs, {stdio: 'inherit', cwd: projectRoot});
348-
installSpmCodegenTemplate(appRoot, reactNativeRoot, logger);
349+
// Callers that re-point the xcframework symlinks after codegen (e.g. the SPM
350+
// sync, which runs generate-spm-package afterwards) install the template
351+
// themselves once the symlinks are final; they pass installTemplate: false to
352+
// avoid a wasted write that renderCodegenTemplate would immediately supersede.
353+
if (opts.installTemplate !== false) {
354+
installSpmCodegenTemplate(appRoot, reactNativeRoot, logger);
355+
}
349356
}
350357

351358
module.exports = {

packages/react-native/scripts/spm/sync-spm-autolinking.js

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@
1818
* Usage (called from the Xcode build phase shell script):
1919
* node sync-spm-autolinking.js --app-root <path> --react-native-root <path>
2020
*
21+
* The autolinking config (autolinking.json) is generated by the caller
22+
* (setup-apple-spm.js) BEFORE this script runs, so codegen below can reuse it
23+
* (findCodegenEnabledLibraries short-circuits on a present autolinking.json)
24+
* and the slow `@react-native-community/cli config` runs exactly once per sync.
25+
*
2126
* This script:
22-
* 0. Runs react-native codegen → build/generated/ios/
27+
* 0. Runs react-native codegen → build/generated/ios/ (reuses autolinking.json)
2328
* 1. Ensures xcframework artifacts are downloaded (auto-heals fresh clones)
2429
* 2. Calls generate-spm-autolinking.js → build/generated/autolinking/Package.swift
2530
* 3. Calls generate-spm-package.js → build/xcframeworks/Package.swift + symlinks
26-
* 4. Resolves the VFS overlay template → build/xcframeworks/React-VFS.yaml
31+
* 4. Installs the codegen template + resolves the VFS overlay template
2732
* 5. Writes build/generated/autolinking/.spm-sync-stamp
2833
*/
2934

@@ -32,9 +37,6 @@ const {
3237
resolveCacheSlotVersion,
3338
} = require('./download-spm-artifacts');
3439
const {main: generateAutolinking} = require('./generate-spm-autolinking');
35-
const {
36-
generateAutolinkingConfig,
37-
} = require('./generate-spm-autolinking-config');
3840
const {main: generatePackage} = require('./generate-spm-package');
3941
const {
4042
defaultCacheDir,
@@ -72,21 +74,25 @@ async function main(argv /*:: ?: Array<string> */) /*: Promise<void> */ {
7274
const reactNativeRoot = path.resolve(parsed['react-native-root']);
7375
const projectRoot = findProjectRoot(appRoot);
7476

77+
// The caller (setup-apple-spm.js) already generated autolinking.json before
78+
// invoking this script, so codegen reuses it here. Defer the codegen template
79+
// install until after generate-spm-package re-points the xcframework symlinks
80+
// (see installSpmCodegenTemplate call below) — installing it now would write a
81+
// template that the post-symlink install immediately supersedes.
7582
try {
76-
runCodegenAndInstallTemplate(projectRoot, appRoot, reactNativeRoot, {log});
83+
runCodegenAndInstallTemplate(
84+
projectRoot,
85+
appRoot,
86+
reactNativeRoot,
87+
{log},
88+
{
89+
installTemplate: false,
90+
},
91+
);
7792
} catch {
7893
log('Codegen failed — continuing with existing output');
7994
}
8095

81-
try {
82-
const {outputPath} = generateAutolinkingConfig({projectRoot});
83-
log(`Refreshed ${path.relative(appRoot, outputPath)}`);
84-
} catch (e) {
85-
log(
86-
`Autolinking config refresh failed: ${e.message}. Using existing autolinking.json if present.`,
87-
);
88-
}
89-
9096
const pkg = readPackageJson(reactNativeRoot);
9197
const rawVersion = pkg?.version ?? '0.0.0';
9298
const flavor = 'debug';

0 commit comments

Comments
 (0)