Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execution failed for task ':react-native-gesture-handler:buildCodegenCLI' + Building with Monorepo #2299

Closed
only1chi opened this issue Oct 30, 2022 · 14 comments
Labels
Missing repro Platform: Android This issue is specific to Android

Comments

@only1chi
Copy link

Description

I can't seem to build react-native-gesture-handler with my monorepo setup. I get the following error:

FAILURE: Build completed with 2 failures.

1: Task failed with an exception.
-----------
* What went wrong:
Execution failed for task ':react-native-gesture-handler:buildCodegenCLI'.
> A problem occurred starting process 'command '/Users/redacted/Desktop/Git_Repo/janus/packages/testapp/node_modules/react-native-codegen/scripts/oss/build.sh''

Looking at my workspace, all the packages are in the node-modules on the root folder. However, codegen CLI is looking for the scripts in the workspace node_modules folder instead of the root node_modules. See image below:

image

Steps to reproduce

  1. Configure a mono repo with workspaces
  2. Use react-native-gesture-handler in one of the workspaces
  3. Build the project

Snack or a link to a repository

Private Repo

Gesture Handler version

2.7.1

React Native version

0.70.4

Platforms

Android

JavaScript runtime

Hermes

Workflow

React Native (without Expo)

Architecture

Fabric (New Architecture)

Build type

Debug mode

Device

Android emulator

Device model

No response

Acknowledgements

Yes

@github-actions github-actions bot added Platform: Android This issue is specific to Android Missing repro labels Oct 30, 2022
@github-actions
Copy link

Hey! 👋

The issue doesn't seem to contain a minimal reproduction.

Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?

@only1chi
Copy link
Author

This piece of code in react-native-gesture-handler/main/android/build.gradle shows that it is not aware of the monorepo structure.

image

@j-piasecki
Copy link
Member

This seems to be more of a problem with the codegen itself. The code snippet you've highlighted is kind of problematic, but it would fail only in cases where react-native-gesture-handler is higher in the directory structure than react-native itself, which doesn't seem to be the case here.

In case you're using a custom setup, you are able to set the paths to relevant packages in your build.gradle like so:

react {
    reactNativeDir = rootProject.file("../../../node_modules/react-native/")
    codegenDir = rootProject.file("../../../node_modules/react-native-codegen/")
}

The problem with that is the fact that React Gradle Plugin is also used by the libraries on the new architecture in order to trigger codegen. Because of this, the custom paths are only applied to the root project instead of all projects using the plugin. If all your packages are on the same level, you could use something like this:

project.pluginManager.withPlugin("com.facebook.react") {
    react {
        reactNativeDir = rootProject.file("../../../node_modules/react-native/")
        codegenDir = rootProject.file("../../../node_modules/react-native-codegen/")
    }
}

to set the same paths for all projects using the plugin. Otherwise you may need to take a more granular approach and set the paths per package depending on whether it's being hoisted or not.

@only1chi
Copy link
Author

only1chi commented Nov 9, 2022

@j-piasecki Thanks for the tip.
I am using the upgrade helper utility to upgrade to RN 70.5.
In the app/build.gradle file there is a project.ext.react block; NOT a react {} block.
When I try to change to using a react {} block to define the folders as you suggested above, I get other build errors. For example:

> Could not set unknown property 'cliFile' for extension 'react' of type com.facebook.react.ReactExtension.

The react block you reference is defined in the android template. However, whenever I try to use this block in my app, I get the errors noted above.

Also, I'm using the new architecture, which requires some modifications to the build.gradle file which does not exist in the template above.

@j-piasecki
Copy link
Member

@only1chi The template you've linked to is already updated for React Native 0.71.0-rc.0, and it seems like the cliFile property replaced the cliPath in facebook/react-native@2df63e5. So in your case, using replacing cliFile with cliPath should do the trick.

@only1chi
Copy link
Author

@j-piasecki Thanks again for your response and notes.

I had substituted cliPath for cliFile, which is defined in the app/build.gradle file, in the projects.ext.react block, but I'm still not able to properly build my application.
I do know that it has to do with the mono repo structure, which is setup using yarn workspaces. Pointedly, it does not build when using the new architecture. The project builds with RN 0.68.2 and the old architecture.

A couple of things to note:

  • When I configure one the packages to NOT hoist (for yarn v3.2, this is done by setting nmHoistingLimit to "workspaces"), I can successfully build the application. In this configuration, the node-modules are replicated within the package/app structure. However it crashes on startup. This is because it does not properly resolve files and folders between the root folder and the package sub-folders.
  • On the other hand, when I try to build as before (node-modules) is hoisted to the root. Then I get build issues. This is frustrating because this configuration works with RN 0.68.2 and old architecture. Something about the new architecture, and having to build packages with react-native-codegen is problematic. I haven't been able to find it.

See below android/build.gradle, android/app/build.gradle and settings.gradle files. You can see how the package is configured to build. Perhaps I missed something.

android/build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext {
        buildToolsVersion = "31.0.0"
        minSdkVersion = 26
        compileSdkVersion = 31
        targetSdkVersion = 31
        kotlinVersion = '1.6.21'        // for react-native screens
        if (System.properties['os.arch'] == "aarch64") {
            // For M1 Users we need to use the NDK 24 which added support for aarch64
            ndkVersion = "24.0.8215888"
        } else {
            // Otherwise we default to the side-by-side NDK version from AGP.
            ndkVersion = "21.4.7075529"
        }
    }
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:7.2.2")
        classpath("com.facebook.react:react-native-gradle-plugin")
        classpath("de.undercouch:gradle-download-task:5.0.1")
        classpath "com.google.gms:google-services:4.2.0"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
        classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.1'
    }
}

allprojects {
    repositories {
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url("$rootDir/../../../node_modules/react-native/android")
        }
        maven {
            // Android JSC is installed from npm
            url("$rootDir/../../../node_modules/jsc-android/dist")
        }
        mavenCentral {
            // We don't want to fetch react-native from Maven Central as there are
            // older versions over there.
            content {
                excludeGroup "com.facebook.react"
            }
        }
        google()
        maven { url 'https://www.jitpack.io' }
    }
}

android/app/build.gradle

apply plugin: "com.android.application"

import com.android.build.OutputFile
import org.apache.tools.ant.taskdefs.condition.Os


project.ext.react = [
    cliPath: "../../../../node_modules/react-native/cli.js",
    enableHermes: true,  // clean and rebuild if changing
    entryFile: "index.js"
]

apply from: "../../../../node_modules/react-native/react.gradle"

/**
 * Set this to true to create two separate APKs instead of one:
 *   - An APK that only works on ARM devices
 *   - An APK that only works on x86 devices
 * The advantage is the size of the APK is reduced by about 4MB.
 * Upload all the APKs to the Play Store and people will download
 * the correct one based on the CPU architecture of their device.
 */
def enableSeparateBuildPerCPUArchitecture = false

/**
 * Run Proguard to shrink the Java bytecode in release builds.
 */
def enableProguardInReleaseBuilds = false

/**
 * The preferred build flavor of JavaScriptCore.
 *
 * 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:+'

/**
 * Whether to enable the Hermes VM.
 *
 * This should be set on project.ext.react and that value will be read here. If it is not set
 * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
 * and the benefits of using Hermes will therefore be sharply reduced.
 */
def enableHermes = project.ext.react.get("enableHermes", false);

/**
 * Architectures to build native code for.
 */
def reactNativeArchitectures() {
    def value = project.getProperties().get("reactNativeArchitectures")
    return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}

