import com.android.build.gradle.tasks.ExternalNativeBuildJsonTask import expo.modules.plugin.gradle.ExpoModuleExtension import groovy.json.JsonSlurper import org.jetbrains.kotlin.gradle.tasks.KotlinCompile buildscript { // List of features that are required by linked modules def coreFeatures = project.findProperty("coreFeatures") ?: [] ext.shouldIncludeCompose = coreFeatures.contains("compose") repositories { mavenCentral() } dependencies { if (shouldIncludeCompose) { classpath("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:${kotlinVersion}") } classpath("org.apache.commons:commons-text:1.14.0") } } apply plugin: 'com.android.library' apply plugin: 'expo-module-gradle-plugin' if (shouldIncludeCompose) { apply plugin: 'org.jetbrains.kotlin.plugin.compose' } group = 'host.exp.exponent' version = '3.0.29' def isExpoModulesCoreTests = { Gradle gradle = getGradle() String tskReqStr = gradle.getStartParameter().getTaskRequests().toString() if (tskReqStr =~ /:expo-modules-core:connected\w*AndroidTest/) { def androidTests = project.file("src/androidTest") return androidTests.exists() && androidTests.isDirectory() } return false }.call() def expoModuleExtension = project.extensions.getByType(ExpoModuleExtension) def reactNativeArchitectures() { def value = project.getProperties().get("reactNativeArchitectures") return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"] } // HERMES def USE_HERMES = true if (findProject(":app")) { def appProject = project(":app") USE_HERMES = appProject?.hermesEnabled?.toBoolean() || appProject?.ext?.react?.enableHermes?.toBoolean() } // Currently the needs for hermes/jsc are only for androidTest, so we turn on this flag only when `isExpoModulesCoreTests` is true USE_HERMES = USE_HERMES && isExpoModulesCoreTests // END HERMES def isNewArchitectureEnabled = findProperty("newArchEnabled") == "true" def shouldTurnWarningsIntoErrors = findProperty("EXPO_TURN_WARNINGS_INTO_ERRORS") == "true" expoModule { canBePublished false } android { if (rootProject.hasProperty("ndkPath")) { ndkPath rootProject.ext.ndkPath } if (rootProject.hasProperty("ndkVersion")) { ndkVersion rootProject.ext.ndkVersion } namespace "expo.modules" defaultConfig { consumerProguardFiles 'proguard-rules.pro' versionCode 1 versionName "3.0.29" buildConfigField "String", "EXPO_MODULES_CORE_VERSION", "\"${versionName}\"" buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled.toString() testInstrumentationRunner "expo.modules.TestRunner" externalNativeBuild { cmake { abiFilters(*reactNativeArchitectures()) arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", "-DREACT_NATIVE_DIR=${expoModuleExtension.reactNativeDir}", "-DREACT_NATIVE_TARGET_VERSION=${expoModuleExtension.reactNativeVersion.minor}", "-DUSE_HERMES=${USE_HERMES}", "-DIS_NEW_ARCHITECTURE_ENABLED=${isNewArchitectureEnabled}", "-DUNIT_TEST=${isExpoModulesCoreTests}" } } } externalNativeBuild { cmake { path "CMakeLists.txt" } } buildFeatures { buildConfig true prefab true compose shouldIncludeCompose } packagingOptions { // Gradle will add cmake target dependencies into packaging. // Theses files are intermediated linking files to build modules-core and should not be in final package. def sharedLibraries = [ "**/libc++_shared.so", "**/libfabricjni.so", "**/libfbjni.so", "**/libfolly_json.so", "**/libfolly_runtime.so", "**/libglog.so", "**/libhermes.so", "**/libjscexecutor.so", "**/libjsi.so", "**/libreactnative.so", "**/libreactnativejni.so", "**/libreact_debug.so", "**/libreact_nativemodule_core.so", "**/libreact_utils.so", "**/libreact_render_debug.so", "**/libreact_render_graphics.so", "**/libreact_render_core.so", "**/libreact_render_componentregistry.so", "**/libreact_render_mapbuffer.so", "**/librrc_view.so", "**/libruntimeexecutor.so", "**/libyoga.so", ] // Required or mockk will crash resources { excludes += [ "META-INF/LICENSE.md", "META-INF/LICENSE-notice.md" ] } // In android (instrumental) tests, we want to package all so files to enable our JSI functionality. // Otherwise, those files should be excluded, because will be loaded by the application. if (isExpoModulesCoreTests) { pickFirsts += sharedLibraries } else { excludes += sharedLibraries } } sourceSets { main { java { if (shouldIncludeCompose) { srcDirs += 'src/compose' } else { srcDirs += 'src/withoutCompose' } } } } testOptions { unitTests.includeAndroidResources = true unitTests.all { test -> testLogging { outputs.upToDateWhen { false } events "passed", "failed", "skipped", "standardError" showCauses true showExceptions true showStandardStreams true } } } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVersion}" implementation "org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}" implementation 'androidx.annotation:annotation:1.7.1' api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" api "androidx.core:core-ktx:1.13.1" if (shouldIncludeCompose) { implementation 'androidx.compose.foundation:foundation-android:1.7.6' } implementation("androidx.tracing:tracing-ktx:1.2.0") implementation 'com.facebook.react:react-android' compileOnly 'com.facebook.fbjni:fbjni:0.5.1' testImplementation 'androidx.test:core:1.5.0' testImplementation 'junit:junit:4.13.2' testImplementation 'io.mockk:mockk:1.13.10' testImplementation "com.google.truth:truth:1.1.2" testImplementation "org.robolectric:robolectric:4.11.1" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0" testImplementation "org.json:json:20230227" androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:core:1.5.0' androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation "io.mockk:mockk-android:1.13.10" androidTestImplementation "com.google.truth:truth:1.1.2" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0" if (isExpoModulesCoreTests) { if (USE_HERMES) { compileOnly "com.facebook.react:hermes-android" } else { compileOnly "org.webkit:android-jsc:+" } } } if (shouldTurnWarningsIntoErrors) { tasks.withType(JavaCompile) configureEach { options.compilerArgs << "-Werror" << "-Xlint:all" << '-Xlint:-serial' << '-Xlint:-rawtypes' } tasks.withType(KotlinCompile) configureEach { compilerOptions.allWarningsAsErrors = true } } // Generates the PCH file during sync if it doesn't exist yet def generatePCHTask = tasks.register("generatePCH") { def configureTaskName = "configureCMakeDebug" dependsOn(configureTaskName) doLast { reactNativeArchitectures().each { abi -> def configureTaskNameForAbi = configureTaskName + "[" + abi + "]" ExternalNativeBuildJsonTask configureTask = tasks.named(configureTaskNameForAbi).get() as ExternalNativeBuildJsonTask // Gets CxxModel for the given ABI File cxxBuildFolder = configureTask.abi.cxxBuildFolder // Gets compile_commands.json file to find the command to generate the PCH file File compileCommandsFile = new File(cxxBuildFolder, "compile_commands.json") if (!compileCommandsFile.exists()) { return } def parsedJson = new JsonSlurper().parseText(compileCommandsFile.text) for (int i = 0; i < parsedJson.size(); i++) { def commandObj = parsedJson[i] def path = commandObj.file if (!path.endsWith("cmake_pch.hxx.cxx")) { continue } def generatedFilePath = path.substring(0, path.length() - ".cxx".length()) + ".pch" // Checks if the file already exists, and skip if so if (new File(generatedFilePath).exists()) { continue } def tokenizer = new org.apache.commons.text.StringTokenizer(commandObj.command, " ") def tokens = tokenizer.tokenList def workingDirFile = new File(commandObj.directory) providers.exec { workingDir(providers.provider { workingDirFile }.get()) commandLine(tokens) }.getResult().get().assertNormalExitValue() } } } } // This task will run on the IDE project sync, ensuring the PCH file is generated early enough tasks.register("prepareKotlinBuildScriptModel") { dependsOn(generatePCHTask) }