From b67dc069832ac776e083c8bb292b205041244bf5 Mon Sep 17 00:00:00 2001 From: tido64 <4123478+tido64@users.noreply.github.com> Date: Thu, 29 Oct 2026 09:42:15 +0000 Subject: [PATCH] fix(android): copy Gradle wrapper from the React Native template `init` copied the Gradle wrapper from the bundled example, which pins Gradle 9.3.1. React Native 0.88 requires 9.4.1, so building directly with Gradle failed. Copy the wrapper from the template matching the target React Native version instead, falling back to the example's wrapper when no template is available. --- packages/app/android/template.config.mjs | 24 ++++++++++++------- .../configure/gatherConfig.test.mts.snapshot | 16 ++++++------- .../app/test/configure/getConfig.test.mts | 22 +++++++++++++++++ 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/packages/app/android/template.config.mjs b/packages/app/android/template.config.mjs index 633d7bb94..da3d5af7e 100644 --- a/packages/app/android/template.config.mjs +++ b/packages/app/android/template.config.mjs @@ -190,30 +190,36 @@ export function configure( * @param {ConfigureParams} params * @returns {Configuration} */ -export function getTemplate({ name, testAppPath, targetVersion }) { +export function getTemplate({ + name, + templatePath, + testAppPath, + targetVersion, +}) { const targetVersionNum = toVersionNumber(targetVersion); + // Use the Gradle wrapper from the template matching the target React Native + // version, falling back to the one bundled with the example app. + const wrapperDir = templatePath + ? path.join(templatePath, "android") + : path.join(testAppPath, "example", "android"); return { files: { "build.gradle": buildGradle(), "gradle/wrapper/gradle-wrapper.jar": copyFrom( - testAppPath, - "example", - "android", + wrapperDir, "gradle", "wrapper", "gradle-wrapper.jar" ), "gradle/wrapper/gradle-wrapper.properties": copyFrom( - testAppPath, - "example", - "android", + wrapperDir, "gradle", "wrapper", "gradle-wrapper.properties" ), "gradle.properties": gradleProperties(targetVersionNum), - gradlew: copyFrom(testAppPath, "example", "android", "gradlew"), - "gradlew.bat": copyFrom(testAppPath, "example", "android", "gradlew.bat"), + gradlew: copyFrom(wrapperDir, "gradlew"), + "gradlew.bat": copyFrom(wrapperDir, "gradlew.bat"), "settings.gradle": settingsGradle(name), }, oldFiles: [], diff --git a/packages/app/test/configure/gatherConfig.test.mts.snapshot b/packages/app/test/configure/gatherConfig.test.mts.snapshot index 99c226b31..0c7e0bf18 100644 --- a/packages/app/test/configure/gatherConfig.test.mts.snapshot +++ b/packages/app/test/configure/gatherConfig.test.mts.snapshot @@ -87,17 +87,17 @@ exports[`gatherConfig() > returns configuration for all platforms 1`] = ` "react-native.config.js": "const project = (() => {\\n try {\\n const { configureProjects } = require(\\"react-native-test-app\\");\\n return configureProjects({\\n android: {\\n sourceDir: \\"android\\",\\n },\\n ios: {\\n sourceDir: \\"ios\\",\\n },\\n macos: {\\n sourceDir: \\"macos\\",\\n },\\n visionos: {\\n sourceDir: \\"visionos\\",\\n },\\n windows: {\\n sourceDir: \\"windows\\",\\n solutionFile: \\"windows/Test.sln\\",\\n },\\n });\\n } catch (_) {\\n return undefined;\\n }\\n})();\\n\\nmodule.exports = {\\n ...(project ? { project } : undefined),\\n};\\n", "android/build.gradle": "buildscript {\\n apply(from: {\\n def searchDir = rootDir.toPath()\\n do {\\n def p = searchDir.resolve(\\"node_modules/react-native-test-app/android/dependencies.gradle\\")\\n if (p.toFile().exists()) {\\n return p.toRealPath().toString()\\n }\\n } while (searchDir = searchDir.getParent())\\n throw new GradleException(\\"Could not find \`react-native-test-app\`\\");\\n }())\\n\\n repositories {\\n mavenCentral()\\n google()\\n }\\n\\n dependencies {\\n getReactNativeDependencies().each { dependency ->\\n classpath(dependency)\\n }\\n }\\n}\\n", "android/gradle/wrapper/gradle-wrapper.jar": { - "source": "example/android/gradle/wrapper/gradle-wrapper.jar" + "source": "node_modules/@react-native-community/template/template/android/gradle/wrapper/gradle-wrapper.jar" }, "android/gradle/wrapper/gradle-wrapper.properties": { - "source": "example/android/gradle/wrapper/gradle-wrapper.properties" + "source": "node_modules/@react-native-community/template/template/android/gradle/wrapper/gradle-wrapper.properties" }, "android/gradle.properties": "# Project-wide Gradle settings.\\n\\n# IDE (e.g. Android Studio) users:\\n# Gradle settings configured through the IDE *will override*\\n# any settings specified in this file.\\n\\n# For more details on how to configure your build environment visit\\n# http://www.gradle.org/docs/current/userguide/build_environment.html\\n\\n# Specifies the JVM arguments used for the Gradle Daemon. The setting is\\n# particularly useful for configuring JVM memory settings for build performance.\\n# This does not affect the JVM settings for the Gradle client VM.\\n# The default is \`-Xmx512m -XX:MaxMetaspaceSize=256m\`.\\norg.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\\n\\n# When configured, Gradle will fork up to org.gradle.workers.max JVMs to execute\\n# projects in parallel. To learn more about parallel task execution, see the\\n# section on Gradle build performance:\\n# https://docs.gradle.org/current/userguide/performance.html#parallel_execution.\\n# Default is \`false\`.\\n#org.gradle.parallel=true\\n\\n# AndroidX package structure to make it clearer which packages are bundled with the\\n# Android operating system, and which are packaged with your app's APK\\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\\nandroid.useAndroidX=true\\n# Automatically convert third-party libraries to use AndroidX\\n#android.enableJetifier=true\\n# Jetifier randomly fails on these libraries\\n#android.jetifier.ignorelist=hermes-android,react-android\\n\\n# Use this property to specify which architecture you want to build.\\n# You can also override it from the CLI using\\n# ./gradlew -PreactNativeArchitectures=x86_64\\nreactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64\\n\\n# Use this property to enable support to the new architecture.\\n# This will allow you to use TurboModules and the Fabric render in\\n# your application. You should enable this flag either if you want\\n# to write custom TurboModules/Fabric components OR use libraries that\\n# are providing them.\\n# Note that this is incompatible with web debugging.\\nnewArchEnabled=true\\n#bridgelessEnabled=true\\n\\n# Uncomment the line below to build React Native from source.\\n#react.buildFromSource=true\\n\\n# Version of Android NDK to build against.\\n#ANDROID_NDK_VERSION=26.1.10909125\\n\\n# Version of Kotlin to build against.\\n#KOTLIN_VERSION=1.8.22\\n\\n# Use this property to enable edge-to-edge display support.\\n# This allows your app to draw behind system bars for an immersive UI.\\n# Note: Only works with ReactActivity and should not be used with custom Activity.\\nedgeToEdgeEnabled=true\\n\\n# Opt out of built-in kotlin and new DSL behavior that ships with AGP 9.\\n# Starting from AGP 10.x these opt outs will be removed.\\nandroid.builtInKotlin=false\\nandroid.newDsl=false\\n", "android/gradlew": { - "source": "example/android/gradlew" + "source": "node_modules/@react-native-community/template/template/android/gradlew" }, "android/gradlew.bat": { - "source": "example/android/gradlew.bat" + "source": "node_modules/@react-native-community/template/template/android/gradlew.bat" }, "android/settings.gradle": "pluginManagement {\\n repositories {\\n gradlePluginPortal()\\n mavenCentral()\\n google()\\n }\\n}\\n\\nrootProject.name = \\"Test\\"\\n\\napply(from: {\\n def searchDir = rootDir.toPath()\\n do {\\n def p = searchDir.resolve(\\"node_modules/react-native-test-app/test-app.gradle\\")\\n if (p.toFile().exists()) {\\n return p.toRealPath().toString()\\n }\\n } while (searchDir = searchDir.getParent())\\n throw new GradleException(\\"Could not find \`react-native-test-app\`\\");\\n}())\\napplyTestAppSettings(settings)\\n", "ios/Podfile": "ws_dir = Pathname.new(__dir__)\\nws_dir = ws_dir.parent until\\n File.exist?(\\"#{ws_dir}/node_modules/react-native-test-app/test_app.rb\\") ||\\n ws_dir.expand_path.to_s == '/'\\nrequire \\"#{ws_dir}/node_modules/react-native-test-app/test_app.rb\\"\\n\\nworkspace 'Test.xcworkspace'\\n\\nuse_test_app! :hermes_enabled => true, :fabric_enabled => true\\n", @@ -155,17 +155,17 @@ exports[`gatherConfig() > returns configuration for arbitrary platforms 1`] = ` "react-native.config.js": "const project = (() => {\\n try {\\n const { configureProjects } = require(\\"react-native-test-app\\");\\n return configureProjects({\\n android: {\\n sourceDir: \\"android\\",\\n },\\n ios: {\\n sourceDir: \\"ios\\",\\n },\\n macos: {\\n sourceDir: \\"macos\\",\\n },\\n visionos: {\\n sourceDir: \\"visionos\\",\\n },\\n windows: {\\n sourceDir: \\"windows\\",\\n solutionFile: \\"windows/Test.sln\\",\\n },\\n });\\n } catch (_) {\\n return undefined;\\n }\\n})();\\n\\nmodule.exports = {\\n ...(project ? { project } : undefined),\\n};\\n", "android/build.gradle": "buildscript {\\n apply(from: {\\n def searchDir = rootDir.toPath()\\n do {\\n def p = searchDir.resolve(\\"node_modules/react-native-test-app/android/dependencies.gradle\\")\\n if (p.toFile().exists()) {\\n return p.toRealPath().toString()\\n }\\n } while (searchDir = searchDir.getParent())\\n throw new GradleException(\\"Could not find \`react-native-test-app\`\\");\\n }())\\n\\n repositories {\\n mavenCentral()\\n google()\\n }\\n\\n dependencies {\\n getReactNativeDependencies().each { dependency ->\\n classpath(dependency)\\n }\\n }\\n}\\n", "android/gradle/wrapper/gradle-wrapper.jar": { - "source": "example/android/gradle/wrapper/gradle-wrapper.jar" + "source": "node_modules/@react-native-community/template/template/android/gradle/wrapper/gradle-wrapper.jar" }, "android/gradle/wrapper/gradle-wrapper.properties": { - "source": "example/android/gradle/wrapper/gradle-wrapper.properties" + "source": "node_modules/@react-native-community/template/template/android/gradle/wrapper/gradle-wrapper.properties" }, "android/gradle.properties": "# Project-wide Gradle settings.\\n\\n# IDE (e.g. Android Studio) users:\\n# Gradle settings configured through the IDE *will override*\\n# any settings specified in this file.\\n\\n# For more details on how to configure your build environment visit\\n# http://www.gradle.org/docs/current/userguide/build_environment.html\\n\\n# Specifies the JVM arguments used for the Gradle Daemon. The setting is\\n# particularly useful for configuring JVM memory settings for build performance.\\n# This does not affect the JVM settings for the Gradle client VM.\\n# The default is \`-Xmx512m -XX:MaxMetaspaceSize=256m\`.\\norg.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\\n\\n# When configured, Gradle will fork up to org.gradle.workers.max JVMs to execute\\n# projects in parallel. To learn more about parallel task execution, see the\\n# section on Gradle build performance:\\n# https://docs.gradle.org/current/userguide/performance.html#parallel_execution.\\n# Default is \`false\`.\\n#org.gradle.parallel=true\\n\\n# AndroidX package structure to make it clearer which packages are bundled with the\\n# Android operating system, and which are packaged with your app's APK\\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\\nandroid.useAndroidX=true\\n# Automatically convert third-party libraries to use AndroidX\\n#android.enableJetifier=true\\n# Jetifier randomly fails on these libraries\\n#android.jetifier.ignorelist=hermes-android,react-android\\n\\n# Use this property to specify which architecture you want to build.\\n# You can also override it from the CLI using\\n# ./gradlew -PreactNativeArchitectures=x86_64\\nreactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64\\n\\n# Use this property to enable support to the new architecture.\\n# This will allow you to use TurboModules and the Fabric render in\\n# your application. You should enable this flag either if you want\\n# to write custom TurboModules/Fabric components OR use libraries that\\n# are providing them.\\n# Note that this is incompatible with web debugging.\\nnewArchEnabled=true\\n#bridgelessEnabled=true\\n\\n# Uncomment the line below to build React Native from source.\\n#react.buildFromSource=true\\n\\n# Version of Android NDK to build against.\\n#ANDROID_NDK_VERSION=26.1.10909125\\n\\n# Version of Kotlin to build against.\\n#KOTLIN_VERSION=1.8.22\\n\\n# Use this property to enable edge-to-edge display support.\\n# This allows your app to draw behind system bars for an immersive UI.\\n# Note: Only works with ReactActivity and should not be used with custom Activity.\\nedgeToEdgeEnabled=true\\n\\n# Opt out of built-in kotlin and new DSL behavior that ships with AGP 9.\\n# Starting from AGP 10.x these opt outs will be removed.\\nandroid.builtInKotlin=false\\nandroid.newDsl=false\\n", "android/gradlew": { - "source": "example/android/gradlew" + "source": "node_modules/@react-native-community/template/template/android/gradlew" }, "android/gradlew.bat": { - "source": "example/android/gradlew.bat" + "source": "node_modules/@react-native-community/template/template/android/gradlew.bat" }, "android/settings.gradle": "pluginManagement {\\n repositories {\\n gradlePluginPortal()\\n mavenCentral()\\n google()\\n }\\n}\\n\\nrootProject.name = \\"Test\\"\\n\\napply(from: {\\n def searchDir = rootDir.toPath()\\n do {\\n def p = searchDir.resolve(\\"node_modules/react-native-test-app/test-app.gradle\\")\\n if (p.toFile().exists()) {\\n return p.toRealPath().toString()\\n }\\n } while (searchDir = searchDir.getParent())\\n throw new GradleException(\\"Could not find \`react-native-test-app\`\\");\\n}())\\napplyTestAppSettings(settings)\\n", "ios/Podfile": "ws_dir = Pathname.new(__dir__)\\nws_dir = ws_dir.parent until\\n File.exist?(\\"#{ws_dir}/node_modules/react-native-test-app/test_app.rb\\") ||\\n ws_dir.expand_path.to_s == '/'\\nrequire \\"#{ws_dir}/node_modules/react-native-test-app/test_app.rb\\"\\n\\nworkspace 'Test.xcworkspace'\\n\\nuse_test_app! :hermes_enabled => true, :fabric_enabled => true\\n" diff --git a/packages/app/test/configure/getConfig.test.mts b/packages/app/test/configure/getConfig.test.mts index c92fb6135..c474d6ff5 100644 --- a/packages/app/test/configure/getConfig.test.mts +++ b/packages/app/test/configure/getConfig.test.mts @@ -1,6 +1,7 @@ import { deepEqual, ok } from "node:assert/strict"; import * as path from "node:path"; import { describe, it } from "node:test"; +import { getTemplate as getAndroidTemplate } from "../../android/template.config.mjs"; import { getConfig as getConfigActual, getPlatformPackage, @@ -10,6 +11,12 @@ import { templatePath } from "../template.mts"; import { mockParams } from "./mockParams.mts"; describe("getConfig()", () => { + const gradleWrapperFiles = [ + "gradle/wrapper/gradle-wrapper.jar", + "gradle/wrapper/gradle-wrapper.properties", + "gradlew", + "gradlew.bat", + ]; const getConfig: typeof getConfigActual = (params, platform) => getConfigActual({ ...params, templatePath }, platform, true); @@ -79,6 +86,21 @@ describe("getConfig()", () => { "settings.gradle", ]); deepEqual(config.oldFiles, []); + + for (const file of gradleWrapperFiles) { + deepEqual(config.files[file], { + source: path.join(templatePath, "android", file), + }); + } + }); + + it("falls back to the example app's Gradle wrapper without a template", () => { + const { files } = getAndroidTemplate(mockParams()); + for (const file of gradleWrapperFiles) { + deepEqual(files[file], { + source: path.join(".", "example", "android", file), + }); + } }); it("returns iOS specific scripts and additional files", () => {