Skip to content

Commit 00781eb

Browse files
authored
feat: autolinking for the new architecture on Android (#1603)
* feat: generate new arch files with hardcoded values * feat: add libraryName to android dependency config * feat: add componentNames and componentDescriptors to android dependency config * feat: add androidMkPath to android dependency config * fix: remove duplicate component names * chore: run generateNewArchitectureFiles when new architecture is turned on * feat: use only componentDescriptors, take interfacesOnly into account * docs: document new architecture autolinking * chore: add comments * fix: TurboModule support and indentation * chore: prettify mk output; remove jni sourceset; extract variables * feat: make new config nullable * chore: update docs on how to disable new arch autolinking
1 parent 810b6a0 commit 00781eb

File tree

12 files changed

+341
-1
lines changed

12 files changed

+341
-1
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ And then:
3939

4040
```sh
4141
cd /my/new/react-native/project/
42-
yarn link "@react-native-community/cli-platform-ios" "@react-native-community/cli-platform-android" "@react-native-community/cli" "@react-native-community/cli-server-api" "@react-native-community/cli-types" "@react-native-community/cli-tools" "@react-native-community/cli-debugger-ui" "@react-native-community/cli-hermes" "@react-native-community/cli-plugin-metro"
42+
yarn link "@react-native-community/cli-platform-ios" "@react-native-community/cli-platform-android" "@react-native-community/cli" "@react-native-community/cli-server-api" "@react-native-community/cli-types" "@react-native-community/cli-tools" "@react-native-community/cli-debugger-ui" "@react-native-community/cli-hermes" "@react-native-community/cli-plugin-metro" "@react-native-community/cli-clean"
4343
```
4444

4545
Once you're done with testing and you'd like to get back to regular setup, run `yarn unlink` instead of `yarn link` from above command. Then `yarn install --force`.

docs/autolinking.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ yarn react-native run-android
1616
That's it. No more editing build config files to use native code.
1717

1818
Also, removing a library is similar to adding a library:
19+
1920
```sh
2021
# uninstall
2122
yarn remove react-native-webview
@@ -48,6 +49,10 @@ The [native_modules.gradle](https://github.com/react-native-community/cli/blob/m
4849
1. At build time, before the build script is run:
4950
1. A first Gradle plugin (in `settings.gradle`) runs `applyNativeModulesSettingsGradle` method. It uses the package metadata from `react-native config` to add Android projects.
5051
1. A second Gradle plugin (in `app/build.gradle`) runs `applyNativeModulesAppBuildGradle` method. It creates a list of React Native packages to include in the generated `/android/build/generated/rn/src/main/java/com/facebook/react/PackageList.java` file.
52+
1. When the new architecture is turned on, the `generateNewArchitectureFiles` task is fired, generating `/android/build/generated/rn/src/main/jni` directory with the following files:
53+
- `Android-rncli.mk` – creates a list of codegen'd libs. Used by the project's `Android.mk`.
54+
- `rncli.cpp` – registers codegen'd Turbo Modules and Fabric component providers. Used by `MainApplicationModuleProvider.cpp` and `MainComponentsRegistry.cpp`.
55+
- `rncli.h` - a header file for `rncli.cpp`.
5156
1. At runtime, the list of React Native packages generated in step 1.2 is registered by `getPackages` method of `ReactNativeHost` in `MainApplication.java`.
5257
1. You can optionally pass in an instance of `MainPackageConfig` when initializing `PackageList` if you want to override the default configuration of `MainReactPackage`.
5358

@@ -97,6 +102,27 @@ module.exports = {
97102
};
98103
```
99104

105+
## How can I disable autolinking for new architecture (Fabric, TurboModules)?
106+
107+
It happens that packages come with their own linking setup for the new architecture. To disable autolinking in such cases (currently `react-native-screens`, `react-native-safe-area-context`, `react-native-reanimated`, `react-native-gesture-handler`), update your `react-native.config.js`'s `dependencies` entry to look like this:
108+
109+
```js
110+
// react-native.config.js
111+
module.exports = {
112+
dependencies: {
113+
'fabric-or-tm-library': {
114+
platforms: {
115+
android: {
116+
libraryName: null,
117+
componentDescriptors: null,
118+
androidMkPath: null,
119+
},
120+
},
121+
},
122+
},
123+
};
124+
```
125+
100126
## How can I autolink a local library?
101127

102128
We can leverage CLI configuration to make it "see" React Native libraries that are not part of our 3rd party dependencies. To do so, update your `react-native.config.js`'s `dependencies` entry to look like this:
@@ -124,6 +150,7 @@ correct location and update them accordingly:
124150
- path to `native_modules.gradle` in your `android/app/build.gradle`
125151

126152
Dependencies are only linked if they are listed in the package.json of the mobile workspace, where "react-native" dependency is defined. For example, with this file structure:
153+
127154
```
128155
/root
129156
/packages
@@ -135,4 +162,5 @@ Dependencies are only linked if they are listed in the package.json of the mobil
135162
package.json <-- Dependencies here are ignored when auto-linking
136163
package.json
137164
```
165+
138166
In this example, if you add a package with native code as a dependency of `components`, you need to also add it as a dependency of `mobile` for auto-linking to work.

docs/dependencies.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ type AndroidDependencyParams = {
5555
packageImportPath?: string;
5656
packageInstance?: string;
5757
buildTypes?: string[];
58+
libraryName?: string | null;
59+
componentDescriptors?: string[] | null;
60+
androidMkPath?: string | null;
5861
};
5962
```
6063

@@ -119,3 +122,21 @@ An array of build variants or flavors which will include the dependency. If the
119122

120123
A string that defines which method other than `implementation` do you want to use
121124
for autolinking inside `build.gradle` i.e: `'embed project(path: ":$dependencyName", configuration: "default")',` - `"dependencyName` will be replaced by the actual package's name. You can achieve the same result by directly defining this key per `dependency` _(without placeholder)_ and it will have higher priority than this option.
125+
126+
#### platforms.android.libraryName
127+
128+
> Note: Only applicable when new architecture is turned on.
129+
130+
A string indicating your custom library name. By default it's taken from the `libraryName` variable in your library's `build.gradle`.
131+
132+
#### platforms.android.componentDescriptors
133+
134+
> Note: Only applicable when new architecture is turned on.
135+
136+
An array of custom component descriptor strings. By default they're generated based on `codegenNativeComponent` calls.
137+
138+
#### platforms.android.androidMkPath
139+
140+
> Note: Only applicable when new architecture is turned on.
141+
142+
A relative path to a custom _Android.mk_ file not registered by codegen. Relative to `sourceDir`.

docs/platforms.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,5 +118,8 @@ type AndroidDependencyConfig = {
118118
packageInstance: string;
119119
dependencyConfiguration?: string;
120120
buildTypes: string[];
121+
libraryName?: string | null;
122+
componentDescriptors?: string[] | null;
123+
androidMkPath?: string | null;
121124
};
122125
```

packages/cli-config/src/schema.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ export const dependencyConfig = t
8383
packageInstance: t.string(),
8484
dependencyConfiguration: t.string(),
8585
buildTypes: t.array().items(t.string()).default([]),
86+
libraryName: t.string().allow(null),
87+
componentDescriptors: t.array().items(t.string()).allow(null),
88+
androidMkPath: t.string().allow(null),
8689
})
8790
.default({}),
8891
})
@@ -131,6 +134,9 @@ export const projectConfig = t
131134
packageInstance: t.string(),
132135
dependencyConfiguration: t.string(),
133136
buildTypes: t.array().items(t.string()).default([]),
137+
libraryName: t.string().allow(null),
138+
componentDescriptors: t.array().items(t.string()).allow(null),
139+
androidMkPath: t.string().allow(null),
134140
})
135141
.allow(null),
136142
}),

packages/cli-types/src/android.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export type AndroidDependencyConfig = {
1919
packageInstance: string;
2020
dependencyConfiguration?: string;
2121
buildTypes: string[];
22+
libraryName?: string | null;
23+
componentDescriptors?: string[] | null;
24+
androidMkPath?: string | null;
2225
};
2326

2427
export type AndroidDependencyParams = {
@@ -29,4 +32,7 @@ export type AndroidDependencyParams = {
2932
packageImportPath?: string;
3033
packageInstance?: string;
3134
buildTypes?: string[];
35+
libraryName?: string | null;
36+
componentDescriptors?: string[] | null;
37+
androidMkPath?: string | null;
3238
};

packages/platform-android/native_modules.gradle

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,67 @@ public class PackageList {
6868
}
6969
"""
7070

71+
def androidMkTemplate = """# This code was generated by [React Native CLI](https://www.npmjs.com/package/@react-native-community/cli)
72+
73+
{{ libraryIncludes }}
74+
75+
import-codegen-modules := \\
76+
{{ libraryModules }}
77+
"""
78+
79+
def rncliCppTemplate = """/**
80+
* This code was generated by [React Native CLI](https://www.npmjs.com/package/@react-native-community/cli).
81+
*
82+
* Do not edit this file as changes may cause incorrect behavior and will be lost
83+
* once the code is regenerated.
84+
*
85+
*/
86+
87+
#include "rncli.h"
88+
{{ rncliCppIncludes }}
89+
90+
namespace facebook {
91+
namespace react {
92+
93+
std::shared_ptr<TurboModule> rncli_ModuleProvider(const std::string moduleName, const JavaTurboModule::InitParams &params) {
94+
{{ rncliCppModuleProviders }}
95+
return nullptr;
96+
}
97+
98+
void rncli_registerProviders(std::shared_ptr<ComponentDescriptorProviderRegistry const> providerRegistry) {
99+
{{ rncliCppComponentDescriptors }}
100+
return;
101+
}
102+
103+
} // namespace react
104+
} // namespace facebook
105+
"""
106+
107+
def rncliHTemplate = """/**
108+
* This code was generated by [React Native CLI](https://www.npmjs.com/package/@react-native-community/cli).
109+
*
110+
* Do not edit this file as changes may cause incorrect behavior and will be lost
111+
* once the code is regenerated.
112+
*
113+
*/
114+
115+
#pragma once
116+
117+
#include <ReactCommon/JavaTurboModule.h>
118+
#include <ReactCommon/TurboModule.h>
119+
#include <jsi/jsi.h>
120+
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
121+
122+
namespace facebook {
123+
namespace react {
124+
125+
std::shared_ptr<TurboModule> rncli_ModuleProvider(const std::string moduleName, const JavaTurboModule::InitParams &params);
126+
void rncli_registerProviders(std::shared_ptr<ComponentDescriptorProviderRegistry const> providerRegistry);
127+
128+
} // namespace react
129+
} // namespace facebook
130+
"""
131+
71132
class ReactNativeModules {
72133
private Logger logger
73134
private String packageName
@@ -187,6 +248,91 @@ class ReactNativeModules {
187248
}
188249
}
189250

251+
void generateAndroidMkFile(File outputDir, String generatedFileName, String generatedFileContentsTemplate) {
252+
ArrayList<HashMap<String, String>> packages = this.reactNativeModules
253+
String packageName = this.packageName
254+
String codegenLibPrefix = "libreact_codegen_"
255+
String libraryIncludes = ""
256+
String libraryModules = ""
257+
258+
if (packages.size() > 0) {
259+
libraryIncludes = packages.collect {
260+
it.androidMkPath ? "include ${it.androidMkPath}" : null
261+
}.minus(null).join('\n')
262+
libraryModules = packages.collect {
263+
it.libraryName ? "${codegenLibPrefix}${it.libraryName}" : null
264+
}.minus(null).join(" \\\n ")
265+
}
266+
267+
String generatedFileContents = generatedFileContentsTemplate
268+
.replace("{{ libraryIncludes }}", libraryIncludes)
269+
.replace("{{ libraryModules }}", libraryModules)
270+
271+
outputDir.mkdirs()
272+
final FileTreeBuilder treeBuilder = new FileTreeBuilder(outputDir)
273+
treeBuilder.file(generatedFileName).newWriter().withWriter { w ->
274+
w << generatedFileContents
275+
}
276+
}
277+
278+
void generateRncliCpp(File outputDir, String generatedFileName, String generatedFileContentsTemplate) {
279+
ArrayList<HashMap<String, String>> packages = this.reactNativeModules
280+
String rncliCppIncludes = ""
281+
String rncliCppModuleProviders = ""
282+
String rncliCppComponentDescriptors = ""
283+
String codegenComponentDescriptorsHeaderFile = "ComponentDescriptors.h"
284+
String codegenReactComponentsDir = "react/renderer/components"
285+
286+
if (packages.size() > 0) {
287+
rncliCppIncludes = packages.collect {
288+
if (!it.libraryName) {
289+
return null
290+
}
291+
def result = "#include <${it.libraryName}.h>"
292+
if (it.componentDescriptors && it.componentDescriptors.size() > 0) {
293+
result += "\n#include <${codegenReactComponentsDir}/${it.libraryName}/${codegenComponentDescriptorsHeaderFile}>"
294+
}
295+
result
296+
}.minus(null).join('\n')
297+
rncliCppModuleProviders = packages.collect {
298+
it.libraryName ? """ auto module_${it.libraryName} = ${it.libraryName}_ModuleProvider(moduleName, params);
299+
if (module_${it.libraryName} != nullptr) {
300+
return module_${it.libraryName};
301+
}""" : null
302+
}.minus(null).join("\n")
303+
rncliCppComponentDescriptors = packages.collect {
304+
def result = ""
305+
if (it.componentDescriptors && it.componentDescriptors.size() > 0) {
306+
result += it.componentDescriptors.collect {
307+
" providerRegistry->add(concreteComponentDescriptorProvider<${it}>());"
308+
}.join('\n')
309+
}
310+
result
311+
}.join("\n")
312+
}
313+
314+
String generatedFileContents = generatedFileContentsTemplate
315+
.replace("{{ rncliCppIncludes }}", rncliCppIncludes)
316+
.replace("{{ rncliCppModuleProviders }}", rncliCppModuleProviders)
317+
.replace("{{ rncliCppComponentDescriptors }}", rncliCppComponentDescriptors)
318+
319+
outputDir.mkdirs()
320+
final FileTreeBuilder treeBuilder = new FileTreeBuilder(outputDir)
321+
treeBuilder.file(generatedFileName).newWriter().withWriter { w ->
322+
w << generatedFileContents
323+
}
324+
}
325+
326+
void generateRncliH(File outputDir, String generatedFileName, String generatedFileContentsTemplate) {
327+
String generatedFileContents = generatedFileContentsTemplate
328+
329+
outputDir.mkdirs()
330+
final FileTreeBuilder treeBuilder = new FileTreeBuilder(outputDir)
331+
treeBuilder.file(generatedFileName).newWriter().withWriter { w ->
332+
w << generatedFileContents
333+
}
334+
}
335+
190336
/**
191337
* Runs a specified command using Runtime exec() in a specified directory.
192338
* Throws when the command result is empty.
@@ -272,6 +418,10 @@ class ReactNativeModules {
272418
reactNativeModuleConfig.put("androidSourceDir", androidConfig["sourceDir"])
273419
reactNativeModuleConfig.put("packageInstance", androidConfig["packageInstance"])
274420
reactNativeModuleConfig.put("packageImportPath", androidConfig["packageImportPath"])
421+
reactNativeModuleConfig.put("libraryName", androidConfig["libraryName"])
422+
reactNativeModuleConfig.put("componentDescriptors", androidConfig["componentDescriptors"])
423+
reactNativeModuleConfig.put("androidMkPath", androidConfig["androidMkPath"])
424+
275425
if (androidConfig["buildTypes"] && !androidConfig["buildTypes"].isEmpty()) {
276426
reactNativeModulesBuildVariants.put(nameCleansed, androidConfig["buildTypes"])
277427
}
@@ -326,15 +476,28 @@ ext.applyNativeModulesAppBuildGradle = { Project project, String root = null ->
326476

327477
def generatedSrcDir = new File(buildDir, "generated/rncli/src/main/java")
328478
def generatedCodeDir = new File(generatedSrcDir, generatedFilePackage.replace('.', '/'))
479+
def generatedJniDir = new File(buildDir, "generated/rncli/src/main/jni")
329480

330481
task generatePackageList {
331482
doLast {
332483
autoModules.generatePackagesFile(generatedCodeDir, generatedFileName, generatedFileContentsTemplate)
333484
}
334485
}
335486

487+
task generateNewArchitectureFiles {
488+
doLast {
489+
autoModules.generateAndroidMkFile(generatedJniDir, "Android-rncli.mk", androidMkTemplate)
490+
autoModules.generateRncliCpp(generatedJniDir, "rncli.cpp", rncliCppTemplate)
491+
autoModules.generateRncliH(generatedJniDir, "rncli.h", rncliHTemplate)
492+
}
493+
}
494+
336495
preBuild.dependsOn generatePackageList
337496

497+
if (isNewArchitectureEnabled()) {
498+
preBuild.dependsOn generateNewArchitectureFiles
499+
}
500+
338501
android {
339502
sourceSets {
340503
main {
@@ -345,3 +508,5 @@ ext.applyNativeModulesAppBuildGradle = { Project project, String root = null ->
345508
}
346509
}
347510
}
511+
512+

0 commit comments

Comments
 (0)