-
-
Save balvinderg/d6fafbb75c06e4388cf51ac3d1bf41a1 to your computer and use it in GitHub Desktop.
Latest native_modules.gradle
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import groovy.json.JsonSlurper | |
import org.gradle.initialization.DefaultSettings | |
import org.apache.tools.ant.taskdefs.condition.Os | |
def generatedFileName = "PackageList.java" | |
def generatedFilePackage = "com.facebook.react" | |
def generatedFileContentsTemplate = """ | |
package $generatedFilePackage; | |
import android.app.Application; | |
import android.content.Context; | |
import android.content.res.Resources; | |
import com.facebook.react.ReactPackage; | |
import com.facebook.react.shell.MainPackageConfig; | |
import com.facebook.react.shell.MainReactPackage; | |
import java.util.Arrays; | |
import java.util.ArrayList; | |
{{ packageImports }} | |
public class PackageList { | |
private Application application; | |
private ReactNativeHost reactNativeHost; | |
private MainPackageConfig mConfig; | |
public PackageList(ReactNativeHost reactNativeHost) { | |
this(reactNativeHost, null); | |
} | |
public PackageList(Application application) { | |
this(application, null); | |
} | |
public PackageList(ReactNativeHost reactNativeHost, MainPackageConfig config) { | |
this.reactNativeHost = reactNativeHost; | |
mConfig = config; | |
} | |
public PackageList(Application application, MainPackageConfig config) { | |
this.reactNativeHost = null; | |
this.application = application; | |
mConfig = config; | |
} | |
private ReactNativeHost getReactNativeHost() { | |
return this.reactNativeHost; | |
} | |
private Resources getResources() { | |
return this.getApplication().getResources(); | |
} | |
private Application getApplication() { | |
if (this.reactNativeHost == null) return this.application; | |
return this.reactNativeHost.getApplication(); | |
} | |
private Context getApplicationContext() { | |
return this.getApplication().getApplicationContext(); | |
} | |
public ArrayList<ReactPackage> getPackages() { | |
return new ArrayList<>(Arrays.<ReactPackage>asList( | |
new MainReactPackage(mConfig){{ packageClassInstances }} | |
)); | |
} | |
} | |
""" | |
def cmakeTemplate = """# This code was generated by [React Native CLI](https://www.npmjs.com/package/@react-native-community/cli) | |
cmake_minimum_required(VERSION 3.13) | |
set(CMAKE_VERBOSE_MAKEFILE on) | |
{{ libraryIncludes }} | |
set(AUTOLINKED_LIBRARIES | |
{{ libraryModules }} | |
) | |
""" | |
def rncliCppTemplate = """/** | |
* This code was generated by [React Native CLI](https://www.npmjs.com/package/@react-native-community/cli). | |
* | |
* Do not edit this file as changes may cause incorrect behavior and will be lost | |
* once the code is regenerated. | |
* | |
*/ | |
#include "rncli.h" | |
{{ rncliCppIncludes }} | |
namespace facebook { | |
namespace react { | |
{{ rncliReactLegacyComponentNames }} | |
std::shared_ptr<TurboModule> rncli_ModuleProvider(const std::string moduleName, const JavaTurboModule::InitParams ¶ms) { | |
{{ rncliCppModuleProviders }} | |
return nullptr; | |
} | |
void rncli_registerProviders(std::shared_ptr<ComponentDescriptorProviderRegistry const> providerRegistry) { | |
{{ rncliCppComponentDescriptors }} | |
{{ rncliReactLegacyComponentDescriptors }} | |
return; | |
} | |
} // namespace react | |
} // namespace facebook | |
""" | |
def rncliHTemplate = """/** | |
* This code was generated by [React Native CLI](https://www.npmjs.com/package/@react-native-community/cli). | |
* | |
* Do not edit this file as changes may cause incorrect behavior and will be lost | |
* once the code is regenerated. | |
* | |
*/ | |
#pragma once | |
#include <ReactCommon/JavaTurboModule.h> | |
#include <ReactCommon/TurboModule.h> | |
#include <jsi/jsi.h> | |
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h> | |
namespace facebook { | |
namespace react { | |
std::shared_ptr<TurboModule> rncli_ModuleProvider(const std::string moduleName, const JavaTurboModule::InitParams ¶ms); | |
void rncli_registerProviders(std::shared_ptr<ComponentDescriptorProviderRegistry const> providerRegistry); | |
} // namespace react | |
} // namespace facebook | |
""" | |
class ReactNativeModules { | |
private Logger logger | |
private ProviderFactory providers | |
private String packageName | |
private File root | |
private ArrayList<HashMap<String, String>> reactNativeModules | |
private HashMap<String, ArrayList> reactNativeModulesBuildVariants | |
private String reactNativeVersion | |
private static String LOG_PREFIX = ":ReactNative:" | |
ReactNativeModules(Logger logger, ProviderFactory providers, File root) { | |
this.logger = logger | |
this.providers = providers | |
this.root = root | |
def (nativeModules, reactNativeModulesBuildVariants, androidProject, reactNativeVersion) = this.getReactNativeConfig() | |
this.reactNativeModules = nativeModules | |
this.reactNativeModulesBuildVariants = reactNativeModulesBuildVariants | |
this.packageName = androidProject["packageName"] | |
this.reactNativeVersion = reactNativeVersion | |
} | |
/** | |
* Include the react native modules android projects and specify their project directory | |
*/ | |
void addReactNativeModuleProjects(DefaultSettings defaultSettings) { | |
reactNativeModules.forEach { reactNativeModule -> | |
String nameCleansed = reactNativeModule["nameCleansed"] | |
String androidSourceDir = reactNativeModule["androidSourceDir"] | |
defaultSettings.include(":${nameCleansed}") | |
defaultSettings.project(":${nameCleansed}").projectDir = new File("${androidSourceDir}") | |
} | |
} | |
/** | |
* Adds the react native modules as dependencies to the users `app` project | |
*/ | |
void addReactNativeModuleDependencies(Project appProject) { | |
reactNativeModules.forEach { reactNativeModule -> | |
def nameCleansed = reactNativeModule["nameCleansed"] | |
def dependencyConfiguration = reactNativeModule["dependencyConfiguration"] | |
appProject.dependencies { | |
if (reactNativeModulesBuildVariants.containsKey(nameCleansed)) { | |
reactNativeModulesBuildVariants | |
.get(nameCleansed) | |
.forEach { buildVariant -> | |
if(dependencyConfiguration != null) { | |
"${buildVariant}${dependencyConfiguration}" | |
} else { | |
"${buildVariant}Implementation" project(path: ":${nameCleansed}") | |
} | |
} | |
} else { | |
if(dependencyConfiguration != null) { | |
"${dependencyConfiguration}" | |
} else { | |
implementation project(path: ":${nameCleansed}") | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Code-gen a java file with all the detected ReactNativePackage instances automatically added | |
* | |
* @param outputDir | |
* @param generatedFileName | |
* @param generatedFileContentsTemplate | |
*/ | |
void generatePackagesFile(File outputDir, String generatedFileName, String generatedFileContentsTemplate) { | |
ArrayList<HashMap<String, String>> packages = this.reactNativeModules | |
String packageName = this.packageName | |
String packageImports = "" | |
String packageClassInstances = "" | |
if (packages.size() > 0) { | |
def interpolateDynamicValues = { | |
it | |
// Before adding the package replacement mechanism, | |
// BuildConfig and R classes were imported automatically | |
// into the scope of the file. We want to replace all | |
// non-FQDN references to those classes with the package name | |
// of the MainApplication. | |
// | |
// We want to match "R" or "BuildConfig": | |
// - new Package(R.string…), | |
// - Module.configure(BuildConfig); | |
// ^ hence including (BuildConfig|R) | |
// but we don't want to match "R": | |
// - new Package(getResources…), | |
// - new PackageR…, | |
// - new Royal…, | |
// ^ hence excluding \w before and after matches | |
// and "BuildConfig" that has FQDN reference: | |
// - Module.configure(com.acme.BuildConfig); | |
// ^ hence excluding . before the match. | |
.replaceAll(~/([^.\w])(BuildConfig|R)([^\w])/, { | |
wholeString, prefix, className, suffix -> | |
"${prefix}${packageName}.${className}${suffix}" | |
}) | |
} | |
packageImports = packages.collect { | |
"// ${it.name}\n${interpolateDynamicValues(it.packageImportPath)}" | |
}.join('\n') | |
packageClassInstances = ",\n " + packages.collect { | |
interpolateDynamicValues(it.packageInstance) | |
}.join(",\n ") | |
} | |
String generatedFileContents = generatedFileContentsTemplate | |
.replace("{{ packageImports }}", packageImports) | |
.replace("{{ packageClassInstances }}", packageClassInstances) | |
outputDir.mkdirs() | |
final FileTreeBuilder treeBuilder = new FileTreeBuilder(outputDir) | |
treeBuilder.file(generatedFileName).newWriter().withWriter { w -> | |
w << generatedFileContents | |
} | |
} | |
void generateCmakeFile(File outputDir, String generatedFileName, String generatedFileContentsTemplate) { | |
ArrayList<HashMap<String, String>> packages = this.reactNativeModules | |
String packageName = this.packageName | |
String codegenLibPrefix = "react_codegen_" | |
String libraryIncludes = "" | |
String libraryModules = "" | |
if (packages.size() > 0) { | |
libraryIncludes = packages.collect { | |
if (it.libraryName != null && it.cmakeListsPath != null) { | |
// If user provided a custom cmakeListsPath, let's honor it. | |
String nativeFolderPath = it.cmakeListsPath.replace("CMakeLists.txt", "") | |
"add_subdirectory($nativeFolderPath ${it.libraryName}_autolinked_build)" | |
} else { | |
null | |
} | |
}.minus(null).join('\n') | |
libraryModules = packages.collect { | |
it.libraryName ? "${codegenLibPrefix}${it.libraryName}" : null | |
}.minus(null).join('\n ') | |
} | |
String generatedFileContents = generatedFileContentsTemplate | |
.replace("{{ libraryIncludes }}", libraryIncludes) | |
.replace("{{ libraryModules }}", libraryModules) | |
outputDir.mkdirs() | |
final FileTreeBuilder treeBuilder = new FileTreeBuilder(outputDir) | |
treeBuilder.file(generatedFileName).newWriter().withWriter { w -> | |
w << generatedFileContents | |
} | |
} | |
void generateRncliCpp(File outputDir, String generatedFileName, String generatedFileContentsTemplate) { | |
ArrayList<HashMap<String, String>> packages = this.reactNativeModules | |
String rncliCppIncludes = "" | |
String rncliCppModuleProviders = "" | |
String rncliCppComponentDescriptors = "" | |
String rncliReactLegacyComponentDescriptors = "" | |
String rncliReactLegacyComponentNames = "" | |
String codegenComponentDescriptorsHeaderFile = "ComponentDescriptors.h" | |
String codegenReactComponentsDir = "react/renderer/components" | |
if (packages.size() > 0) { | |
rncliCppIncludes = packages.collect { | |
if (!it.libraryName) { | |
return null | |
} | |
def result = "#include <${it.libraryName}.h>" | |
if (it.componentDescriptors && it.componentDescriptors.size() > 0) { | |
result += "\n#include <${codegenReactComponentsDir}/${it.libraryName}/${codegenComponentDescriptorsHeaderFile}>" | |
} | |
result | |
}.minus(null).join('\n') | |
rncliCppModuleProviders = packages.collect { | |
it.libraryName ? """ auto module_${it.libraryName} = ${it.libraryName}_ModuleProvider(moduleName, params); | |
if (module_${it.libraryName} != nullptr) { | |
return module_${it.libraryName}; | |
}""" : null | |
}.minus(null).join("\n") | |
rncliCppComponentDescriptors = packages.collect { | |
def result = "" | |
if (it.componentDescriptors && it.componentDescriptors.size() > 0) { | |
result += it.componentDescriptors.collect { | |
" providerRegistry->add(concreteComponentDescriptorProvider<${it}>());" | |
}.join('\n') | |
} | |
result | |
}.join("\n") | |
} | |
String generatedFileContents = generatedFileContentsTemplate | |
.replace("{{ rncliCppIncludes }}", rncliCppIncludes) | |
.replace("{{ rncliCppModuleProviders }}", rncliCppModuleProviders) | |
.replace("{{ rncliCppComponentDescriptors }}", rncliCppComponentDescriptors) | |
.replace("{{ rncliReactLegacyComponentDescriptors }}", rncliReactLegacyComponentDescriptors) | |
.replace("{{ rncliReactLegacyComponentNames }}", rncliReactLegacyComponentNames) | |
outputDir.mkdirs() | |
final FileTreeBuilder treeBuilder = new FileTreeBuilder(outputDir) | |
treeBuilder.file(generatedFileName).newWriter().withWriter { w -> | |
w << generatedFileContents | |
} | |
} | |
void generateRncliH(File outputDir, String generatedFileName, String generatedFileContentsTemplate) { | |
String generatedFileContents = generatedFileContentsTemplate | |
outputDir.mkdirs() | |
final FileTreeBuilder treeBuilder = new FileTreeBuilder(outputDir) | |
treeBuilder.file(generatedFileName).newWriter().withWriter { w -> | |
w << generatedFileContents | |
} | |
} | |
/** | |
* Runs a specified command using providers.exec in a specified directory. | |
* Throws when the command result is empty. | |
*/ | |
String getCommandOutput(String[] command, File directory) { | |
try { | |
def execOutput = providers.exec { | |
commandLine(command) | |
workingDir(directory) | |
} | |
def output = execOutput.standardOutput.asText.get().trim() | |
if (!output) { | |
this.logger.error("${LOG_PREFIX}Unexpected empty result of running '${command}' command.") | |
def error = execOutput.standardError.asText.get().trim() | |
throw new Exception(error) | |
} | |
return output | |
} catch (Exception exception) { | |
this.logger.error("${LOG_PREFIX}Running '${command}' command failed.") | |
throw exception | |
} | |
} | |
/** | |
* Runs a process to call the React Native CLI Config command and parses the output | |
*/ | |
ArrayList<HashMap<String, String>> getReactNativeConfig() { | |
if (this.reactNativeModules != null) return this.reactNativeModules | |
ArrayList<HashMap<String, String>> reactNativeModules = new ArrayList<HashMap<String, String>>() | |
HashMap<String, ArrayList> reactNativeModulesBuildVariants = new HashMap<String, ArrayList>() | |
def packageDotJsonPath = root.absolutePath + "/package.json" | |
def packageLockDotJsonPath = root.absolutePath + "/package-lock.json" | |
def cachedPackageDotJsonPath = root.absolutePath + "/cached_package.json" | |
def cachedPackageLockDotJsonPath = root.absolutePath + "/cached_package-lock.json" | |
def cachedNodeModuleOutputPath = root.absolutePath + "/node_command_output.json" | |
def cachedNodeModuleOutputFile = new File(cachedNodeModuleOutputPath) | |
def cachedPackageFile = new File(cachedPackageDotJsonPath); | |
def cachedPackageLockFile = new File(cachedPackageLockDotJsonPath); | |
def packageFileContent = new File(packageDotJsonPath).text | |
def packageLockFileContent = new File(packageLockDotJsonPath).text | |
def shouldUseCache = true | |
if (!cachedPackageFile.exists() || !cachedNodeModuleOutputFile.exists() || !cachedPackageLockFile.exists() || packageFileContent != cachedPackageFile.text || packageLockFileContent != cachedPackageLockFile.text) { | |
cachedPackageFile.delete() | |
cachedPackageFile.createNewFile() | |
cachedPackageFile.write(new File(packageDotJsonPath).text) | |
cachedPackageLockFile.delete() | |
cachedPackageLockFile.createNewFile() | |
cachedPackageLockFile.write(new File(packageLockDotJsonPath).text) | |
shouldUseCache = false | |
} | |
def output = "" | |
if (shouldUseCache) { | |
output = cachedNodeModuleOutputFile.text.replaceAll("to_be_replaced_with", root.absolutePath) | |
} else { | |
/** | |
* Resolve the CLI location from Gradle file | |
* | |
* @todo: Sometimes Gradle can be called outside of the JavaScript hierarchy (-p flag) which | |
* will fail to resolve the script and the dependencies. We should resolve this soon. | |
* | |
* @todo: `fastlane` has been reported to not work too. | |
*/ | |
def cliResolveScript = "try {console.log(require('@react-native-community/cli').bin);} catch (e) {console.log(require('react-native/cli').bin);}" | |
String[] nodeCommand = ["node", "-e", cliResolveScript] | |
def cliPath = this.getCommandOutput(nodeCommand, this.root) | |
String[] configCommand = ["node", cliPath, "config"] | |
output = this.getCommandOutput(configCommand, this.root) | |
cachedNodeModuleOutputFile.delete() | |
cachedNodeModuleOutputFile.createNewFile() | |
cachedNodeModuleOutputFile.write(output.replace(root.absolutePath, "to_be_replaced_with")) | |
} | |
def json | |
try { | |
json = new JsonSlurper().parseText(output) | |
} catch (Exception exception) { | |
throw new Exception("Calling node command finished with an exception. Error message: ${exception.toString()}. Output: ${output}"); | |
} | |
def dependencies = json["dependencies"] | |
def project = json["project"]["android"] | |
def reactNativeVersion = json["version"] | |
if (project == null) { | |
throw new Exception("React Native CLI failed to determine Android project configuration. This is likely due to misconfiguration. Config output:\n${json.toMapString()}") | |
} | |
def engine = new groovy.text.SimpleTemplateEngine() | |
dependencies.each { name, value -> | |
def platformsConfig = value["platforms"]; | |
def androidConfig = platformsConfig["android"] | |
if (androidConfig != null && androidConfig["sourceDir"] != null) { | |
this.logger.info("${LOG_PREFIX}Automatically adding native module '${name}'") | |
HashMap reactNativeModuleConfig = new HashMap<String, String>() | |
def nameCleansed = name.replaceAll('[~*!\'()]+', '_').replaceAll('^@([\\w-.]+)/', '$1_') | |
reactNativeModuleConfig.put("name", name) | |
reactNativeModuleConfig.put("nameCleansed", nameCleansed) | |
reactNativeModuleConfig.put("androidSourceDir", androidConfig["sourceDir"]) | |
reactNativeModuleConfig.put("packageInstance", androidConfig["packageInstance"]) | |
reactNativeModuleConfig.put("packageImportPath", androidConfig["packageImportPath"]) | |
reactNativeModuleConfig.put("libraryName", androidConfig["libraryName"]) | |
reactNativeModuleConfig.put("componentDescriptors", androidConfig["componentDescriptors"]) | |
reactNativeModuleConfig.put("cmakeListsPath", androidConfig["cmakeListsPath"]) | |
if (androidConfig["buildTypes"] && !androidConfig["buildTypes"].isEmpty()) { | |
reactNativeModulesBuildVariants.put(nameCleansed, androidConfig["buildTypes"]) | |
} | |
if(androidConfig.containsKey("dependencyConfiguration")) { | |
reactNativeModuleConfig.put("dependencyConfiguration", androidConfig["dependencyConfiguration"]) | |
} else if (project.containsKey("dependencyConfiguration")) { | |
def bindings = ["dependencyName": nameCleansed] | |
def template = engine.createTemplate(project["dependencyConfiguration"]).make(bindings) | |
reactNativeModuleConfig.put("dependencyConfiguration", template.toString()) | |
} | |
this.logger.trace("${LOG_PREFIX}'${name}': ${reactNativeModuleConfig.toMapString()}") | |
reactNativeModules.add(reactNativeModuleConfig) | |
} else { | |
this.logger.info("${LOG_PREFIX}Skipping native module '${name}'") | |
} | |
} | |
return [reactNativeModules, reactNativeModulesBuildVariants, json["project"]["android"], reactNativeVersion]; | |
} | |
} | |
/* | |
* Sometimes Gradle can be called outside of JavaScript hierarchy. Detect the directory | |
* where build files of an active project are located. | |
*/ | |
def projectRoot = rootProject.projectDir | |
def autoModules = new ReactNativeModules(logger, providers, projectRoot) | |
def reactNativeVersionRequireNewArchEnabled(autoModules) { | |
def rnVersion = autoModules.reactNativeVersion | |
def regexPattern = /^(\d+)\.(\d+)\.(\d+)(?:-(\w+(?:[-.]\d+)?))?$/ | |
if (rnVersion =~ regexPattern) { | |
def result = (rnVersion =~ regexPattern).findAll().first() | |
def major = result[1].toInteger() | |
if (major > 0 && major < 1000) { | |
return true | |
} | |
} | |
return false | |
} | |
/** ----------------------- | |
* Exported Extensions | |
* ------------------------ */ | |
ext.applyNativeModulesSettingsGradle = { DefaultSettings defaultSettings -> | |
autoModules.addReactNativeModuleProjects(defaultSettings) | |
} | |
ext.applyNativeModulesAppBuildGradle = { Project project -> | |
autoModules.addReactNativeModuleDependencies(project) | |
def generatedSrcDir = new File(buildDir, "generated/rncli/src/main/java") | |
def generatedCodeDir = new File(generatedSrcDir, generatedFilePackage.replace('.', '/')) | |
def generatedJniDir = new File(buildDir, "generated/rncli/src/main/jni") | |
task generatePackageList { | |
doLast { | |
autoModules.generatePackagesFile(generatedCodeDir, generatedFileName, generatedFileContentsTemplate) | |
} | |
} | |
task generateNewArchitectureFiles { | |
doLast { | |
autoModules.generateCmakeFile(generatedJniDir, "Android-rncli.cmake", cmakeTemplate) | |
autoModules.generateRncliCpp(generatedJniDir, "rncli.cpp", rncliCppTemplate) | |
autoModules.generateRncliH(generatedJniDir, "rncli.h", rncliHTemplate) | |
} | |
} | |
preBuild.dependsOn generatePackageList | |
def isNewArchEnabled = (project.hasProperty("newArchEnabled") && project.newArchEnabled == "true") || | |
reactNativeVersionRequireNewArchEnabled(autoModules) | |
if (isNewArchEnabled) { | |
preBuild.dependsOn generateNewArchitectureFiles | |
} | |
android { | |
sourceSets { | |
main { | |
java { | |
srcDirs += generatedSrcDir | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment