-
-
Save balvinderg/d7b93e329c84ae81eab7936f9c9b8f54 to your computer and use it in GitHub Desktop.
Modified native_modules.gradle (7.0.1)
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 }} | |
)); | |
} | |
} | |
""" | |
class ReactNativeModules { | |
private Logger logger | |
private String packageName | |
private File root | |
private ArrayList<HashMap<String, String>> reactNativeModules | |
private HashMap<String, ArrayList> reactNativeModulesBuildVariants | |
private static String LOG_PREFIX = ":ReactNative:" | |
ReactNativeModules(Logger logger, File root) { | |
this.logger = logger | |
this.root = root | |
def (nativeModules, reactNativeModulesBuildVariants, packageName) = this.getReactNativeConfig() | |
this.reactNativeModules = nativeModules | |
this.reactNativeModulesBuildVariants = reactNativeModulesBuildVariants | |
this.packageName = packageName | |
} | |
/** | |
* 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 | |
} | |
} | |
/** | |
* Runs a specified command using Runtime exec() in a specified directory. | |
* Throws when the command result is empty. | |
*/ | |
String getCommandOutput(String[] command, File directory) { | |
try { | |
def output = "" | |
def cmdProcess = Runtime.getRuntime().exec(command, null, directory) | |
def bufferedReader = new BufferedReader(new InputStreamReader(cmdProcess.getInputStream())) | |
def buff = "" | |
def readBuffer = new StringBuffer() | |
while ((buff = bufferedReader.readLine()) != null) { | |
readBuffer.append(buff) | |
} | |
output = readBuffer.toString() | |
if (!output) { | |
this.logger.error("${LOG_PREFIX}Unexpected empty result of running '${command}' command.") | |
def bufferedErrorReader = new BufferedReader(new InputStreamReader(cmdProcess.getErrorStream())) | |
def errBuff = "" | |
def readErrorBuffer = new StringBuffer() | |
while ((errBuff = bufferedErrorReader.readLine()) != null) { | |
readErrorBuffer.append(errBuff) | |
} | |
throw new Exception(readErrorBuffer.toString()) | |
} | |
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: ${ouput}"); | |
} | |
def dependencies = json["dependencies"] | |
def project = json["project"]["android"] | |
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"]) | |
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"]["packageName"]]; | |
} | |
} | |
/* | |
* 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, projectRoot) | |
/** ----------------------- | |
* Exported Extensions | |
* ------------------------ */ | |
ext.applyNativeModulesSettingsGradle = { DefaultSettings defaultSettings, String root = null -> | |
if (root != null) { | |
logger.warn("${ReactNativeModules.LOG_PREFIX}Passing custom root is deprecated. CLI detects root automatically now."); | |
logger.warn("${ReactNativeModules.LOG_PREFIX}Please remove second argument to `applyNativeModulesSettingsGradle`."); | |
} | |
autoModules.addReactNativeModuleProjects(defaultSettings) | |
} | |
ext.applyNativeModulesAppBuildGradle = { Project project, String root = null -> | |
if (root != null) { | |
logger.warn("${ReactNativeModules.LOG_PREFIX}Passing custom root is deprecated. CLI detects root automatically now"); | |
logger.warn("${ReactNativeModules.LOG_PREFIX}Please remove second argument to `applyNativeModulesAppBuildGradle`."); | |
} | |
autoModules.addReactNativeModuleDependencies(project) | |
def generatedSrcDir = new File(buildDir, "generated/rncli/src/main/java") | |
def generatedCodeDir = new File(generatedSrcDir, generatedFilePackage.replace('.', '/')) | |
task generatePackageList { | |
doLast { | |
autoModules.generatePackagesFile(generatedCodeDir, generatedFileName, generatedFileContentsTemplate) | |
} | |
} | |
preBuild.dependsOn generatePackageList | |
android { | |
sourceSets { | |
main { | |
java { | |
srcDirs += generatedSrcDir | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment