React Native Expo Android构建失败:未知属性hermesEnabled
解决React Native EAS构建中hermesEnabled属性未找到的问题
问题场景
执行命令 eas build -p android --profile preview 构建React Native项目时,反复出现以下错误:
FAILURE: Build failed with an exception. * Where: Build file '/home/expo/workingdir/build/android/app/build.gradle' line: 198 * What went wrong: A problem occurred evaluating project ':app'. > Could not get unknown property 'hermesEnabled' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
已经在gradle.properties中设置hermesEnabled=true,也尝试在build.gradle里添加enableHermes: true,但问题依旧。
现有配置文件内容
android/gradle.properties
# Gradle properties org.gradle.parallel=true org.gradle.caching=true org.gradle.jvmargs=-Duser.language=en -Duser.country=US -Dfile.encoding=UTF-8 # System properties systemProp.pts.enabled=true systemProp.log4j2.disableJmx=true systemProp.file.encoding = UTF-8 # Project properties kotlin.code.style=official android.nonTransitiveRClass=false spring-boot.version = 2.2.1.RELEASE hermesEnabled=true
android/app/build.gradle
apply plugin: "com.android.application" apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() static def versionToNumber(major, minor, patch) { return patch * 100 + minor * 10000 + major * 1000000 } def getRNVersion() { def version = providers.exec { workingDir(projectDir) commandLine("node", "-e", "console.log(require('react-native/package.json').version);") }.standardOutput.asText.get().trim() def coreVersion = version.split("-")[0] def (major, minor, patch) = coreVersion.tokenize('.').collect { it.toInteger() } return versionToNumber( major, minor, patch ) } def rnVersion = getRNVersion() /** * This is the configuration block to customize your React Native Android app. * By default you don't need to apply any configuration, just uncomment the lines you need. */ react { entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() // Use Expo CLI to bundle the app, this ensures the Metro config // works correctly with Expo projects. cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) bundleCommand = "export:embed" /* Folders */ // The root of your project, i.e. where "package.json" lives. Default is '..' // root = file("../") // The folder where the react-native NPM package is. Default is ../node_modules/react-native // reactNativeDir = file("../node_modules/react-native") // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen // codegenDir = file("../node_modules/@react-native/codegen") /* Variants */ // The list of variants to that are debuggable. For those we're going to // skip the bundling of the JS bundle and the assets. By default is just 'debug'. // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. // debuggableVariants = ["liteDebug", "prodDebug"] /* Bundling */ // A list containing the node command and its flags. Default is just 'node'. // nodeExecutableAndArgs = ["node"] // // The path to the CLI configuration file. Default is empty. // bundleConfig = file(../rn-cli.config.js) // // The name of the generated asset file containing your JS bundle // bundleAssetName = "MyApplication.android.bundle" // // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' // entryFile = file("../js/MyApplication.android.js") // // A list of extra flags to pass to the 'bundle' commands. // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle // extraPackagerArgs = [] /* Hermes Commands */ // The hermes compiler command to run. By default it is 'hermesc' // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" // // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" // hermesFlags = ["-O", "-output-source-map"] if (rnVersion >= versionToNumber(0, 75, 0)) { /* Autolinking */ autolinkLibrariesWithApp() } } /** * Set this to true to Run Proguard on Release builds to minify the Java bytecode. */ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() /** * The preferred build flavor of JavaScriptCore (JSC) * * For example, to use the international variant, you can use: * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` * * The international variant includes ICU i18n library and necessary data * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that * give correct results when using with locales other than en-US. Note that * this variant is about 6MiB larger per architecture than default. */ def jscFlavor = 'org.webkit:android-jsc:+' android { ndkVersion rootProject.ext.ndkVersion buildToolsVersion rootProject.ext.buildToolsVersion compileSdk rootProject.ext.compileSdkVersion namespace 'com.anonymous.rnlearnerandroid' defaultConfig { applicationId 'com.anonymous.rnlearnerandroid' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0.0" } signingConfigs { debug { storeFile file('debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } buildTypes { debug { signingConfig signingConfigs.debug } release { // Caution! In production, you need to generate your own keystore file. // see https://reactnative.dev/docs/signed-apk-android. signingConfig signingConfigs.debug shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true) } } packagingOptions { jniLibs { useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false) } } } // Apply static values from `gradle.properties` to the `android.packagingOptions` // Accepts values in comma delimited lists, example: // android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini ["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> // Split option: 'foo,bar' -> ['foo', 'bar'] def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); // Trim all elements in place. for (i in 0..<options.size()) options[i] = options[i].trim(); // `[] - ""` is essentially `[""].filter(Boolean)` removing all empty strings. options -= "" if (options.length > 0) { println "android.packagingOptions.$prop += $options ($options.length)" // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' options.each { android.packagingOptions[prop] += it } } } dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; if (isGifEnabled) { // For animated gif support implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}") } if (isWebpEnabled) { // For webp support implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}") if (isWebpAnimatedEnabled) { // Animated webp support implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}") } } if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { implementation jscFlavor } } if (rnVersion < versionToNumber(0, 75, 0)) { apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle"); applyNativeModulesAppBuildGradle(project) }
解决方案
问题原因
在dependencies代码块中直接使用hermesEnabled时,Gradle的当前作用域是DefaultDependencyHandler,无法直接读取gradle.properties中定义的项目属性,导致找不到该变量。
修复步骤
选以下任意一种方法修改android/app/build.gradle即可:
方法1:提前获取属性值
在dependencies块之前,添加代码获取gradle.properties中的hermesEnabled值:
// 在dependencies块上方添加 def hermesEnabled = (findProperty('hermesEnabled') ?: false).toBoolean() dependencies { // ... 其他依赖代码 ... if (hermesEnabled) { implementation("com.facebook.react:hermes-android") } else { implementation jscFlavor } }
方法2:明确指定项目作用域
直接在dependencies块的判断中使用project.hermesEnabled,明确从项目根作用域读取属性:
dependencies { // ... 其他依赖代码 ... if (project.hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") } else { implementation jscFlavor } }
额外检查
确认gradle.properties中的hermesEnabled=true没有拼写错误,且位于文件的项目属性区域(不要放到系统属性或Gradle属性块里)。
内容的提问来源于stack exchange,提问作者Flynn
相关产品推荐
相关产品推荐

