Skip to content

Commit c1b508a

Browse files
authored
fix: ios example app has duplicated symbols due to codegen (#757)
### Summary Fixed #755 There is an effort towards shipping React Native in a prebuilt format on iOS. As a result, some of the codegen-related custom code have to be generated at the build time. However, these changes also affected the codegen generated native code for libraries. This PR patches that behavior on bob level. Related PRs: - facebook/react-native#47518 - facebook/react-native#47650 - facebook/react-native#47517 ### Test plan This adds 3 unit test cases to make sure we're able to parse the codegen paths and delete the necessary files. #### To test it locally: 1. Build bob 1. Create a new library using `npx create-react-native-library`, and pick turbo modules 1. Link the local bob by calling `yarn link path/to/bob --all` in the new library 1. Call `yarn bob build --target codegen` 1. Make sure there is no `.podspec` file or mm files in `android/generated` 1. Make sure there is no `.podspec` file or any of the following files in `ios/generated`: ``` 'RCTAppDependencyProvider.h', 'RCTAppDependencyProvider.mm', 'RCTModulesConformingToProtocolsProvider.h', 'RCTModulesConformingToProtocolsProvider.mm', 'RCTThirdPartyComponentsProvider.h', 'RCTThirdPartyComponentsProvider.mm', 'ReactAppDependencyProvider.podspec', ```
1 parent 4c046a2 commit c1b508a

File tree

5 files changed

+178
-8
lines changed

5 files changed

+178
-8
lines changed

packages/react-native-builder-bob/src/targets/codegen.ts renamed to packages/react-native-builder-bob/src/targets/codegen/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import kleur from 'kleur';
2-
import type { Input } from '../types';
3-
import { patchCodegenAndroidPackage } from '../utils/patchCodegenAndroidPackage';
2+
import type { Input } from '../../types';
3+
import { patchCodegenAndroidPackage } from './patches/patchCodegenAndroidPackage';
44
import fs from 'fs-extra';
55
import path from 'path';
66
import del from 'del';
7-
import { runRNCCli } from '../utils/runRNCCli';
7+
import { runRNCCli } from '../../utils/runRNCCli';
8+
import { removeCodegenAppLevelCode } from './patches/removeCodegenAppLevelCode';
89

910
type Options = Input;
1011

@@ -47,6 +48,7 @@ export default async function build({ root, report }: Options) {
4748
if (codegenType === 'modules' || codegenType === 'all') {
4849
await patchCodegenAndroidPackage(root, packageJson, report);
4950
}
51+
await removeCodegenAppLevelCode(root, packageJson);
5052

5153
report.success('Generated native code with codegen');
5254
} catch (e: unknown) {

packages/react-native-builder-bob/src/__tests__/patchCodegenAndroidPackage.test.ts renamed to packages/react-native-builder-bob/src/targets/codegen/patches/patchCodegenAndroidPackage.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { expect, it, describe, beforeEach, afterEach } from '@jest/globals';
22
import fs from 'fs-extra';
33
import path from 'node:path';
4-
import { patchCodegenAndroidPackage } from '../utils/patchCodegenAndroidPackage';
4+
import { patchCodegenAndroidPackage } from './patchCodegenAndroidPackage';
55
import mockfs from 'mock-fs';
6-
import type { Report } from '../types';
6+
import type { Report } from '../../../types';
77

88
const mockPackageJson = {
99
codegenConfig: {

packages/react-native-builder-bob/src/utils/patchCodegenAndroidPackage.ts renamed to packages/react-native-builder-bob/src/targets/codegen/patches/patchCodegenAndroidPackage.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import fs from 'fs-extra';
22
import path from 'path';
3-
import type { Report } from '../types';
3+
import type { Report } from '../../../types';
44

5-
const CODEGEN_DOCS =
6-
'https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-libraries-prerequisites.md#configure-codegen';
5+
export const CODEGEN_DOCS =
6+
'https://reactnative.dev/docs/the-new-architecture/using-codegen#configuring-codegen';
77

88
/**
99
* Currently, running react-native codegen generates java files with package name `com.facebook.fbreact.specs`.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { expect, it, describe, beforeEach, afterEach } from '@jest/globals';
2+
import fs from 'fs-extra';
3+
import path from 'node:path';
4+
import { removeCodegenAppLevelCode } from './removeCodegenAppLevelCode';
5+
import mockfs from 'mock-fs';
6+
7+
const mockPackageJson = {
8+
codegenConfig: {
9+
outputDir: {
10+
android: 'android/generated',
11+
ios: 'ios/generated',
12+
},
13+
},
14+
};
15+
16+
const mockProjectPath = path.resolve(__dirname, 'mockProject');
17+
18+
describe('patchCodegenAndroidPackage', () => {
19+
beforeEach(() => {
20+
mockfs({
21+
[mockProjectPath]: {
22+
'package.json': JSON.stringify(mockPackageJson),
23+
'ios': {
24+
generated: {
25+
'RCTAppDependencyProvider.h': '',
26+
'RCTAppDependencyProvider.mm': '',
27+
'RCTModulesConformingToProtocolsProvider.h': '',
28+
'RCTModulesConformingToProtocolsProvider.mm': '',
29+
'RCTThirdPartyComponentsProvider.h': '',
30+
'RCTThirdPartyComponentsProvider.mm': '',
31+
'ReactAppDependencyProvider.podspec': '',
32+
},
33+
},
34+
'android': {
35+
generated: {
36+
'RCTAppDependencyProvider.h': '',
37+
'RCTAppDependencyProvider.mm': '',
38+
'RCTModulesConformingToProtocolsProvider.h': '',
39+
'RCTModulesConformingToProtocolsProvider.mm': '',
40+
'RCTThirdPartyComponentsProvider.h': '',
41+
'RCTThirdPartyComponentsProvider.mm': '',
42+
'ReactAppDependencyProvider.podspec': '',
43+
},
44+
},
45+
},
46+
});
47+
});
48+
49+
afterEach(() => {
50+
mockfs.restore();
51+
});
52+
53+
it('removes the duplicate iOS files', async () => {
54+
await removeCodegenAppLevelCode(mockProjectPath, mockPackageJson);
55+
56+
expect(
57+
(
58+
await fs.promises.readdir(
59+
path.join(mockProjectPath, 'ios', 'generated')
60+
)
61+
).length
62+
).toBe(0);
63+
});
64+
65+
it('removes the unnecessary Android files', async () => {
66+
await removeCodegenAppLevelCode(mockProjectPath, mockPackageJson);
67+
68+
expect(
69+
(
70+
await fs.promises.readdir(
71+
path.join(mockProjectPath, 'android', 'generated')
72+
)
73+
).length
74+
).toBe(0);
75+
});
76+
77+
it("doesn't crash the process when there are no files to remove", async () => {
78+
mockfs({
79+
[mockProjectPath]: {
80+
'package.json': JSON.stringify(mockPackageJson),
81+
'ios': {
82+
generated: {
83+
someRandomFile: '',
84+
},
85+
},
86+
'android': {
87+
generated: {
88+
someRandomFile: '',
89+
},
90+
},
91+
},
92+
});
93+
94+
await expect(
95+
removeCodegenAppLevelCode(mockProjectPath, mockPackageJson)
96+
).resolves.not.toThrow();
97+
});
98+
});
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import fs from 'fs-extra';
2+
import path from 'path';
3+
import { CODEGEN_DOCS } from './patchCodegenAndroidPackage';
4+
5+
const FILES_TO_REMOVE = [
6+
'RCTAppDependencyProvider.h',
7+
'RCTAppDependencyProvider.mm',
8+
'RCTModulesConformingToProtocolsProvider.h',
9+
'RCTModulesConformingToProtocolsProvider.mm',
10+
'RCTThirdPartyComponentsProvider.h',
11+
'RCTThirdPartyComponentsProvider.mm',
12+
'ReactAppDependencyProvider.podspec',
13+
];
14+
15+
/**
16+
* With React Native 0.77, calling `@react-native-community/cli codegen` generates
17+
* some app level source files such as `RCTAppDependencyProvider.mm`.
18+
* These files are supposed to be only generated for apps
19+
* but the cli misbehaves and generates them for all sorts of projects.
20+
* You can find the relevant PR here: https://github.com/facebook/react-native/pull/47650
21+
* This patch can be removed when this gets fixed in React Native.
22+
*/
23+
export async function removeCodegenAppLevelCode(
24+
projectPath: string,
25+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
26+
packageJson: any
27+
) {
28+
const codegenAndroidPathSetting: string | undefined =
29+
packageJson.codegenConfig?.outputDir?.android;
30+
if (!codegenAndroidPathSetting) {
31+
throw new Error(
32+
`Your package.json doesn't contain codegenConfig.outputDir.android. Please see ${CODEGEN_DOCS}`
33+
);
34+
}
35+
const codegenAndroidPath = path.resolve(
36+
projectPath,
37+
codegenAndroidPathSetting
38+
);
39+
40+
if (!(await fs.pathExists(codegenAndroidPath))) {
41+
throw new Error(
42+
`The codegen android path defined in your package.json: ${codegenAndroidPath} doesnt' exist.`
43+
);
44+
}
45+
46+
const codegenIosPathSetting: string | undefined =
47+
packageJson.codegenConfig?.outputDir?.ios;
48+
if (!codegenIosPathSetting) {
49+
throw new Error(
50+
`Your package.json doesn't contain codegenConfig.outputDir.ios. Please see ${CODEGEN_DOCS}`
51+
);
52+
}
53+
const codegenIosPath = path.resolve(projectPath, codegenIosPathSetting);
54+
55+
if (!(await fs.pathExists(codegenAndroidPath))) {
56+
throw new Error(
57+
`The codegen iOS path defined in your package.json: ${codegenIosPathSetting} doesnt' exist.`
58+
);
59+
}
60+
61+
const androidPromises = FILES_TO_REMOVE.map((fileName) =>
62+
fs.rm(path.join(codegenAndroidPath, fileName))
63+
);
64+
65+
const iosPromises = FILES_TO_REMOVE.map((fileName) =>
66+
fs.rm(path.join(codegenIosPath, fileName))
67+
);
68+
69+
await Promise.allSettled([...androidPromises, ...iosPromises]);
70+
}

0 commit comments

Comments
 (0)