react-native
Version:
A framework for building native apps using React
170 lines (155 loc) • 5.89 kB
JavaScript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
'use strict';
/**
* sync-spm-autolinking.js – Lightweight script invoked by the Xcode pre-build
* phase to re-run invariant codegen and autolinking output when dependency
* inputs have changed.
*
* Usage (called from the Xcode build phase shell script):
* node sync-spm-autolinking.js --app-root <path> --react-native-root <path>
*
* The autolinking config (autolinking.json) is generated by the caller
* (setup-apple-spm.js) BEFORE this script runs, so codegen below can reuse it
* (findCodegenEnabledLibraries short-circuits on a present autolinking.json)
* and the slow `@react-native-community/cli config` runs exactly once per sync.
*
* This script:
* 0. Runs react-native codegen → build/generated/ios/ (reuses autolinking.json)
* 1. Installs the codegen template into build/generated/ios/Package.swift,
* replacing codegen's mis-rooted default (done immediately, before any
* step below that can throw — see the failure-atomicity comment at the
* call site)
* 2. Calls generate-spm-autolinking.js → build/generated/autolinking/Package.swift
* 3. Rebuilds the generated-headers farm
* 4. Writes build/generated/autolinking/.spm-sync-stamp
*
* Runtime frameworks are deliberately not downloaded or regenerated from an
* Xcode build. `spm add` / `spm update` own the immutable dual-flavor slots and
* the app target's linker/embed configuration.
*/
const {main: generateAutolinking} = require('./generate-spm-autolinking');
const {
RemoteVersionError,
buildPerAppHeaderTree,
findProjectRoot,
installSpmCodegenTemplate,
makeLogger,
runCodegenAndInstallTemplate,
} = require('./spm-utils');
const fs = require('node:fs');
const path = require('node:path');
const yargs = require('yargs');
const {log} = makeLogger('sync-spm-autolinking');
// Collaborators are injected (with these real implementations as defaults) so
// main() can be exercised end-to-end in tests without mocking the module
// system. Tests pass fakes that record calls and point defaultCacheDir at a
// tempdir; everything fs-based runs for real against that tempdir.
const defaultDeps = {
runCodegenAndInstallTemplate,
generateAutolinking,
installSpmCodegenTemplate,
buildPerAppHeaderTree,
findProjectRoot,
};
async function main(
argv /*:: ?: Array<string> */,
overrides /*:: ?: Partial<typeof defaultDeps> */,
) /*: Promise<void> */ {
const deps = {...defaultDeps, ...(overrides ?? {})};
const parsed = yargs(argv ?? process.argv.slice(2))
.version(false)
.option('app-root', {
type: 'string',
demandOption: true,
describe: 'Path to the app directory',
})
.option('react-native-root', {
type: 'string',
demandOption: true,
describe: 'Path to react-native package root',
})
.option('ios-deployment-target', {
type: 'string',
describe: 'Platform floor forwarded to the autolinker',
})
.help()
.parseSync();
const appRoot = path.resolve(parsed['app-root']);
const reactNativeRoot = path.resolve(parsed['react-native-root']);
const projectRoot = deps.findProjectRoot(appRoot);
// The caller (setup-apple-spm.js) already generated autolinking.json before
// invoking this script, so codegen reuses it here.
try {
deps.runCodegenAndInstallTemplate(
projectRoot,
appRoot,
reactNativeRoot,
{log},
{
installTemplate: false,
},
);
} catch {
log('Codegen failed — continuing with existing output');
}
// Install the codegen template right away, before artifact download and
// autolinking-plugin execution below. Failure-atomicity: generateAutolinking
// runs fail-closed plugins that can throw, and if that happens before the
// template is installed, codegen's mis-rooted default manifest is left in
// build/generated/ios/Package.swift — breaking every subsequent Xcode
// "Resolve Package Graph". Installing it immediately after codegen means a
// later throw can never leave that broken manifest in place.
deps.installSpmCodegenTemplate(appRoot, reactNativeRoot, {log});
log('Re-generating build/generated/autolinking/Package.swift...');
const autolinkingArgv = [
'--app-root',
appRoot,
'--react-native-root',
reactNativeRoot,
];
const iosDeploymentTarget = parsed['ios-deployment-target'];
if (iosDeploymentTarget != null) {
autolinkingArgv.push('--ios-deployment-target', iosDeploymentTarget);
}
deps.generateAutolinking(autolinkingArgv);
// Rebuild the per-app generated-headers farm (vended as the ReactAppHeaders
// SPM target inside the codegen package). React core headers need no trees
// — they are vended by the invariant ReactHeaders/ReactNativeHeaders products.
// The
// generated manifests are fully declarative (fixed-relative package paths),
// so no path-locator JSON is written.
deps.buildPerAppHeaderTree(appRoot, {log});
const stampPath = path.join(
appRoot,
'build',
'generated',
'autolinking',
'.spm-sync-stamp',
);
fs.mkdirSync(path.dirname(stampPath), {recursive: true});
fs.writeFileSync(stampPath, new Date().toISOString() + '\n', 'utf8');
log('SPM autolinking sync complete.');
}
if (require.main === module) {
main().catch(e => {
if (e instanceof RemoteVersionError) {
// Clean message + exit 2 (the build phase hard-fails on it) instead of a
// stack trace, matching how setup-apple-spm.js surfaces remote-mode
// version errors.
log(e.message);
process.exitCode = 2;
return;
}
console.error(e);
process.exitCode = 1;
});
}
module.exports = {main};