Skip to content

Commit a90142f

Browse files
authored
feat: ship codegen-generated specs (#566)
## Summary So far with the new architecture-supported templates, we've been generating libraries that didn't ship their codegen-generated specs. This means the library's users had to build the codegen specs on their end (this was done implicitly). With this, the codegen-generated specs are generated at the build time and they are shipped with the library. I've followed [this guide from the React Native new arch working group](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/codegen.md#including-generated-code-into-libraries). ### Making sure example app builds are triggering codegen An important problem to figure out was to make sure the codegen-generated specs were being built with each native build of the example app. To do that,`create-react-native-library` now modifies non-legacy example apps and adds: 1. A new task to `example/android/build.gradle` that's triggered before each native build. 2. A prebuild action to the XCode build schema. 3. A `pre_install` hook to `example/ios/Podfile` that's triggered when user calls `pod install`. These modifications make sure `yarn codegen` is called on the repo root to generate the codegen specs. ### Notes 1. There is an important problem with React Native itself right now. When `react-native codegen` is called, the generated Java code doesn't follow the `codegenConfig.android.javaPackageName` property in the `package.json`. This means the generated Java files are stored in a default location with the wrong package name. To fix it, I've added a script that moves the codegen-generated files into the correct place. You can check facebook/react-native#45079 to see more. ## Test plan ### Test if Android builds trigger codegen 1. Create a non-legacy library using `create-react-native-library` 2. Install the dependencies and build the example app on Android 3. Make sure the build passes, and there are files under `android/generated`. ### Test if installing pods triggers codegen 1. Create a non-legacy library using `create-react-native-library` 2. Install the dependencies and run `pod install` in `example/ios` 3. Make sure the pods are installed and there are files under `ios/generated`. ### Test if iOS builds trigger codegen 1. Create a non-legacy library using `create-react-native-library` 2. Install the dependencies and run `pod install` in `example/ios` 3. Remove the codegen generated code from `ios/generated` since that's generated by the pod install step 4. Build the app for iOS 5. Make sure there are files in `ios/generated`. ### Test if building the library triggers codegen 1. Run `yarn prepare` to emulate the library release process 2. Run `yarn pack` 3. Extract the files from the generated `package.tgz` 4. Make sure there are files under `ios/generated`, and `android/generated` in the generated package.
1 parent a4450d2 commit a90142f

File tree

18 files changed

+572
-54
lines changed

18 files changed

+572
-54
lines changed

packages/create-react-native-library/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import generateExampleApp, {
1313
} from './utils/generateExampleApp';
1414
import { spawn } from './utils/spawn';
1515
import { version } from '../package.json';
16+
import { addCodegenBuildScript } from './utils/addCodegenBuildScript';
1617

1718
const FALLBACK_BOB_VERSION = '0.29.0';
1819

@@ -788,6 +789,10 @@ async function create(_argv: yargs.Arguments<any>) {
788789
rootPackageJson.devDependencies['react-native'] =
789790
examplePackageJson.dependencies['react-native'];
790791
}
792+
793+
if (arch !== 'legacy') {
794+
addCodegenBuildScript(folder, options.project.name);
795+
}
791796
}
792797

793798
// Some of the passed args can already be derived from the generated package.json file.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import path from 'path';
2+
import fs from 'fs-extra';
3+
4+
// This is added to the example app's build.gradle file to invoke codegen before every build
5+
const GRADLE_INVOKE_CODEGEN_TASK = `
6+
def isNewArchitectureEnabled() {
7+
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
8+
}
9+
10+
if (isNewArchitectureEnabled()) {
11+
// Since our library doesn't invoke codegen automatically we need to do it here.
12+
tasks.register('invokeLibraryCodegen', Exec) {
13+
workingDir "$rootDir/../../"
14+
commandLine "npx", "bob", "build", "--target", "codegen"
15+
}
16+
preBuild.dependsOn invokeLibraryCodegen
17+
}`;
18+
19+
// This is added to the example app's xcscheme file to invoke codegen before every build
20+
const XCODE_INVOKE_CODEGEN_ACTION = `
21+
<PreActions>
22+
<ExecutionAction
23+
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
24+
<ActionContent
25+
title = "Invoke Codegen"
26+
scriptText = "cd &quot;$WORKSPACE_PATH/../../../&quot; &amp;&amp; npx bob build --target codegen&#10;">
27+
</ActionContent>
28+
</ExecutionAction>
29+
</PreActions>`;
30+
31+
// You need to have the files before calling pod install otherwise they won't be registered in your pod.
32+
// So we add a pre_install hook to the podfile that invokes codegen
33+
const PODSPEC_INVOKE_CODEGEN_SCRIPT = `
34+
pre_install do |installer|
35+
system("cd ../../ && npx bob build --target codegen")
36+
end
37+
`;
38+
39+
/**
40+
* Codegen isn't invoked for libraries with `includesGeneratedCode` set to `true`.
41+
* This patches the example app to invoke library codegen on every app build.
42+
*/
43+
export async function addCodegenBuildScript(
44+
libraryPath: string,
45+
projectName: string
46+
) {
47+
const appBuildGradlePath = path.join(
48+
libraryPath,
49+
'example',
50+
'android',
51+
'app',
52+
'build.gradle'
53+
);
54+
const exampleAppBuildSchemePath = path.join(
55+
libraryPath,
56+
'example',
57+
'ios',
58+
`${projectName}Example.xcodeproj`,
59+
'xcshareddata',
60+
'xcschemes',
61+
`${projectName}Example.xcscheme`
62+
);
63+
const podfilePath = path.join(libraryPath, 'example', 'ios', 'Podfile');
64+
65+
// Add a gradle task that runs before every build
66+
let appBuildGradle = (await fs.readFile(appBuildGradlePath)).toString();
67+
appBuildGradle += GRADLE_INVOKE_CODEGEN_TASK;
68+
69+
await fs.writeFile(appBuildGradlePath, appBuildGradle);
70+
71+
// Add an XCode prebuild action.
72+
const exampleAppBuildScheme = (await fs.readFile(exampleAppBuildSchemePath))
73+
.toString()
74+
.split('\n');
75+
// Used XCode and inspected the result to determine where it inserts the actions
76+
const actionTargetLineIndex = exampleAppBuildScheme.findIndex((line) =>
77+
line.includes('<BuildActionEntries>')
78+
);
79+
exampleAppBuildScheme.splice(
80+
actionTargetLineIndex,
81+
0,
82+
XCODE_INVOKE_CODEGEN_ACTION
83+
);
84+
85+
await fs.writeFile(
86+
exampleAppBuildSchemePath,
87+
exampleAppBuildScheme.join('\n')
88+
);
89+
90+
// Add a preinstall action to the podfile that invokes codegen
91+
const podfile = (await fs.readFile(podfilePath)).toString().split('\n');
92+
const podfilePostInstallIndex = podfile.findIndex((line) =>
93+
line.includes('post_install do |installer|')
94+
);
95+
podfile.splice(podfilePostInstallIndex, 0, PODSPEC_INVOKE_CODEGEN_SCRIPT);
96+
97+
await fs.writeFile(podfilePath, podfile.join('\n'));
98+
}

packages/create-react-native-library/templates/common/$.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,7 @@ android/keystores/debug.keystore
7676

7777
# generated by bob
7878
lib/
79+
80+
# React Native Codegen
81+
ios/generated
82+
android/generated

packages/create-react-native-library/templates/common/$package.json

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@
168168
"source": "src",
169169
"output": "lib",
170170
"targets": [
171+
<% if (project.arch !== 'legacy') { -%>
172+
"codegen",
173+
<% } -%>
171174
[
172175
"commonjs",
173176
{
@@ -191,8 +194,16 @@
191194
},
192195
"codegenConfig": {
193196
"name": "RN<%- project.name -%><%- project.view ? 'View': '' -%>Spec",
194-
"type": <%- project.view ? '"components"': '"modules"' %>,
195-
"jsSrcsDir": "src"
197+
"type": "all",
198+
"jsSrcsDir": "src",
199+
"outputDir": {
200+
"ios": "ios/generated",
201+
"android": "android/generated"
202+
},
203+
"android": {
204+
"javaPackageName": "com.<%- project.package %>"
205+
},
206+
"includesGeneratedCode": true
196207
<% } -%>
197208
}
198209
}

packages/create-react-native-library/templates/native-common/android/build.gradle

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ android {
114114
main {
115115
if (isNewArchitectureEnabled()) {
116116
java.srcDirs += [
117-
// This is needed to build Kotlin project with NewArch enabled
118-
"${project.buildDir}/generated/source/codegen/java"
117+
"generated/java",
118+
"generated/jni"
119119
]
120120
}
121121
}
@@ -127,8 +127,9 @@ android {
127127
if (isNewArchitectureEnabled()) {
128128
java.srcDirs += [
129129
"src/newarch",
130-
// This is needed to build Kotlin project with NewArch enabled
131-
"${project.buildDir}/generated/source/codegen/java"
130+
// Codegen specs
131+
"generated/java",
132+
"generated/jni"
132133
]
133134
} else {
134135
java.srcDirs += ["src/oldarch"]

packages/create-react-native-library/templates/native-common/{%- project.identifier %}.podspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ Pod::Spec.new do |s|
1818
s.source_files = "ios/**/*.{h,m,mm}", "cpp/**/*.{hpp,cpp,c,h}"
1919
<% } else if (project.swift) { -%>
2020
s.source_files = "ios/**/*.{h,m,mm,swift}"
21+
<% } else if (project.arch !== "legacy") { -%>
22+
s.source_files = "ios/**/*.{h,m,mm,cpp}"
2123
<% } else { -%>
2224
s.source_files = "ios/**/*.{h,m,mm}"
2325
<% } -%>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @type {import('@react-native-community/cli-types').UserDependencyConfig}
3+
*/
4+
module.exports = {
5+
dependency: {
6+
platforms: {
7+
android: {
8+
cmakeListsPath: 'generated/jni/CMakeLists.txt',
9+
},
10+
},
11+
},
12+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @type {import('@react-native-community/cli-types').UserDependencyConfig}
3+
*/
4+
module.exports = {
5+
dependency: {
6+
platforms: {
7+
android: {
8+
cmakeListsPath: 'generated/jni/CMakeLists.txt',
9+
},
10+
},
11+
},
12+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @type {import('@react-native-community/cli-types').UserDependencyConfig}
3+
*/
4+
module.exports = {
5+
dependency: {
6+
platforms: {
7+
android: {
8+
cmakeListsPath: 'generated/jni/CMakeLists.txt',
9+
},
10+
},
11+
},
12+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @type {import('@react-native-community/cli-types').UserDependencyConfig}
3+
*/
4+
module.exports = {
5+
dependency: {
6+
platforms: {
7+
android: {
8+
cmakeListsPath: 'generated/jni/CMakeLists.txt',
9+
},
10+
},
11+
},
12+
};

0 commit comments

Comments
 (0)