android {
    ndkVersion rootProject.ext.ndkVersion

    compileSdkVersion rootProject.ext.compileSdkVersion

    defaultConfig {
        applicationId "com.testapp"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
        missingDimensionStrategy 'react-native-camera', 'mlkit'
        buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
        if (isNewArchitectureEnabled()) {
            // We configure the NDK build only if you decide to opt-in for the New Architecture.
            externalNativeBuild {
                ndkBuild {
                    arguments "PROJECT_BUILD_DIR=$buildDir",
                        "REACT_ANDROID_DIR=$rootDir/../../../node_modules/react-native/ReactAndroid",
                        "REACT_ANDROID_BUILD_DIR=$rootDir/../../../node_modules/react-native/ReactAndroid/build"
                        "NODE_MODULES_DIR=../../../../node_modules"
                    }
                cmake {
                    arguments "-DPROJECT_BUILD_DIR=$buildDir",
                        "-DREACT_ANDROID_DIR=$rootDir/../../../node_modules/react-native/ReactAndroid",
                        "-DREACT_ANDROID_BUILD_DIR=$rootDir/../../../node_modules/react-native/ReactAndroid/build",
                        "-DNODE_MODULES_DIR=$rootDir/../../../node_modules",
                        "-DANDROID_STL=c++_shared"
                }
            }
            if (!enableSeparateBuildPerCPUArchitecture) {
                ndk {
                    abiFilters (*reactNativeArchitectures())
                }
            }
        }
    }
    if (isNewArchitectureEnabled()) {
        // We configure the CMake build only if you decide to opt-in for the New Architecture.
        externalNativeBuild {
            cmake {
                path "$projectDir/src/main/jni/CMakeLists.txt"
            }
        }
        def reactAndroidProjectDir = project(':ReactAndroid').projectDir
        def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) {
            dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck")
            from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
            into("$buildDir/react-ndk/exported")
        }
        def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) {
            dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck")
            from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
            into("$buildDir/react-ndk/exported")
        }
        afterEvaluate {
            // If you wish to add a custom TurboModule or component locally,
            // you should uncomment this line.
            // preBuild.dependsOn("generateCodegenArtifactsFromSchema")
            preDebugBuild.dependsOn(packageReactNdkDebugLibs)
            preReleaseBuild.dependsOn(packageReactNdkReleaseLibs)
            // Due to a bug inside AGP, we have to explicitly set a dependency
            // between configureCMakeDebug* tasks and the preBuild tasks.
            // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732
            configureCMakeRelWithDebInfo.dependsOn(preReleaseBuild)
            configureCMakeDebug.dependsOn(preDebugBuild)
            reactNativeArchitectures().each { architecture ->
                tasks.findByName("configureCMakeDebug[${architecture}]")?.configure {
                    dependsOn("preDebugBuild")
                }
                tasks.findByName("configureCMakeRelWithDebInfo[${architecture}]")?.configure {
                    dependsOn("preReleaseBuild")
                }
            }
        }
    }
    splits {
        abi {
            reset()
            enable enableSeparateBuildPerCPUArchitecture
            universalApk false  // If true, also generate a universal APK
            include (*reactNativeArchitectures())
        }
    }
    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
            minifyEnabled enableProguardInReleaseBuilds
            proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
        }
    }

    // applicationVariants are e.g. debug, release
    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            // For each separate APK per architecture, set a unique version code as described here:
            // https://developer.android.com/studio/build/configure-apk-splits.html
            // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
            def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
            def abi = output.getFilter(OutputFile.ABI)
            if (abi != null) {  // null for the universal-debug, universal-release variants
                output.versionCodeOverride =
                        defaultConfig.versionCode * 1000 + versionCodes.get(abi)
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])

    //noinspection GradleDynamicVersion
    implementation "com.facebook.react:react-native:+"  // From node_modules

    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"

    debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.fbjni'
    }

    debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.flipper'
        exclude group:'com.squareup.okhttp3', module:'okhttp'
    }

    debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
        exclude group:'com.facebook.flipper'
    }

    if (enableHermes) {
        //noinspection GradleDynamicVersion
        implementation("com.facebook.react:hermes-engine:+") { // From node_modules
            exclude group:'com.facebook.fbjni'
        }
    } else {
        implementation jscFlavor
    }
}

