diff --git a/packages/cli/src/tools/config/resolveReactNativePath.ts b/packages/cli/src/tools/config/resolveReactNativePath.ts index db5150fff..014ee0804 100644 --- a/packages/cli/src/tools/config/resolveReactNativePath.ts +++ b/packages/cli/src/tools/config/resolveReactNativePath.ts @@ -11,16 +11,14 @@ export default function resolveReactNativePath(root: string) { return resolveNodeModuleDir(root, 'react-native'); } catch (_ignored) { throw new CLIError(` - Unable to find React Native files. Make sure "react-native" module is installed + Unable to find React Native files looking up from ${root}. Make sure "react-native" module is installed in your project dependencies. If you are using React Native from a non-standard location, consider setting: { - "react-native": { - "reactNativePath": "./path/to/react-native" - } + reactNativePath: "./path/to/react-native" } - in your \`package.json\`. + in your \`react-native.config.js\`. `); } } diff --git a/packages/platform-android/native_modules.gradle b/packages/platform-android/native_modules.gradle index 8475f095d..015d7b137 100644 --- a/packages/platform-android/native_modules.gradle +++ b/packages/platform-android/native_modules.gradle @@ -2,6 +2,7 @@ import groovy.json.JsonSlurper import org.gradle.initialization.DefaultSettings import org.apache.tools.ant.taskdefs.condition.Os +def jsAppDir = buildscript.sourceFile.toString().split("node_modules/@react-native-community/cli-platform-android")[0] def generatedFileName = "PackageList.java" def generatedFilePackage = "com.facebook.react" def generatedFileContentsTemplate = """ @@ -71,12 +72,14 @@ public class PackageList { class ReactNativeModules { private Logger logger private String packageName + private String jsAppDir private ArrayList> reactNativeModules private static String LOG_PREFIX = ":ReactNative:" - ReactNativeModules(Logger logger) { + ReactNativeModules(Logger logger, String jsAppDir) { this.logger = logger + this.jsAppDir = jsAppDir def (nativeModules, packageName) = this.getReactNativeConfig() this.reactNativeModules = nativeModules @@ -142,45 +145,66 @@ class ReactNativeModules { } /** - * Runs a process to call the React Native CLI Config command and parses the output - * - * @return ArrayList < HashMap < String , String > > + * Runs a specified command using Runtime exec() in a specified directory. + * Throws when the command result is empty. */ - ArrayList> getReactNativeConfig() { - if (this.reactNativeModules != null) return this.reactNativeModules - ArrayList> reactNativeModules = new ArrayList>() - - def cmdProcess - def npx = Os.isFamily(Os.FAMILY_WINDOWS) ? "npx.cmd" : "npx" - def command = "${npx} --quiet react-native config" - def reactNativeConfigOutput = "" - + String getCommandOutput(String command, File directory = null) { try { - cmdProcess = Runtime.getRuntime().exec(command) + 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) + 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 from '${directory}' directory.") + 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()) } - reactNativeConfigOutput = readBuffer.toString() + return output } catch (Exception exception) { - this.logger.warn("${LOG_PREFIX}${exception.message}") - this.logger.warn("${LOG_PREFIX}Automatic import of native modules failed.") + this.logger.error("${LOG_PREFIX}Running '${command}' command from '${directory}' directory failed.") + throw exception + } + } - def bufferedErrorReader = new BufferedReader(new InputStreamReader(cmdProcess.getErrorStream())) - def buff = "" - def readBuffer = new StringBuffer() - while ((buff = bufferedErrorReader.readLine()) != null){ - readBuffer.append(buff) - } - this.logger.warn("${LOG_PREFIX}${readBuffer.toString()}") + /** + * Runs a process to call the React Native CLI Config command and parses the output + */ + ArrayList> getReactNativeConfig() { + if (this.reactNativeModules != null) return this.reactNativeModules - return reactNativeModules + ArrayList> reactNativeModules = new ArrayList>() + def npx = Os.isFamily(Os.FAMILY_WINDOWS) ? "npx.cmd" : "npx" + def command = "${npx} --quiet --no-install react-native config" + /** + * Running npx from the directory of the JS app which holds this script in its node_modules. + * We do so, because Gradle may be ran with a different directory as CWD, that's outside of JS project + * (e.g. when running with -p flag), in which case npx wouldn't resolve correct `react-native` binary. + */ + def dir = new File(this.jsAppDir) + def reactNativeConfigOutput = this.getCommandOutput(command, dir) + def json + try { + json = new JsonSlurper().parseText(reactNativeConfigOutput) + } catch (Exception exception) { + this.logger.error("${LOG_PREFIX}Failed to parse React Native CLI configuration: ${exception.toString()}") + throw new Exception("Failed to parse React Native CLI configuration. Expected running '${command}' command from '${dir}' directory to output valid JSON, but it didn't. This may be caused by npx resolving to a legacy global react-native binary. Please make sure to uninstall any global 'react-native' binaries: 'npm uninstall -g react-native react-native-cli' and try again") } - - def json = new JsonSlurper().parseText(reactNativeConfigOutput) 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()}") + } dependencies.each { name, value -> def platformsConfig = value["platforms"]; @@ -211,7 +235,7 @@ class ReactNativeModules { * Exported Extensions * ------------------------ */ -def autoModules = new ReactNativeModules(logger) +def autoModules = new ReactNativeModules(logger, jsAppDir) ext.applyNativeModulesSettingsGradle = { DefaultSettings defaultSettings, String root = null -> if (root != null) {