mirror of
https://github.com/react/react-native.git
synced 2026-09-28 05:13:06 +08:00
Fail with clear error message when a dep depends on an autolinking plugin host (#58087)
Summary: A library can ship with its own SwiftPM autolinking plugin, making the RN autolinker skip generating a target for it since the plugin now owns its native contribution. If another library depends on a library that contains such a plugin, we currently just fail without any explanation to why and how we can fix this. This PR diagnoses this at the declaration level, checking if a dependency is an existing target or not - emitting a clear error about what happens and why it happens. ## Changelog: [IOS] [FIXED] - Added hard fail and clear error message when an autolinking plugin host is referenced as a dependency Pull Request resolved: https://github.com/react/react-native/pull/58087 Test Plan: ✅ Unit tests passes Reviewed By: Abbondanzo Differential Revision: D117196705 Pulled By: cipolleschi fbshipit-source-id: fa7865ec6a5c07232e67240d322c315dad570d17
This commit is contained in:
committed by
meta-codesync[bot]
parent
25ebffd1f7
commit
90a539c92d
@@ -1154,6 +1154,63 @@ describe('main() — autolinking plugin host exemption', () => {
|
||||
main(['--app-root', appRoot, '--react-native-root', rnRoot]),
|
||||
).toThrow(MissingManifestError);
|
||||
});
|
||||
|
||||
// Adds a dep that declares the plugin host in its own `spm.dependencies`.
|
||||
// `selfManaged` gives it a Package.swift of its own — which is what decides
|
||||
// whether React Native emits its package references or the dep does.
|
||||
function addDependentOfExpo(appRoot, name, {selfManaged = false} = {}) {
|
||||
const depDir = path.join(appRoot, 'node_modules', name);
|
||||
fs.mkdirSync(path.join(depDir, 'ios'), {recursive: true});
|
||||
fs.writeFileSync(path.join(depDir, 'ios', 'Dep.mm'), '// native source\n');
|
||||
if (selfManaged) {
|
||||
fs.writeFileSync(
|
||||
path.join(depDir, 'Package.swift'),
|
||||
'// swift-tools-version: 6.0\n',
|
||||
);
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(depDir, 'react-native.config.js'),
|
||||
'module.exports = {dependency: {platforms: {ios: {}}}, ' +
|
||||
"spm: {dependencies: ['expo']}};\n",
|
||||
);
|
||||
const jsonPath = path.join(
|
||||
appRoot,
|
||||
'build/generated/autolinking/autolinking.json',
|
||||
);
|
||||
const json = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
||||
json.dependencies[name] = {root: depDir, platforms: {ios: {}}};
|
||||
fs.writeFileSync(jsonPath, JSON.stringify(json));
|
||||
}
|
||||
|
||||
it('fails when a dep whose manifest RN generates declares the plugin host in its spm.dependencies', () => {
|
||||
const {appRoot, rnRoot} = buildFixture({withPlugin: true});
|
||||
addDependentOfExpo(appRoot, 'react-native-y');
|
||||
const run = () =>
|
||||
main(['--app-root', appRoot, '--react-native-root', rnRoot]);
|
||||
// Both packages, so the reader knows which config to edit and why.
|
||||
expect(run).toThrow(/'react-native-y'/);
|
||||
expect(run).toThrow(/'expo'/);
|
||||
expect(run).toThrow(/autolinking plugin/);
|
||||
expect(run).toThrow(/spm\.dependencies/);
|
||||
});
|
||||
|
||||
it('leaves a self-managed dependent alone — its own Package.swift declares its package references, so RN emits none', () => {
|
||||
const {appRoot, rnRoot} = buildFixture({withPlugin: true});
|
||||
addDependentOfExpo(appRoot, 'react-native-y', {selfManaged: true});
|
||||
expect(() =>
|
||||
main(['--app-root', appRoot, '--react-native-root', rnRoot]),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('leaves the same pair alone when the host ships no plugin — a plain spm.dependency is not a plugin host', () => {
|
||||
const {appRoot, rnRoot} = buildFixture({withPlugin: false});
|
||||
addDependentOfExpo(appRoot, 'react-native-y');
|
||||
// Both deps ship no manifest, so the missing-manifest error is the expected
|
||||
// one — the plugin-host diagnosis must not fire for a plain dependency.
|
||||
expect(() =>
|
||||
main(['--app-root', appRoot, '--react-native-root', rnRoot]),
|
||||
).toThrow(MissingManifestError);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1295,8 +1295,39 @@ function main(argv /*:: ?: Array<string> */) /*: void */ {
|
||||
discoveredPlugins.map(p => p.depName),
|
||||
);
|
||||
|
||||
// Skipped means no sibling package is created for the host either, so a
|
||||
// dep declaring it in `spm.dependencies` gets a package reference to a
|
||||
// path this run never writes — SPM then reports only the missing path.
|
||||
// Only manifests React Native emits can carry that reference: a dep
|
||||
// shipping its own Package.swift declares its package references itself,
|
||||
// and the classification loop below would treat it as self-managed.
|
||||
const pluginHostDependents /*: Map<string, Array<string>> */ = new Map();
|
||||
for (const dep of allDeps) {
|
||||
const declaredHosts = (dep.spmDependencies ?? []).filter(name =>
|
||||
pluginHostDeps.has(name),
|
||||
);
|
||||
if (declaredHosts.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const sourceDir = dep.platforms.ios.sourceDir ?? dep.root;
|
||||
if (sourceDir == null || findSelfManagedPackageDir(sourceDir) != null) {
|
||||
continue;
|
||||
}
|
||||
for (const host of declaredHosts) {
|
||||
const dependents = pluginHostDependents.get(host) ?? [];
|
||||
dependents.push(dep.name);
|
||||
pluginHostDependents.set(host, dependents);
|
||||
}
|
||||
}
|
||||
|
||||
for (const dep of allDeps) {
|
||||
if (pluginHostDeps.has(dep.name)) {
|
||||
const dependents = pluginHostDependents.get(dep.name);
|
||||
if (dependents != null) {
|
||||
throw new Error(
|
||||
`react-native autolinking: '${dep.name}' ships an SPM autolinking plugin, which owns its native contribution — so React Native does not build it as a sibling target for anything to depend on. It is declared in 'spm.dependencies' by ${dependents.map(name => `'${name}'`).join(', ')}. Remove it there; nothing is lost. Its plugin links its products into the app and resolves its own ecosystem's dependencies, so a library that builds against it does not declare it here.`,
|
||||
);
|
||||
}
|
||||
log(
|
||||
`Skipping ${dep.name} target generation — provided by its SPM autolinking plugin`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user