if (isNewArchitectureEnabled()) {
    // If new architecture is enabled, we let you build RN from source
    // Otherwise we fallback to a prebuilt .aar bundled in the NPM package.
    // This will be applied to all the imported transtitive dependency.
    configurations.all {
        resolutionStrategy.dependencySubstitution {
            substitute(module("com.facebook.react:react-native"))
                    .using(project(":ReactAndroid"))
                    .because("On New Architecture we're building React Native from source")
            substitute(module("com.facebook.react:hermes-engine"))
                    .using(project(":ReactAndroid:hermes-engine"))
                    .because("On New Architecture we're building Hermes from source")
        }
    }
}

// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
    from configurations.implementation
    into 'libs'
}

apply from: "../../../../node_modules/react-native-vector-icons/fonts.gradle"
apply from: file("../../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'

def isNewArchitectureEnabled() {
    // To opt-in for the New Architecture, you can either:
    // - Set `newArchEnabled` to true inside the `gradle.properties` file
    // - Invoke gradle with `-newArchEnabled=true`
    // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
    return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}

settings.gradle

rootProject.name = 'testapp'
apply from: file("../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
includeBuild('../../../node_modules/react-native-gradle-plugin')

if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") {
    include(":ReactAndroid")
    project(":ReactAndroid").projectDir = file('../../../node_modules/react-native/ReactAndroid')
    include(":ReactAndroid:hermes-engine")
    project(":ReactAndroid:hermes-engine").projectDir = file('../../../node_modules/react-native/ReactAndroid/hermes-engine')
}

@j-piasecki
Copy link
Member

If the problem is still related to codegen, then you might try adding the react {} block from one of my previous posts (with paths adjusted). I believe that at the moment you can have both react {} and project.ext.react {} blocks present.

If it's not related to codegen, could you paste the gradle log?

@only1chi
Copy link
Author

only1chi commented Nov 11, 2022

@j-piasecki I did some more digging...

Also it is clear that the application is failing to generate the schema for react-native-gesture-handler. The command:
./gradlew generateCodegenArtifactsFromSchema

Results inTask :react-native-gesture-handler:buildCodegenCLI FAILED

I managed to drill down to the BuildCodegenCLITask.kt file and it appears that codegenDir is not pointing to the location I expect to see it in.

Here is a code excerpt from react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/BuildCodegenCLITask.kt

  init {
    // We need this condition as we want a single instance of BuildCodegenCLITask to execute
    // per project. Therefore we can safely skip the task if the lib/cli/ folder is available.
    onlyIf {
      val cliDir = codegenDir.file("lib/cli/").get().asFile
      !cliDir.exists() || cliDir.listFiles()?.size == 0
    }
  }

Per the code above, the build should skip the buildcodegenCLI task for each module if it can locate the react-native-codegen directory.
In my application it is looking in the root/packages/workspace/node-modules/react-native-codegen folder.
I have modified my app/build.gradle to explicitly set codegenDir definition in two ways, but that doesn't help the situation.

apply(plugin: "com.facebook.react")
react {
    reactNativeDir = rootProject.file("../../../node_modules/react-native/")
    codegenDir = rootProject.file("../../../node_modules/react-native-codegen/")
}

project.ext.react = [
    cliPath: "../../../../node_modules/react-native/cli.js",
    enableHermes: true,  // clean and rebuild if changing
    codegenDir: "../../../../node_modules/react-native-codegen/",
    reactNativeDir: "../../../../node_modules/react-native/"
]

Unfortunately, I am not successful.

Here's the error:

> Task :react-native-gesture-handler:buildCodegenCLI FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':react-native-gesture-handler:buildCodegenCLI'.
> A problem occurred starting process 'command '/Users/chizurokechikwendu/Desktop/Git_Repo/janus/packages/testapp/node_modules/react-native-codegen/scripts/oss/build.sh''

@j-piasecki
Copy link
Member

One thing I've noticed, is the fact that you've set different paths in the react {} block and the project.ext.react array:

react {
    reactNativeDir = rootProject.file("../../../node_modules/react-native/")
    codegenDir = rootProject.file("../../../node_modules/react-native-codegen/")
}
project.ext.react = [
    ...
    codegenDir: "../../../../node_modules/react-native-codegen/",
    reactNativeDir: "../../../../node_modules/react-native/"
]

Based on the paths you've described, I think the ones in the react {} block may be going up one level too little. Could you try adding a one ../ to them to match the ones in the project.ext.react block? (or make them match the other way around, in case I mixed up the paths).

@only1chi
Copy link
Author

@j-piasecki
Thanks for the observation. I made changes to the react block, and actually added print statements to verify that I do have the right location configured.
The code block is as follows:

apply(plugin: "com.facebook.react")
react {
    reactNativeDir = rootProject.file("../../../node_modules/react-native/")
    codegenDir = rootProject.file("../../../node_modules/react-native-codegen/")
    println("codegenDir: ${rootProject.file("../../../node_modules/react-native-codegen/")}")
    println("reactNativeDir: ${rootProject.file("../../../node_modules/react-native/")}")
}

I can confirm that the correct codegen directory is setup.

image

However, I'm still encountering similar problems which I think has to do with the codegen directory. The file BuildCodgenCLITask.kt is called by react-gradle-plugin when it tries to generate schema. The problem is that that the codegen directory seems to change. See image below:

image

@j-piasecki
Copy link
Member

That's what I meant by

The problem with that is the fact that React Gradle Plugin is also used by the libraries on the new architecture in order to trigger codegen. Because of this, the custom paths are only applied to the root project instead of all projects using the plugin.

You've configured the paths for the :app subproject, but other subprojects (namely, libraries you're using) also use the React Native Gradle Plugin, and this config is not propagated to them, so they're using the default paths.

You should be able to set the same path for every subproject by adding

project.pluginManager.withPlugin("com.facebook.react") {
    react {
        reactNativeDir = rootProject.file("../../../node_modules/react-native/")
        codegenDir = rootProject.file("../../../node_modules/react-native-codegen/")
    }
}

in the allprojects {} block in your top-level build.gradle.

@only1chi
Copy link
Author

@j-piasecki
Thank you for being patient and explaining what you meant.
I did try what you suggested. I had to put print statements to see what folder locations where being referenced.
It worked!!
I have other unrelated problems which I will resolve.
Thanks a lot for your help.

@j-piasecki
Copy link
Member

No problem, great to see that you were able to solve the problem. I'll close the issue since you've resolved the problems with codegen.

@haiderTkxel
Copy link

@j-piasecki

Task :react-native-gesture-handler:releaseSourcesJar FAILED

FAILURE: Build failed with an exception.

  • What went wrong:
    A problem was found with the configuration of task ':react-native-gesture-handler:releaseSourcesJar' (type 'Jar').
    • Gradle detected a problem with the following location: '/Users/tk-lpt-1073/Documents/Haider/PlayerU/node_modules/react-native-gesture-handler/android/build/generated/source/codegen/java'.

      Reason: Task ':react-native-gesture-handler:releaseSourcesJar' uses this output of task ':react-native-gesture-handler:generateCodegenArtifactsFromSchema' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.

      Possible solutions:

      1. Declare task ':react-native-gesture-handler:generateCodegenArtifactsFromSchema' as an input of ':react-native-gesture-handler:releaseSourcesJar'.
      2. Declare an explicit dependency on ':react-native-gesture-handler:generateCodegenArtifactsFromSchema' from ':react-native-gesture-handler:releaseSourcesJar' using Task#dependsOn.
      3. Declare an explicit dependency on ':react-native-gesture-handler:generateCodegenArtifactsFromSchema' from ':react-native-gesture-handler:releaseSourcesJar' using Task#mustRunAfter.

      For more information, please refer to https://docs.gradle.org/8.10.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Missing repro Platform: Android This issue is specific to Android
Projects
None yet
Development

No branches or pull requests

3 participants