feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,23 @@
# @react-native/gradle-plugin
[![Version][version-badge]][package]
A Gradle Plugin used to support development of React Native applications for Android.
## Installation
```
yarn add @react-native/gradle-plugin
```
*Note: We're using `yarn` to install deps. Feel free to change commands to use `npm` 3+ and `npx` if you like*
[version-badge]: https://img.shields.io/npm/v/@react-native/gradle-plugin?style=flat-square
[package]: https://www.npmjs.com/package/@react-native/gradle-plugin
## Testing
To run the tests in this package, run the following commands from the React Native root folder:
1. `yarn` to install the dependencies. You just need to run this once
2. `yarn jest packages/react-native-gradle-plugin`.

View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import org.gradle.api.internal.classpath.ModuleRegistry
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.configurationcache.extensions.serviceOf
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
alias(libs.plugins.kotlin.jvm)
id("java-gradle-plugin")
}
repositories {
google()
mavenCentral()
}
gradlePlugin {
plugins {
create("react") {
id = "com.facebook.react"
implementationClass = "com.facebook.react.ReactPlugin"
}
create("reactrootproject") {
id = "com.facebook.react.rootproject"
implementationClass = "com.facebook.react.ReactRootProjectPlugin"
}
}
}
group = "com.facebook.react"
dependencies {
implementation(gradleApi())
// The KGP/AGP version is defined by React Native Gradle plugin.
// Therefore we specify an implementation dep rather than a compileOnly.
implementation(libs.kotlin.gradle.plugin)
implementation(libs.android.gradle.plugin)
implementation(libs.gson)
implementation(libs.guava)
implementation(libs.javapoet)
testImplementation(libs.junit)
testRuntimeOnly(
files(
serviceOf<ModuleRegistry>()
.getModule("gradle-tooling-api-builders")
.classpath
.asFiles
.first()))
}
// We intentionally don't build for Java 17 as users will see a cryptic bytecode version
// error first. Instead we produce a Java 11-compatible Gradle Plugin, so that AGP can print their
// nice message showing that JDK 11 (or 17) is required first
java { targetCompatibility = JavaVersion.VERSION_11 }
kotlin { jvmToolchain(17) }
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
apiVersion = "1.6"
// See comment above on JDK 11 support
jvmTarget = "11"
allWarningsAsErrors = true
}
}
tasks.withType<Test>().configureEach {
testLogging {
exceptionFormat = TestExceptionFormat.FULL
showExceptions = true
showCauses = true
showStackTraces = true
}
}

View File

@@ -0,0 +1,18 @@
[versions]
agp = "8.2.1"
gson = "2.8.9"
guava = "31.0.1-jre"
javapoet = "1.13.0"
junit = "4.13.2"
kotlin = "1.9.22"
[libraries]
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
javapoet = { module = "com.squareup:javapoet", version.ref = "javapoet" }
junit = {module = "junit:junit", version.ref = "junit" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -0,0 +1,35 @@
{
"name": "@react-native/gradle-plugin",
"version": "0.74.81",
"description": "Gradle Plugin for React Native",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/facebook/react-native.git",
"directory": "packages/react-native-gradle-plugin"
},
"homepage": "https://github.com/facebook/react-native/tree/HEAD/packages/react-native-gradle-plugin#readme",
"keywords": [
"gradle",
"plugin",
"react-native"
],
"bugs": "https://github.com/facebook/react-native/issues",
"engines": {
"node": ">=18"
},
"scripts": {
"build": "./gradlew build",
"clean": "./gradlew clean",
"test": "./gradlew check"
},
"files": [
"settings.gradle.kts",
"build.gradle.kts",
"gradle",
"gradlew",
"gradlew.bat",
"src/main",
"README.md"
]
}

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
pluginManagement {
repositories {
mavenCentral()
google()
gradlePluginPortal()
}
}
plugins { id("org.gradle.toolchains.foojay-resolver-convention").version("0.5.0") }
rootProject.name = "react-native-gradle-plugin"

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react
import com.facebook.react.utils.projectPathToLibraryName
import javax.inject.Inject
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
abstract class ReactExtension @Inject constructor(project: Project) {
private val objects = project.objects
/**
* The path to the root of your project. This is the path to where the `package.json` lives. All
* the CLI commands will be invoked from this folder as working directory.
*
* Default: ${rootProject.dir}/../
*/
val root: DirectoryProperty =
objects.directoryProperty().convention(project.rootProject.layout.projectDirectory.dir("../"))
/**
* The path to the react-native NPM package folder.
*
* Default: ${rootProject.dir}/../node_modules/react-native
*/
val reactNativeDir: DirectoryProperty =
objects.directoryProperty().convention(root.dir("node_modules/react-native"))
/**
* The path to the JS entry file. If not specified, the plugin will try to resolve it using a list
* of known locations (e.g. `index.android.js`, `index.js`, etc.).
*/
val entryFile: RegularFileProperty = objects.fileProperty()
/**
* The reference to the React Native CLI. If not specified, the plugin will try to resolve it
* looking for `react-native` CLI inside `node_modules` in [root].
*/
val cliFile: RegularFileProperty =
objects.fileProperty().convention(reactNativeDir.file("cli.js"))
/**
* The path to the Node executable and extra args. By default it assumes that you have `node`
* installed and configured in your $PATH. Default: ["node"]
*/
val nodeExecutableAndArgs: ListProperty<String> =
objects.listProperty(String::class.java).convention(listOf("node"))
/** The command to use to invoke bundle. Default is `bundle` and will be invoked on [root]. */
val bundleCommand: Property<String> = objects.property(String::class.java).convention("bundle")
/**
* Custom configuration file for the [bundleCommand]. If provided, it will be passed over with a
* `--config` flag to the bundle command.
*/
val bundleConfig: RegularFileProperty = objects.fileProperty()
/**
* The Bundle Asset name. This name will be used also for deriving other bundle outputs such as
* the packager source map, the compiler source map and the output source map file.
*
* Default: index.android.bundle
*/
val bundleAssetName: Property<String> =
objects.property(String::class.java).convention("index.android.bundle")
/**
* Toggles the .so Cleanup step. If enabled, we will clean up all the unnecessary files before the
* bundle task. If disabled, the developers will have to manually cleanup the files. Default: true
*/
val enableSoCleanup: Property<Boolean> = objects.property(Boolean::class.java).convention(true)
/** Extra args that will be passed to the [bundleCommand] Default: [] */
val extraPackagerArgs: ListProperty<String> =
objects.listProperty(String::class.java).convention(emptyList())
/**
* Allows to specify the debuggable variants (by default just 'debug'). Variants in this list
* will:
* - Not be bundled (the bundle file will not be created and won't be copied over).
* - Have the Hermes Debug flags set. That's useful if you have another variant (say `canary`)
* where you want dev mode to be enabled. Default: ['debug']
*/
val debuggableVariants: ListProperty<String> =
objects.listProperty(String::class.java).convention(listOf("debug"))
/** Hermes Config */
/**
* The command to use to invoke hermesc (the hermes compiler). Default is "", the plugin will
* autodetect it.
*/
val hermesCommand: Property<String> = objects.property(String::class.java).convention("")
/**
* Whether to enable Hermes only on certain variants. If specified as a non-empty list, hermesc
* and the .so cleanup for Hermes will be executed only for variants in this list. An empty list
* assumes you're either using Hermes for all variants or not (see [enableHermes]).
*
* Default: []
*/
val enableHermesOnlyInVariants: ListProperty<String> =
objects.listProperty(String::class.java).convention(emptyList())
/** Flags to pass to Hermesc. Default: ["-O", "-output-source-map"] */
val hermesFlags: ListProperty<String> =
objects.listProperty(String::class.java).convention(listOf("-O", "-output-source-map"))
/** Codegen Config */
/**
* The path to the react-native-codegen NPM package folder.
*
* Default: ${rootProject.dir}/../node_modules/@react-native/codegen
*/
val codegenDir: DirectoryProperty =
objects.directoryProperty().convention(root.dir("node_modules/@react-native/codegen"))
/**
* The root directory for all JS files for the app.
*
* Default: the parent folder of the `/android` folder.
*/
val jsRootDir: DirectoryProperty = objects.directoryProperty()
/**
* The library name that will be used for the codegen artifacts.
*
* Default: <UpperCamelVersionOfProjectPath>Spec (e.g. for :example:project it will be
* ExampleProjectSpec).
*/
val libraryName: Property<String> =
objects.property(String::class.java).convention(projectPathToLibraryName(project.path))
/**
* Java package name to use for any codegen artifacts produced during build time. Default:
* com.facebook.fbreact.specs
*/
val codegenJavaPackageName: Property<String> =
objects.property(String::class.java).convention("com.facebook.fbreact.specs")
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.facebook.react.internal.PrivateReactExtension
import com.facebook.react.tasks.GenerateCodegenArtifactsTask
import com.facebook.react.tasks.GenerateCodegenSchemaTask
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts
import com.facebook.react.utils.AgpConfiguratorUtils.configureNamespaceForLibraries
import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
import com.facebook.react.utils.DependencyUtils.configureDependencies
import com.facebook.react.utils.DependencyUtils.configureRepositories
import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings
import com.facebook.react.utils.JdkConfiguratorUtils.configureJavaToolChains
import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk
import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson
import com.facebook.react.utils.ProjectUtils.shouldWarnIfNewArchFlagIsSetInPrealpha
import com.facebook.react.utils.findPackageJsonFile
import java.io.File
import kotlin.system.exitProcess
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.internal.jvm.Jvm
class ReactPlugin : Plugin<Project> {
override fun apply(project: Project) {
checkJvmVersion(project)
val extension = project.extensions.create("react", ReactExtension::class.java, project)
checkIfNewArchFlagIsSet(project, extension)
// We register a private extension on the rootProject so that project wide configs
// like codegen config can be propagated from app project to libraries.
val rootExtension =
project.rootProject.extensions.findByType(PrivateReactExtension::class.java)
?: project.rootProject.extensions.create(
"privateReact", PrivateReactExtension::class.java, project)
// App Only Configuration
project.pluginManager.withPlugin("com.android.application") {
// We wire the root extension with the values coming from the app (either user populated or
// defaults).
rootExtension.root.set(extension.root)
rootExtension.reactNativeDir.set(extension.reactNativeDir)
rootExtension.codegenDir.set(extension.codegenDir)
rootExtension.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs)
project.afterEvaluate {
val reactNativeDir = extension.reactNativeDir.get().asFile
val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties")
val versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile)
val versionString = versionAndGroupStrings.first
val groupString = versionAndGroupStrings.second
configureDependencies(project, versionString, groupString)
configureRepositories(project, reactNativeDir)
}
configureReactNativeNdk(project, extension)
configureBuildConfigFieldsForApp(project, extension)
configureDevPorts(project)
configureBackwardCompatibilityReactMap(project)
configureJavaToolChains(project)
project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
onVariants(selector().all()) { variant ->
project.configureReactTasks(variant = variant, config = extension)
}
}
configureCodegen(project, extension, rootExtension, isLibrary = false)
}
// Library Only Configuration
configureBuildConfigFieldsForLibraries(project)
configureNamespaceForLibraries(project)
project.pluginManager.withPlugin("com.android.library") {
configureCodegen(project, extension, rootExtension, isLibrary = true)
}
}
private fun checkJvmVersion(project: Project) {
val jvmVersion = Jvm.current()?.javaVersion?.majorVersion
if ((jvmVersion?.toIntOrNull() ?: 0) <= 16) {
project.logger.error(
"""
********************************************************************************
ERROR: requires JDK17 or higher.
Incompatible major version detected: '$jvmVersion'
********************************************************************************
"""
.trimIndent())
exitProcess(1)
}
}
private fun checkIfNewArchFlagIsSet(project: Project, extension: ReactExtension) {
if (project.shouldWarnIfNewArchFlagIsSetInPrealpha(extension)) {
project.logger.warn(
"""
********************************************************************************
WARNING: This version of React Native is ignoring the `newArchEnabled` flag you set. Please set it to true or remove it to suppress this warning.
********************************************************************************
"""
.trimIndent())
}
}
/** This function sets up `react-native-codegen` in our Gradle plugin. */
@Suppress("UnstableApiUsage")
private fun configureCodegen(
project: Project,
localExtension: ReactExtension,
rootExtension: PrivateReactExtension,
isLibrary: Boolean
) {
// First, we set up the output dir for the codegen.
val generatedSrcDir: Provider<Directory> =
project.layout.buildDirectory.dir("generated/source/codegen")
// We specify the default value (convention) for jsRootDir.
// It's the root folder for apps (so ../../ from the Gradle project)
// and the package folder for library (so ../ from the Gradle project)
if (isLibrary) {
localExtension.jsRootDir.convention(project.layout.projectDirectory.dir("../"))
} else {
localExtension.jsRootDir.convention(localExtension.root)
}
// We create the task to produce schema from JS files.
val generateCodegenSchemaTask =
project.tasks.register(
"generateCodegenSchemaFromJavaScript", GenerateCodegenSchemaTask::class.java) { it ->
it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
it.codegenDir.set(rootExtension.codegenDir)
it.generatedSrcDir.set(generatedSrcDir)
// We're reading the package.json at configuration time to properly feed
// the `jsRootDir` @Input property of this task & the onlyIf. Therefore, the
// parsePackageJson should be invoked inside this lambda.
val packageJson = findPackageJsonFile(project, rootExtension.root)
val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }
val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir
val includesGeneratedCode =
parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
if (jsSrcsDirInPackageJson != null) {
it.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson))
} else {
it.jsRootDir.set(localExtension.jsRootDir)
}
val needsCodegenFromPackageJson =
project.needsCodegenFromPackageJson(rootExtension.root)
it.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode }
}
// We create the task to generate Java code from schema.
val generateCodegenArtifactsTask =
project.tasks.register(
"generateCodegenArtifactsFromSchema", GenerateCodegenArtifactsTask::class.java) {
it.dependsOn(generateCodegenSchemaTask)
it.reactNativeDir.set(rootExtension.reactNativeDir)
it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
it.generatedSrcDir.set(generatedSrcDir)
it.packageJsonFile.set(findPackageJsonFile(project, rootExtension.root))
it.codegenJavaPackageName.set(localExtension.codegenJavaPackageName)
it.libraryName.set(localExtension.libraryName)
// Please note that appNeedsCodegen is triggering a read of the package.json at
// configuration time as we need to feed the onlyIf condition of this task.
// Therefore, the appNeedsCodegen needs to be invoked inside this lambda.
val needsCodegenFromPackageJson =
project.needsCodegenFromPackageJson(rootExtension.root)
val packageJson = findPackageJsonFile(project, rootExtension.root)
val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }
val includesGeneratedCode =
parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
it.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode }
}
// We update the android configuration to include the generated sources.
// This equivalent to this DSL:
//
// android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } }
project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile)
}
// `preBuild` is one of the base tasks automatically registered by AGP.
// This will invoke the codegen before compiling the entire project.
project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask)
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* Gradle plugin applied to the `android/build.gradle` file.
*
* This plugin allows to specify project wide configurations that can be applied to both apps and
* libraries before they're evaluated.
*/
class ReactRootProjectPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.subprojects {
// As the :app project (i.e. ReactPlugin) configures both namespaces and JVM toolchains
// for libraries, its evaluation must happen before the libraries' evaluation.
// Eventually the configuration of namespace/JVM toolchain can be moved inside this plugin.
if (it.path != ":app") {
it.evaluationDependsOn(":app")
}
}
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react
import com.android.build.api.variant.Variant
import com.facebook.react.tasks.BundleHermesCTask
import com.facebook.react.utils.KotlinStdlibCompatUtils.capitalizeCompat
import com.facebook.react.utils.NdkConfiguratorUtils.configureJsEnginePackagingOptions
import com.facebook.react.utils.NdkConfiguratorUtils.configureNewArchPackagingOptions
import com.facebook.react.utils.ProjectUtils.isHermesEnabled
import com.facebook.react.utils.detectedCliFile
import com.facebook.react.utils.detectedEntryFile
import java.io.File
import org.gradle.api.Project
@Suppress("SpreadOperator", "UnstableApiUsage")
internal fun Project.configureReactTasks(variant: Variant, config: ReactExtension) {
val targetName = variant.name.capitalizeCompat()
val targetPath = variant.name
val buildDir = layout.buildDirectory.get().asFile
// Resources: generated/assets/react/<variant>/index.android.bundle
val resourcesDir = File(buildDir, "generated/res/react/$targetPath")
// Bundle: generated/assets/react/<variant>/index.android.bundle
val jsBundleDir = File(buildDir, "generated/assets/react/$targetPath")
// Sourcemap: generated/sourcemaps/react/<variant>/index.android.bundle.map
val jsSourceMapsDir = File(buildDir, "generated/sourcemaps/react/$targetPath")
// Intermediate packager:
// intermediates/sourcemaps/react/<variant>/index.android.bundle.packager.map
// Intermediate compiler:
// intermediates/sourcemaps/react/<variant>/index.android.bundle.compiler.map
val jsIntermediateSourceMapsDir = File(buildDir, "intermediates/sourcemaps/react/$targetPath")
// The location of the cli.js file for React Native
val cliFile = detectedCliFile(config)
val isHermesEnabledInProject = project.isHermesEnabled
val isHermesEnabledInThisVariant =
if (config.enableHermesOnlyInVariants.get().isNotEmpty()) {
config.enableHermesOnlyInVariants.get().contains(variant.name) && isHermesEnabledInProject
} else {
isHermesEnabledInProject
}
val isDebuggableVariant =
config.debuggableVariants.get().any { it.equals(variant.name, ignoreCase = true) }
configureNewArchPackagingOptions(project, config, variant)
configureJsEnginePackagingOptions(config, variant, isHermesEnabledInThisVariant)
if (!isDebuggableVariant) {
val entryFileEnvVariable = System.getenv("ENTRY_FILE")
val bundleTask =
tasks.register("createBundle${targetName}JsAndAssets", BundleHermesCTask::class.java) {
it.root.set(config.root)
it.nodeExecutableAndArgs.set(config.nodeExecutableAndArgs)
it.cliFile.set(cliFile)
it.bundleCommand.set(config.bundleCommand)
it.entryFile.set(detectedEntryFile(config, entryFileEnvVariable))
it.extraPackagerArgs.set(config.extraPackagerArgs)
it.bundleConfig.set(config.bundleConfig)
it.bundleAssetName.set(config.bundleAssetName)
it.jsBundleDir.set(jsBundleDir)
it.resourcesDir.set(resourcesDir)
it.hermesEnabled.set(isHermesEnabledInThisVariant)
it.minifyEnabled.set(!isHermesEnabledInThisVariant)
it.devEnabled.set(false)
it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir)
it.jsSourceMapsDir.set(jsSourceMapsDir)
it.hermesCommand.set(config.hermesCommand)
it.hermesFlags.set(config.hermesFlags)
it.reactNativeDir.set(config.reactNativeDir)
}
variant.sources.res?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::resourcesDir)
variant.sources.assets?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::jsBundleDir)
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.internal
import javax.inject.Inject
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty
/**
* A private extension we set on the rootProject to make easier to share values at execution time
* between app project and library project.
*
* Specifically, the [codegenDir], [reactNativeDir] and other properties should be provided by apps
* (for setups like a monorepo which are app specific) and libraries should honor those values.
*
* Users are not supposed to access directly this extension from their build.gradle file.
*/
abstract class PrivateReactExtension @Inject constructor(project: Project) {
private val objects = project.objects
val root: DirectoryProperty =
objects
.directoryProperty()
.convention(
// This is the default for the project root if the users hasn't specified anything.
// If the project is called "react-native-github" or "react-native-build-from-source"
// - We're inside the Github Repo -> root is defined by RN Tester (so no default
// needed)
// - We're inside an includedBuild as we're performing a build from source
// (then we're inside `node_modules/react-native`, so default should be ../../)
// If the project is called in any other name
// - We're inside a user project, so inside the ./android folder. Default should be
// ../
// User can always override this default by setting a `root =` inside the template.
if (project.rootProject.name == "react-native-github" ||
project.rootProject.name == "react-native-build-from-source") {
project.rootProject.layout.projectDirectory.dir("../../")
} else {
project.rootProject.layout.projectDirectory.dir("../")
})
val reactNativeDir: DirectoryProperty =
objects.directoryProperty().convention(root.dir("node_modules/react-native"))
val nodeExecutableAndArgs: ListProperty<String> =
objects.listProperty(String::class.java).convention(listOf("node"))
val codegenDir: DirectoryProperty =
objects.directoryProperty().convention(root.dir("node_modules/@react-native/codegen"))
}

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.model
data class ModelCodegenConfig(
val name: String?,
val type: String?,
val jsSrcsDir: String?,
val android: ModelCodegenConfigAndroid?,
val includesGeneratedCode: Boolean?,
)

View File

@@ -0,0 +1,10 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.model
data class ModelCodegenConfigAndroid(val javaPackageName: String?)

View File

@@ -0,0 +1,10 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.model
data class ModelPackageJson(val version: String, val codegenConfig: ModelCodegenConfig?)

View File

@@ -0,0 +1,199 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks
import com.facebook.react.utils.Os.cliPath
import com.facebook.react.utils.detectOSAwareHermesCommand
import com.facebook.react.utils.moveTo
import com.facebook.react.utils.windowsAwareCommandLine
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileTree
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
abstract class BundleHermesCTask : DefaultTask() {
init {
group = "react"
}
@get:Internal abstract val root: DirectoryProperty
@get:InputFiles
val sources: ConfigurableFileTree =
project.fileTree(root) {
it.include("**/*.js")
it.include("**/*.jsx")
it.include("**/*.ts")
it.include("**/*.tsx")
it.exclude("**/android/**/*")
it.exclude("**/ios/**/*")
it.exclude("**/build/**/*")
it.exclude("**/node_modules/**/*")
}
@get:Input abstract val nodeExecutableAndArgs: ListProperty<String>
@get:InputFile abstract val cliFile: RegularFileProperty
@get:Internal abstract val reactNativeDir: DirectoryProperty
@get:Input abstract val bundleCommand: Property<String>
@get:InputFile abstract val entryFile: RegularFileProperty
@get:InputFile @get:Optional abstract val bundleConfig: RegularFileProperty
@get:Input abstract val bundleAssetName: Property<String>
@get:Input abstract val minifyEnabled: Property<Boolean>
@get:Input abstract val hermesEnabled: Property<Boolean>
@get:Input abstract val devEnabled: Property<Boolean>
@get:Input abstract val extraPackagerArgs: ListProperty<String>
@get:Input abstract val hermesCommand: Property<String>
@get:Input abstract val hermesFlags: ListProperty<String>
@get:OutputDirectory abstract val jsBundleDir: DirectoryProperty
@get:OutputDirectory abstract val resourcesDir: DirectoryProperty
@get:OutputDirectory abstract val jsIntermediateSourceMapsDir: RegularFileProperty
@get:OutputDirectory abstract val jsSourceMapsDir: DirectoryProperty
@TaskAction
fun run() {
jsBundleDir.get().asFile.mkdirs()
resourcesDir.get().asFile.mkdirs()
jsIntermediateSourceMapsDir.get().asFile.mkdirs()
jsSourceMapsDir.get().asFile.mkdirs()
val bundleAssetFilename = bundleAssetName.get()
val bundleFile = File(jsBundleDir.get().asFile, bundleAssetFilename)
val packagerSourceMap = resolvePackagerSourceMapFile(bundleAssetFilename)
val bundleCommand = getBundleCommand(bundleFile, packagerSourceMap)
runCommand(bundleCommand)
if (hermesEnabled.get()) {
val detectedHermesCommand = detectOSAwareHermesCommand(root.get().asFile, hermesCommand.get())
val bytecodeFile = File("${bundleFile}.hbc")
val outputSourceMap = resolveOutputSourceMap(bundleAssetFilename)
val compilerSourceMap = resolveCompilerSourceMap(bundleAssetFilename)
val hermesCommand = getHermescCommand(detectedHermesCommand, bytecodeFile, bundleFile)
runCommand(hermesCommand)
bytecodeFile.moveTo(bundleFile)
if (hermesFlags.get().contains("-output-source-map")) {
val hermesTempSourceMapFile = File("$bytecodeFile.map")
hermesTempSourceMapFile.moveTo(compilerSourceMap)
val reactNativeDir = reactNativeDir.get().asFile
val composeScriptFile = File(reactNativeDir, "scripts/compose-source-maps.js")
val composeSourceMapsCommand =
getComposeSourceMapsCommand(
composeScriptFile, packagerSourceMap, compilerSourceMap, outputSourceMap)
runCommand(composeSourceMapsCommand)
}
}
}
internal fun resolvePackagerSourceMapFile(bundleAssetName: String) =
if (hermesEnabled.get()) {
File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.packager.map")
} else {
resolveOutputSourceMap(bundleAssetName)
}
internal fun resolveOutputSourceMap(bundleAssetName: String) =
File(jsSourceMapsDir.get().asFile, "$bundleAssetName.map")
internal fun resolveCompilerSourceMap(bundleAssetName: String) =
File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.compiler.map")
private fun runCommand(command: List<Any>) {
project.exec {
it.workingDir(root.get().asFile)
it.commandLine(command)
}
}
internal fun getBundleCommand(bundleFile: File, sourceMapFile: File): List<Any> {
val rootFile = root.get().asFile
val commandLine =
mutableListOf<String>().apply {
addAll(nodeExecutableAndArgs.get())
add(cliFile.get().asFile.cliPath(rootFile))
add(bundleCommand.get())
add("--platform")
add("android")
add("--dev")
add(devEnabled.get().toString())
add("--reset-cache")
add("--entry-file")
add(entryFile.get().asFile.cliPath(rootFile))
add("--bundle-output")
add(bundleFile.cliPath(rootFile))
add("--assets-dest")
add(resourcesDir.get().asFile.cliPath(rootFile))
add("--sourcemap-output")
add(sourceMapFile.cliPath(rootFile))
if (bundleConfig.isPresent) {
add("--config")
add(bundleConfig.get().asFile.cliPath(rootFile))
}
add("--minify")
add(minifyEnabled.get().toString())
addAll(extraPackagerArgs.get())
add("--verbose")
}
return windowsAwareCommandLine(commandLine)
}
internal fun getHermescCommand(
hermesCommand: String,
bytecodeFile: File,
bundleFile: File
): List<Any> {
val rootFile = root.get().asFile
return windowsAwareCommandLine(
hermesCommand,
"-emit-binary",
"-max-diagnostic-width=80",
"-out",
bytecodeFile.cliPath(rootFile),
bundleFile.cliPath(rootFile),
*hermesFlags.get().toTypedArray())
}
internal fun getComposeSourceMapsCommand(
composeScript: File,
packagerSourceMap: File,
compilerSourceMap: File,
outputSourceMap: File
): List<Any> {
val rootFile = root.get().asFile
return windowsAwareCommandLine(
*nodeExecutableAndArgs.get().toTypedArray(),
composeScript.cliPath(rootFile),
packagerSourceMap.cliPath(rootFile),
compilerSourceMap.cliPath(rootFile),
"-o",
outputSourceMap.cliPath(rootFile))
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks
import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.Os.cliPath
import com.facebook.react.utils.windowsAwareCommandLine
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Exec
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
abstract class GenerateCodegenArtifactsTask : Exec() {
@get:Internal abstract val reactNativeDir: DirectoryProperty
@get:Internal abstract val generatedSrcDir: DirectoryProperty
@get:InputFile abstract val packageJsonFile: RegularFileProperty
@get:Input abstract val nodeExecutableAndArgs: ListProperty<String>
@get:Input abstract val codegenJavaPackageName: Property<String>
@get:Input abstract val libraryName: Property<String>
@get:InputFile
val generatedSchemaFile: Provider<RegularFile> = generatedSrcDir.file("schema.json")
@get:OutputDirectory val generatedJavaFiles: Provider<Directory> = generatedSrcDir.dir("java")
@get:OutputDirectory val generatedJniFiles: Provider<Directory> = generatedSrcDir.dir("jni")
override fun exec() {
val (resolvedLibraryName, resolvedCodegenJavaPackageName) = resolveTaskParameters()
setupCommandLine(resolvedLibraryName, resolvedCodegenJavaPackageName)
super.exec()
}
internal fun resolveTaskParameters(): Pair<String, String> {
val parsedPackageJson =
if (packageJsonFile.isPresent && packageJsonFile.get().asFile.exists()) {
JsonUtils.fromPackageJson(packageJsonFile.get().asFile)
} else {
null
}
val resolvedLibraryName = parsedPackageJson?.codegenConfig?.name ?: libraryName.get()
val resolvedCodegenJavaPackageName =
parsedPackageJson?.codegenConfig?.android?.javaPackageName ?: codegenJavaPackageName.get()
return resolvedLibraryName to resolvedCodegenJavaPackageName
}
internal fun setupCommandLine(libraryName: String, codegenJavaPackageName: String) {
val workingDir = project.projectDir
commandLine(
windowsAwareCommandLine(
*nodeExecutableAndArgs.get().toTypedArray(),
reactNativeDir.file("scripts/generate-specs-cli.js").get().asFile.cliPath(workingDir),
"--platform",
"android",
"--schemaPath",
generatedSchemaFile.get().asFile.cliPath(workingDir),
"--outputDir",
generatedSrcDir.get().asFile.cliPath(workingDir),
"--libraryName",
libraryName,
"--javaPackageName",
codegenJavaPackageName))
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks
import com.facebook.react.utils.Os.cliPath
import com.facebook.react.utils.windowsAwareCommandLine
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
/**
* A task that will collect all the *.js files inside the provided [jsRootDir] and will run the
* `combine-js-to-schema-cli.js` on top of it (from `react-native-codegen`). The output is a
* `schema.json` file that contains an intermediate representation of the code to be generated.
*/
abstract class GenerateCodegenSchemaTask : Exec() {
@get:Internal abstract val jsRootDir: DirectoryProperty
@get:Internal abstract val codegenDir: DirectoryProperty
@get:Internal abstract val generatedSrcDir: DirectoryProperty
@get:Input abstract val nodeExecutableAndArgs: ListProperty<String>
@get:InputFiles
val jsInputFiles =
project.fileTree(jsRootDir) {
it.include("**/*.js")
it.include("**/*.ts")
// We want to exclude the build directory, to don't pick them up for execution avoidance.
it.exclude("**/build/**/*")
}
@get:OutputFile
val generatedSchemaFile: Provider<RegularFile> = generatedSrcDir.file("schema.json")
override fun exec() {
wipeOutputDir()
setupCommandLine()
super.exec()
}
internal fun wipeOutputDir() {
generatedSrcDir.asFile.get().apply {
deleteRecursively()
mkdirs()
}
}
internal fun setupCommandLine() {
val workingDir = project.projectDir
commandLine(
windowsAwareCommandLine(
*nodeExecutableAndArgs.get().toTypedArray(),
codegenDir
.file("lib/cli/combine/combine-js-to-schema-cli.js")
.get()
.asFile
.cliPath(workingDir),
"--platform",
"android",
"--exclude",
"NativeSampleTurboModule",
generatedSchemaFile.get().asFile.cliPath(workingDir),
jsRootDir.asFile.get().cliPath(workingDir),
))
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks.internal
import com.facebook.react.utils.Os.unixifyPath
import com.facebook.react.utils.windowsAwareBashCommandLine
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileTree
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
/**
* A Task that will call the `scripts/oss/build.sh` script to trigger the creation of the codegen
* lib artifacts.
*
* NOTE: This task is required when using react-native-codegen from source, instead of npm.
*/
abstract class BuildCodegenCLITask : Exec() {
@get:Internal abstract val codegenDir: DirectoryProperty
@get:Internal abstract val bashWindowsHome: Property<String>
@get:InputFiles
val inputFiles: FileTree = project.fileTree(codegenDir) { it.include("src/**/*.js") }
@get:OutputFiles
val outputFiles: FileTree =
project.fileTree(codegenDir) {
it.include("lib/**/*.js")
it.include("lib/**/*.js.flow")
}
override fun exec() {
commandLine(
windowsAwareBashCommandLine(
codegenDir.asFile.get().canonicalPath.unixifyPath().plus(BUILD_SCRIPT_PATH),
bashWindowsHome = bashWindowsHome.orNull,
))
super.exec()
}
companion object {
private const val BUILD_SCRIPT_PATH = "/scripts/oss/build.sh"
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks.internal
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
/**
* A task that takes care of extracting Boost from a source folder/zip and preparing it to be
* consumed by the NDK
*/
abstract class PrepareBoostTask : DefaultTask() {
@get:InputFiles abstract val boostPath: ConfigurableFileCollection
@get:Input abstract val boostVersion: Property<String>
@get:OutputDirectory abstract val outputDir: DirectoryProperty
@TaskAction
fun taskAction() {
project.copy { it ->
it.from(boostPath)
it.from(project.file("src/main/jni/third-party/boost"))
it.include(
"CMakeLists.txt",
"boost_${boostVersion.get()}/boost/**/*.hpp",
"boost/boost/**/*.hpp",
"asm/**/*.S")
it.includeEmptyDirs = false
it.into(outputDir)
}
File(outputDir.asFile.get(), "boost").apply {
renameTo(File(parentFile, "boost_${boostVersion.get()}"))
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks.internal
import java.io.File
import org.apache.tools.ant.filters.ReplaceTokens
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
/**
* A task that takes care of extracting Glog from a source folder/zip and preparing it to be
* consumed by the NDK. This task will also take care of applying the mapping for Glog parameters.
*/
abstract class PrepareGlogTask : DefaultTask() {
@get:InputFiles abstract val glogPath: ConfigurableFileCollection
@get:Input abstract val glogVersion: Property<String>
@get:OutputDirectory abstract val outputDir: DirectoryProperty
@TaskAction
fun taskAction() {
project.copy {
it.from(glogPath)
it.from(project.file("src/main/jni/third-party/glog/"))
it.include("glog-${glogVersion.get()}/src/**/*", "CMakeLists.txt", "config.h")
it.duplicatesStrategy = DuplicatesStrategy.WARN
it.includeEmptyDirs = false
it.filesMatching("**/*.h.in") { matchedFile ->
matchedFile.filter(
mapOf(
"tokens" to
mapOf(
"ac_cv_have_unistd_h" to "1",
"ac_cv_have_stdint_h" to "1",
"ac_cv_have_systypes_h" to "1",
"ac_cv_have_inttypes_h" to "1",
"ac_cv_have_libgflags" to "0",
"ac_google_start_namespace" to "namespace google {",
"ac_cv_have_uint16_t" to "1",
"ac_cv_have_u_int16_t" to "1",
"ac_cv_have___uint16" to "0",
"ac_google_end_namespace" to "}",
"ac_cv_have___builtin_expect" to "1",
"ac_google_namespace" to "google",
"ac_cv___attribute___noinline" to "__attribute__ ((noinline))",
"ac_cv___attribute___noreturn" to "__attribute__ ((noreturn))",
"ac_cv___attribute___printf_4_5" to
"__attribute__((__format__ (__printf__, 4, 5)))")),
ReplaceTokens::class.java)
matchedFile.path = (matchedFile.name.removeSuffix(".in"))
}
it.into(outputDir)
}
val exportedDir = File(outputDir.asFile.get(), "exported/glog/").apply { mkdirs() }
project.copy {
it.from(outputDir)
it.include(
"stl_logging.h",
"logging.h",
"raw_logging.h",
"vlog_is_on.h",
"**/src/glog/log_severity.h")
it.eachFile { file -> file.path = file.name }
it.includeEmptyDirs = false
it.into(exportedDir)
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks.internal
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
/**
* A task that takes care of unbundling JSC and preparing it for be consumed by the Android NDK.
* Specifically it will unbundle shared libs, headers and will copy over the Makefile from
* `src/main/jni/third-party/jsc/`
*/
abstract class PrepareJSCTask : DefaultTask() {
@get:Input abstract val jscPackagePath: Property<String>
@get:OutputDirectory abstract val outputDir: DirectoryProperty
@TaskAction
fun taskAction() {
if (!jscPackagePath.isPresent || jscPackagePath.orNull == null) {
error("Could not find the jsc-android npm package")
}
val jscDist = File(jscPackagePath.get(), "dist")
if (!jscDist.exists()) {
error("The jsc-android npm package is missing its \"dist\" directory")
}
val jscAAR =
project.fileTree(jscDist).matching { it.include("**/android-jsc/**/*.aar") }.singleFile
val soFiles = project.zipTree(jscAAR).matching { it.include("**/*.so") }
val headerFiles = project.fileTree(jscDist).matching { it.include("**/include/*.h") }
project.copy { it ->
it.from(soFiles)
it.from(headerFiles)
it.from(project.file("src/main/jni/third-party/jsc/CMakeLists.txt"))
it.filesMatching("**/*.h") { it.path = "JavaScriptCore/${it.name}" }
it.includeEmptyDirs = false
it.into(outputDir)
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks.internal
import com.facebook.react.tasks.internal.utils.PrefabPreprocessingEntry
import java.io.File
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
/**
* A task that takes care of copying headers and filtering them so that can be consumed by the
* Prefab protocol. This task handles also the header prefixes.
*
* It currently filters out some of the Boost headers as they're not used by React Native and are
* resulting in bigger .aar (250Mb+).
*
* You should provide in input a list fo [PrefabPreprocessingEntry] that will be used by this task
* to do the necessary copy operations.
*/
abstract class PreparePrefabHeadersTask : DefaultTask() {
@get:Input abstract val input: ListProperty<PrefabPreprocessingEntry>
@get:OutputDirectory abstract val outputDir: DirectoryProperty
@get:Inject abstract val fs: FileSystemOperations
@TaskAction
fun taskAction() {
input.get().forEach { (libraryName, pathToPrefixCouples) ->
val outputFolder: RegularFile = outputDir.file(libraryName).get()
pathToPrefixCouples.forEach { (headerPath, headerPrefix) ->
fs.copy {
it.from(headerPath)
it.include("**/*.h")
it.exclude("**/*.cpp")
it.exclude("**/*.txt")
// We don't want to copy all the boost headers as they are 250Mb+
it.include("boost/config.hpp")
it.include("boost/config/**/*.hpp")
it.include("boost/core/*.hpp")
it.include("boost/detail/workaround.hpp")
it.include("boost/operators.hpp")
it.include("boost/preprocessor/**/*.hpp")
// Headers needed for exposing rrc_text and rrc_textinput
it.include("boost/container_hash/**/*.hpp")
it.include("boost/detail/**/*.hpp")
it.include("boost/intrusive/**/*.hpp")
it.include("boost/iterator/**/*.hpp")
it.include("boost/move/**/*.hpp")
it.include("boost/mpl/**/*.hpp")
it.include("boost/mp11/**/*.hpp")
it.include("boost/describe/**/*.hpp")
it.include("boost/preprocessor/**/*.hpp")
it.include("boost/type_traits/**/*.hpp")
it.include("boost/utility/**/*.hpp")
it.include("boost/detail/workaround.hpp")
it.include("boost/assert.hpp")
it.include("boost/static_assert.hpp")
it.include("boost/cstdint.hpp")
it.include("boost/operators.hpp")
it.include("boost/config.hpp")
it.include("boost/utility.hpp")
it.include("boost/version.hpp")
it.into(File(outputFolder.asFile, headerPrefix))
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.tasks.internal.utils
import java.io.Serializable
/**
* This data class represents an entry that can be consumed by the [PreparePrefabHeadersTask].
*
* @param libraryName The name of the library that you're preparing for Prefab
* @param pathToPrefixCouples A list of pairs Path to Header prefix. You can use this list to supply
* a list of paths that you want to be considered for prefab. Each path can specify an header
* prefix that will be used by prefab to re-created the header layout.
*/
data class PrefabPreprocessingEntry(
val libraryName: String,
val pathToPrefixCouples: List<Pair<String, String>>,
) : Serializable {
constructor(
libraryName: String,
pathToPrefixCouple: Pair<String, String>
) : this(libraryName, listOf(pathToPrefixCouple))
}

View File

@@ -0,0 +1,106 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.LibraryExtension
import com.facebook.react.ReactExtension
import com.facebook.react.utils.ProjectUtils.isHermesEnabled
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
import java.io.File
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.plugins.AppliedPlugin
import org.w3c.dom.Element
@Suppress("UnstableApiUsage")
internal object AgpConfiguratorUtils {
fun configureBuildConfigFieldsForApp(project: Project, extension: ReactExtension) {
val action =
Action<AppliedPlugin> {
project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
ext.buildFeatures.buildConfig = true
ext.defaultConfig.buildConfigField(
"boolean",
"IS_NEW_ARCHITECTURE_ENABLED",
project.isNewArchEnabled(extension).toString())
ext.defaultConfig.buildConfigField(
"boolean", "IS_HERMES_ENABLED", project.isHermesEnabled.toString())
}
}
project.pluginManager.withPlugin("com.android.application", action)
project.pluginManager.withPlugin("com.android.library", action)
}
fun configureBuildConfigFieldsForLibraries(appProject: Project) {
appProject.rootProject.allprojects { subproject ->
subproject.pluginManager.withPlugin("com.android.library") {
subproject.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
ext.buildFeatures.buildConfig = true
}
}
}
}
fun configureDevPorts(project: Project) {
val devServerPort =
project.properties["reactNativeDevServerPort"]?.toString() ?: DEFAULT_DEV_SERVER_PORT
val action =
Action<AppliedPlugin> {
project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
ext.defaultConfig.resValue("integer", "react_native_dev_server_port", devServerPort)
}
}
project.pluginManager.withPlugin("com.android.application", action)
project.pluginManager.withPlugin("com.android.library", action)
}
fun configureNamespaceForLibraries(appProject: Project) {
appProject.rootProject.allprojects { subproject ->
subproject.pluginManager.withPlugin("com.android.library") {
subproject.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
if (ext.namespace == null) {
val android = subproject.extensions.getByType(LibraryExtension::class.java)
val manifestFile = android.sourceSets.getByName("main").manifest.srcFile
manifestFile
.takeIf { it.exists() }
?.let { file ->
getPackageNameFromManifest(file)?.let { packageName ->
ext.namespace = packageName
}
}
}
}
}
}
}
}
const val DEFAULT_DEV_SERVER_PORT = "8081"
fun getPackageNameFromManifest(manifest: File): String? {
val factory: DocumentBuilderFactory = DocumentBuilderFactory.newInstance()
val builder: DocumentBuilder = factory.newDocumentBuilder()
try {
val xmlDocument = builder.parse(manifest)
val manifestElement = xmlDocument.getElementsByTagName("manifest").item(0) as? Element
val packageName = manifestElement?.getAttribute("package")
return if (packageName.isNullOrEmpty()) null else packageName
} catch (e: Exception) {
return null
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
import java.util.*
import org.gradle.api.Project
internal object BackwardCompatUtils {
fun configureBackwardCompatibilityReactMap(project: Project) {
if (project.extensions.extraProperties.has("react")) {
@Suppress("UNCHECKED_CAST")
val reactMap =
project.extensions.extraProperties.get("react") as? Map<String, Any?> ?: mapOf()
if (reactMap.isNotEmpty()) {
project.logger.error(
"""
********************************************************************************
ERROR: Using old project.ext.react configuration.
We identified that your project is using a old configuration block as:
project.ext.react = [
// ...
]
You should migrate to the new configuration:
react {
// ...
}
You can find documentation inside `android/app/build.gradle` on how to use it.
********************************************************************************
"""
.trimIndent())
}
}
// We set an empty react[] map so if a library is reading it, they will find empty values.
project.extensions.extraProperties.set("react", mapOf<String, String>())
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
import com.facebook.react.utils.PropertyUtils.DEFAULT_INTERNAL_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.INTERNAL_PUBLISHING_GROUP
import com.facebook.react.utils.PropertyUtils.INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO
import com.facebook.react.utils.PropertyUtils.INTERNAL_USE_HERMES_NIGHTLY
import com.facebook.react.utils.PropertyUtils.INTERNAL_VERSION_NAME
import java.io.File
import java.net.URI
import java.util.*
import org.gradle.api.Project
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
internal object DependencyUtils {
/**
* This method takes care of configuring the repositories{} block for both the app and all the 3rd
* party libraries which are auto-linked.
*/
fun configureRepositories(project: Project, reactNativeDir: File) {
project.rootProject.allprojects { eachProject ->
with(eachProject) {
if (hasProperty(INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO)) {
val mavenLocalRepoPath = property(INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO) as String
mavenRepoFromURI(File(mavenLocalRepoPath).toURI())
}
// We add the snapshot for users on nightlies.
mavenRepoFromUrl("https://oss.sonatype.org/content/repositories/snapshots/")
repositories.mavenCentral { repo ->
// We don't want to fetch JSC from Maven Central as there are older versions there.
repo.content { it.excludeModule("org.webkit", "android-jsc") }
// If the user provided a react.internal.mavenLocalRepo, do not attempt to load
// anything from Maven Central that is react related.
if (hasProperty(INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO)) {
repo.content { it.excludeGroup("com.facebook.react") }
}
}
// Android JSC is installed from npm
mavenRepoFromURI(File(reactNativeDir, "../jsc-android/dist").toURI())
repositories.google()
mavenRepoFromUrl("https://www.jitpack.io")
}
}
}
/**
* This method takes care of configuring the resolution strategy for both the app and all the 3rd
* party libraries which are auto-linked. Specifically it takes care of:
* - Forcing the react-android/hermes-android version to the one specified in the package.json
* - Substituting `react-native` with `react-android` and `hermes-engine` with `hermes-android`.
*/
fun configureDependencies(
project: Project,
versionString: String,
groupString: String = DEFAULT_INTERNAL_PUBLISHING_GROUP
) {
if (versionString.isBlank()) return
project.rootProject.allprojects { eachProject ->
eachProject.configurations.all { configuration ->
// Here we set a dependencySubstitution for both react-native and hermes-engine as those
// coordinates are voided due to https://github.com/facebook/react-native/issues/35210
// This allows users to import libraries that are still using
// implementation("com.facebook.react:react-native:+") and resolve the right dependency.
configuration.resolutionStrategy.dependencySubstitution {
getDependencySubstitutions(versionString, groupString).forEach { (module, dest, reason) ->
it.substitute(it.module(module)).using(it.module(dest)).because(reason)
}
}
configuration.resolutionStrategy.force(
"${groupString}:react-android:${versionString}",
)
if (!(eachProject.findProperty(INTERNAL_USE_HERMES_NIGHTLY) as? String).toBoolean()) {
// Contributors only: The hermes-engine version is forced only if the user has
// not opted into using nightlies for local development.
configuration.resolutionStrategy.force("${groupString}:hermes-android:${versionString}")
}
}
}
}
internal fun getDependencySubstitutions(
versionString: String,
groupString: String = DEFAULT_INTERNAL_PUBLISHING_GROUP
): List<Triple<String, String, String>> {
val dependencySubstitution = mutableListOf<Triple<String, String, String>>()
dependencySubstitution.add(
Triple(
"com.facebook.react:react-native",
"${groupString}:react-android:${versionString}",
"The react-native artifact was deprecated in favor of react-android due to https://github.com/facebook/react-native/issues/35210."))
dependencySubstitution.add(
Triple(
"com.facebook.react:hermes-engine",
"${groupString}:hermes-android:${versionString}",
"The hermes-engine artifact was deprecated in favor of hermes-android due to https://github.com/facebook/react-native/issues/35210."))
if (groupString != DEFAULT_INTERNAL_PUBLISHING_GROUP) {
dependencySubstitution.add(
Triple(
"com.facebook.react:react-android",
"${groupString}:react-android:${versionString}",
"The react-android dependency was modified to use the correct Maven group."))
dependencySubstitution.add(
Triple(
"com.facebook.react:hermes-android",
"${groupString}:hermes-android:${versionString}",
"The hermes-android dependency was modified to use the correct Maven group."))
}
return dependencySubstitution
}
fun readVersionAndGroupStrings(propertiesFile: File): Pair<String, String> {
val reactAndroidProperties = Properties()
propertiesFile.inputStream().use { reactAndroidProperties.load(it) }
val versionStringFromFile = (reactAndroidProperties[INTERNAL_VERSION_NAME] as? String).orEmpty()
// If on a nightly, we need to fetch the -SNAPSHOT artifact from Sonatype.
val versionString =
if (versionStringFromFile.startsWith("0.0.0") || "-nightly-" in versionStringFromFile) {
"$versionStringFromFile-SNAPSHOT"
} else {
versionStringFromFile
}
// Returns Maven group for repos using different group for Maven artifacts
val groupString =
reactAndroidProperties[INTERNAL_PUBLISHING_GROUP] as? String
?: DEFAULT_INTERNAL_PUBLISHING_GROUP
return Pair(versionString, groupString)
}
fun Project.mavenRepoFromUrl(url: String): MavenArtifactRepository =
project.repositories.maven { it.url = URI.create(url) }
fun Project.mavenRepoFromURI(uri: URI): MavenArtifactRepository =
project.repositories.maven { it.url = uri }
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
import java.io.File
internal fun File.moveTo(destination: File) {
copyTo(destination, overwrite = true)
delete()
}
internal fun File.recreateDir() {
deleteRecursively()
mkdirs()
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
import com.android.build.api.variant.AndroidComponentsExtension
import com.facebook.react.utils.PropertyUtils.INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT
import org.gradle.api.Action
import org.gradle.api.JavaVersion
import org.gradle.api.Project
import org.gradle.api.plugins.AppliedPlugin
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
internal object JdkConfiguratorUtils {
/**
* Function that takes care of configuring the JDK toolchain for all the projects projects. As we
* do decide the JDK version based on the AGP version that RNGP brings over, here we can safely
* configure the toolchain to 17.
*/
fun configureJavaToolChains(input: Project) {
// Check at the app level if react.internal.disableJavaVersionAlignment is set.
if (input.hasProperty(INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT)) {
return
}
input.rootProject.allprojects { project ->
// Allows every single module to set react.internal.disableJavaVersionAlignment also.
if (project.hasProperty(INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT)) {
return@allprojects
}
val action =
Action<AppliedPlugin> {
project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext
->
ext.compileOptions.sourceCompatibility = JavaVersion.VERSION_17
ext.compileOptions.targetCompatibility = JavaVersion.VERSION_17
}
}
project.pluginManager.withPlugin("com.android.application", action)
project.pluginManager.withPlugin("com.android.library", action)
project.pluginManager.withPlugin("org.jetbrains.kotlin.android") {
project.extensions.getByType(KotlinTopLevelExtension::class.java).jvmToolchain(17)
}
project.pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
project.extensions.getByType(KotlinTopLevelExtension::class.java).jvmToolchain(17)
}
}
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
import com.facebook.react.model.ModelPackageJson
import com.google.gson.Gson
import java.io.File
object JsonUtils {
private val gsonConverter = Gson()
fun fromPackageJson(input: File): ModelPackageJson? =
input.bufferedReader().use {
runCatching { gsonConverter.fromJson(it, ModelPackageJson::class.java) }.getOrNull()
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
import java.util.Locale
object KotlinStdlibCompatUtils {
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
fun String.lowercaseCompat(): String = (this as java.lang.String).toLowerCase(Locale.ROOT)
fun String.capitalizeCompat(): String =
if (isNotEmpty()) {
val firstChar = this[0]
val uppercaseChar = Character.toUpperCase(firstChar)
val restString = this@capitalizeCompat.substring(1)
uppercaseChar + restString
} else {
this
}
fun String.toBooleanStrictOrNullCompat(): Boolean? =
when (this) {
"true" -> true
"false" -> false
else -> null
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.Variant
import com.facebook.react.ReactExtension
import com.facebook.react.utils.ProjectUtils.getReactNativeArchitectures
import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
import java.io.File
import org.gradle.api.Project
internal object NdkConfiguratorUtils {
@Suppress("UnstableApiUsage")
fun configureReactNativeNdk(project: Project, extension: ReactExtension) {
project.pluginManager.withPlugin("com.android.application") {
project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
if (!project.isNewArchEnabled(extension)) {
// For Old Arch, we don't need to setup the NDK
return@finalizeDsl
}
// We enable prefab so users can consume .so/headers from ReactAndroid and hermes-engine
// .aar
ext.buildFeatures.prefab = true
// If the user has not provided a CmakeLists.txt path, let's provide
// the default one from the framework
if (ext.externalNativeBuild.cmake.path == null) {
ext.externalNativeBuild.cmake.path =
File(
extension.reactNativeDir.get().asFile,
"ReactAndroid/cmake-utils/default-app-setup/CMakeLists.txt")
}
// Parameters should be provided in an additive manner (do not override what
// the user provided, but allow for sensible defaults).
val cmakeArgs = ext.defaultConfig.externalNativeBuild.cmake.arguments
if (cmakeArgs.none { it.startsWith("-DPROJECT_BUILD_DIR") }) {
cmakeArgs.add("-DPROJECT_BUILD_DIR=${project.layout.buildDirectory.get().asFile}")
}
if (cmakeArgs.none { it.startsWith("-DREACT_ANDROID_DIR") }) {
cmakeArgs.add(
"-DREACT_ANDROID_DIR=${extension.reactNativeDir.file("ReactAndroid").get().asFile}")
}
if (cmakeArgs.none { it.startsWith("-DANDROID_STL") }) {
cmakeArgs.add("-DANDROID_STL=c++_shared")
}
// Due to the new NDK toolchain file, the C++ flags gets overridden between compilation
// units. This is causing some libraries to don't be compiled with -DANDROID and other
// crucial flags. This can be revisited once we bump to NDK 25/26
if (cmakeArgs.none { it.startsWith("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE") }) {
cmakeArgs.add("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON")
}
val architectures = project.getReactNativeArchitectures()
// abiFilters are split ABI are not compatible each other, so we set the abiFilters
// only if the user hasn't enabled the split abi feature.
if (architectures.isNotEmpty() && !ext.splits.abi.isEnable) {
ext.defaultConfig.ndk.abiFilters.addAll(architectures)
}
}
}
}
/**
* This method is used to configure the .so Packaging Options for the given variant. It will make
* sure we specify the correct .pickFirsts for all the .so files we are producing or that we're
* aware of as some of our dependencies are pulling them in.
*/
fun configureNewArchPackagingOptions(
project: Project,
extension: ReactExtension,
variant: Variant
) {
if (!project.isNewArchEnabled(extension)) {
// For Old Arch, we set a pickFirst only on libraries that we know are
// clashing with our direct dependencies (mainly FBJNI and Hermes).
variant.packaging.jniLibs.pickFirsts.addAll(
listOf(
"**/libfbjni.so",
"**/libc++_shared.so",
))
} else {
// We set some packagingOptions { pickFirst ... } for our users for libraries we own.
variant.packaging.jniLibs.pickFirsts.addAll(
listOf(
// This is the .so provided by FBJNI via prefab
"**/libfbjni.so",
// Those are prefab libraries we distribute via ReactAndroid
// Due to a bug in AGP, they fire a warning on console as both the JNI
// and the prefab .so files gets considered. See more on:
"**/libfabricjni.so",
"**/libfolly_runtime.so",
"**/libglog.so",
"**/libjsi.so",
"**/libreact_codegen_rncore.so",
"**/libreact_debug.so",
"**/libreact_nativemodule_core.so",
"**/libreact_newarchdefaults.so",
"**/libreact_cxxreactpackage.so",
"**/libreact_render_componentregistry.so",
"**/libreact_render_core.so",
"**/libreact_render_debug.so",
"**/libreact_render_graphics.so",
"**/libreact_render_imagemanager.so",
"**/libreact_render_mapbuffer.so",
"**/libreact_utils.so",
"**/librrc_image.so",
"**/librrc_legacyviewmanagerinterop.so",
"**/librrc_view.so",
"**/libruntimeexecutor.so",
"**/libturbomodulejsijni.so",
"**/libyoga.so",
// AGP will give priority of libc++_shared coming from App modules.
"**/libc++_shared.so",
))
}
}
/**
* This method is used to configure the .so Cleanup for the given variant. It takes care of
* cleaning up the .so files that are not needed for Hermes or JSC, given a specific variant.
*/
fun configureJsEnginePackagingOptions(
config: ReactExtension,
variant: Variant,
hermesEnabled: Boolean,
) {
if (config.enableSoCleanup.get()) {
val (excludes, includes) = getPackagingOptionsForVariant(hermesEnabled)
variant.packaging.jniLibs.excludes.addAll(excludes)
variant.packaging.jniLibs.pickFirsts.addAll(includes)
}
}
fun getPackagingOptionsForVariant(hermesEnabled: Boolean): Pair<List<String>, List<String>> {
val excludes = mutableListOf<String>()
val includes = mutableListOf<String>()
if (hermesEnabled) {
excludes.add("**/libjsc.so")
excludes.add("**/libjscexecutor.so")
includes.add("**/libhermes.so")
includes.add("**/libhermes_executor.so")
} else {
excludes.add("**/libhermes.so")
excludes.add("**/libhermes_executor.so")
includes.add("**/libjsc.so")
includes.add("**/libjscexecutor.so")
}
return excludes to includes
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat
import java.io.File
internal object Os {
fun isWindows(): Boolean =
System.getProperty("os.name")?.lowercaseCompat()?.contains("windows") ?: false
fun isMac(): Boolean = System.getProperty("os.name")?.lowercaseCompat()?.contains("mac") ?: false
fun isLinuxAmd64(): Boolean {
val osNameMatch = System.getProperty("os.name")?.lowercaseCompat()?.contains("linux") ?: false
val archMatch = System.getProperty("os.arch")?.lowercaseCompat()?.contains("amd64") ?: false
return osNameMatch && archMatch
}
fun String.unixifyPath() =
this.replace('\\', '/').replace(":", "").let {
if (!it.startsWith("/")) {
"/$it"
} else {
it
}
}
/**
* As Gradle doesn't support well path with spaces on Windows, we need to return relative path on
* Win. On Linux & Mac we'll default to return absolute path.
*/
fun File.cliPath(base: File): String =
if (isWindows()) {
this.relativeTo(base).path
} else {
absolutePath
}
}

View File

@@ -0,0 +1,232 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
@file:JvmName("PathUtils")
package com.facebook.react.utils
import com.facebook.react.ReactExtension
import com.facebook.react.model.ModelPackageJson
import com.facebook.react.utils.KotlinStdlibCompatUtils.capitalizeCompat
import com.facebook.react.utils.Os.cliPath
import java.io.File
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
/**
* Computes the entry file for React Native. The Algo follows this order:
* 1. The file pointed by the ENTRY_FILE env variable, if set.
* 2. The file provided by the `entryFile` config in the `reactApp` Gradle extension
* 3. The `index.android.js` file, if available.
* 4. Fallback to the `index.js` file.
*
* @param config The [ReactExtension] configured for this project
*/
internal fun detectedEntryFile(config: ReactExtension, envVariableOverride: String? = null): File =
detectEntryFile(
entryFile = config.entryFile.orNull?.asFile,
reactRoot = config.root.get().asFile,
envVariableOverride = envVariableOverride)
/**
* Computes the CLI file for React Native. The Algo follows this order:
* 1. The path provided by the `cliFile` config in the `react {}` Gradle extension
* 2. The output of `node --print "require.resolve('react-native/cli');"` if not failing.
* 3. The `node_modules/react-native/cli.js` file if exists
* 4. Fails otherwise
*/
internal fun detectedCliFile(config: ReactExtension): File =
detectCliFile(
reactNativeRoot = config.root.get().asFile,
preconfiguredCliFile = config.cliFile.asFile.orNull)
/**
* Computes the `hermesc` command location. The Algo follows this order:
* 1. The path provided by the `hermesCommand` config in the `react` Gradle extension
* 2. The file located in `node_modules/react-native/sdks/hermes/build/bin/hermesc`. This will be
* used if the user is building Hermes from source.
* 3. The file located in `node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc` where `%OS-BIN%`
* is substituted with the correct OS arch. This will be used if the user is using a precompiled
* hermes-engine package.
* 4. Fails otherwise
*/
internal fun detectedHermesCommand(config: ReactExtension): String =
detectOSAwareHermesCommand(config.root.get().asFile, config.hermesCommand.get())
private fun detectEntryFile(
entryFile: File?,
reactRoot: File,
envVariableOverride: String? = null
): File =
when {
envVariableOverride != null -> File(reactRoot, envVariableOverride)
entryFile != null -> entryFile
File(reactRoot, "index.android.js").exists() -> File(reactRoot, "index.android.js")
else -> File(reactRoot, "index.js")
}
private fun detectCliFile(reactNativeRoot: File, preconfiguredCliFile: File?): File {
// 1. preconfigured path
if (preconfiguredCliFile != null) {
if (preconfiguredCliFile.exists()) {
return preconfiguredCliFile
}
}
// 2. node module path
val nodeProcess =
Runtime.getRuntime()
.exec(
arrayOf("node", "--print", "require.resolve('react-native/cli');"),
emptyArray(),
reactNativeRoot)
val nodeProcessOutput = nodeProcess.inputStream.use { it.bufferedReader().readText().trim() }
if (nodeProcessOutput.isNotEmpty()) {
val nodeModuleCliJs = File(nodeProcessOutput)
if (nodeModuleCliJs.exists()) {
return nodeModuleCliJs
}
}
// 3. cli.js in the root folder
val rootCliJs = File(reactNativeRoot, "node_modules/react-native/cli.js")
if (rootCliJs.exists()) {
return rootCliJs
}
error(
"""
Couldn't determine CLI location!
Please set `react { cliFile = file(...) }` inside your
build.gradle to the path of the react-native cli.js file.
This file typically resides in `node_modules/react-native/cli.js`
"""
.trimIndent())
}
/**
* Computes the `hermesc` command location. The Algo follows this order:
* 1. The path provided by the `hermesCommand` config in the `react` Gradle extension
* 2. The file located in `node_modules/react-native/sdks/hermes/build/bin/hermesc`. This will be
* used if the user is building Hermes from source.
* 3. The file located in `node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc` where `%OS-BIN%`
* is substituted with the correct OS arch. This will be used if the user is using a precompiled
* hermes-engine package.
* 4. Fails otherwise
*/
internal fun detectOSAwareHermesCommand(projectRoot: File, hermesCommand: String): String {
// 1. If the project specifies a Hermes command, don't second guess it.
if (hermesCommand.isNotBlank()) {
val osSpecificHermesCommand =
if ("%OS-BIN%" in hermesCommand) {
hermesCommand.replace("%OS-BIN%", getHermesOSBin())
} else {
hermesCommand
}
return osSpecificHermesCommand
// Execution on Windows fails with / as separator
.replace('/', File.separatorChar)
}
// 2. If the project is building hermes-engine from source, use hermesc from there
val builtHermesc =
getBuiltHermescFile(projectRoot, System.getenv("REACT_NATIVE_OVERRIDE_HERMES_DIR"))
if (builtHermesc.exists()) {
return builtHermesc.cliPath(projectRoot)
}
// 3. If the react-native contains a pre-built hermesc, use it.
val prebuiltHermesPath =
HERMESC_IN_REACT_NATIVE_DIR.plus(getHermesCBin())
.replace("%OS-BIN%", getHermesOSBin())
// Execution on Windows fails with / as separator
.replace('/', File.separatorChar)
val prebuiltHermes = File(projectRoot, prebuiltHermesPath)
if (prebuiltHermes.exists()) {
return prebuiltHermes.cliPath(projectRoot)
}
error(
"Couldn't determine Hermesc location. " +
"Please set `react.hermesCommand` to the path of the hermesc binary file. " +
"node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc")
}
/**
* Gets the location where Hermesc should be. If nothing is specified, built hermesc is assumed to
* be inside [HERMESC_BUILT_FROM_SOURCE_DIR]. Otherwise user can specify an override with
* [pathOverride], which is assumed to be an absolute path where Hermes source code is
* provided/built.
*
* @param projectRoot The root of the Project.
*/
internal fun getBuiltHermescFile(projectRoot: File, pathOverride: String?) =
if (!pathOverride.isNullOrBlank()) {
File(pathOverride, "build/bin/${getHermesCBin()}")
} else {
File(projectRoot, HERMESC_BUILT_FROM_SOURCE_DIR.plus(getHermesCBin()))
}
internal fun getHermesCBin() = if (Os.isWindows()) "hermesc.exe" else "hermesc"
internal fun getHermesOSBin(): String {
if (Os.isWindows()) return "win64-bin"
if (Os.isMac()) return "osx-bin"
if (Os.isLinuxAmd64()) return "linux64-bin"
error(
"OS not recognized. Please set project.react.hermesCommand " +
"to the path of a working Hermes compiler.")
}
internal fun projectPathToLibraryName(projectPath: String): String =
projectPath
.split(':', '-', '_', '.')
.joinToString("") { token -> token.capitalizeCompat() }
.plus("Spec")
/**
* Function to look for the relevant `package.json`. We first look in the parent folder of this
* Gradle module (generally the case for library projects) or we fallback to looking into the `root`
* folder of a React Native project (generally the case for app projects).
*/
internal fun findPackageJsonFile(project: Project, rootProperty: DirectoryProperty): File? {
val inParent = project.file("../package.json")
if (inParent.exists()) {
return inParent
}
val fromExtension = rootProperty.file("package.json").orNull?.asFile
if (fromExtension?.exists() == true) {
return fromExtension
}
return null
}
/**
* Function to look for the `package.json` and parse it. It returns a [ModelPackageJson] if found or
* null others.
*
* Please note that this function access the [DirectoryProperty] parameter and calls .get() on them,
* so calling this during apply() of the ReactPlugin is not recommended. It should be invoked inside
* lazy lambdas or at execution time.
*/
internal fun readPackageJsonFile(
project: Project,
rootProperty: DirectoryProperty
): ModelPackageJson? {
val packageJson = findPackageJsonFile(project, rootProperty)
return packageJson?.let { JsonUtils.fromPackageJson(it) }
}
private const val HERMESC_IN_REACT_NATIVE_DIR = "node_modules/react-native/sdks/hermesc/%OS-BIN%/"
private const val HERMESC_BUILT_FROM_SOURCE_DIR =
"node_modules/react-native/ReactAndroid/hermes-engine/build/hermes/bin/"

View File

@@ -0,0 +1,132 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
import com.facebook.react.ReactExtension
import com.facebook.react.model.ModelPackageJson
import com.facebook.react.utils.KotlinStdlibCompatUtils.lowercaseCompat
import com.facebook.react.utils.KotlinStdlibCompatUtils.toBooleanStrictOrNullCompat
import com.facebook.react.utils.PropertyUtils.HERMES_ENABLED
import com.facebook.react.utils.PropertyUtils.NEW_ARCH_ENABLED
import com.facebook.react.utils.PropertyUtils.REACT_NATIVE_ARCHITECTURES
import com.facebook.react.utils.PropertyUtils.SCOPED_HERMES_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_NEW_ARCH_ENABLED
import com.facebook.react.utils.PropertyUtils.SCOPED_REACT_NATIVE_ARCHITECTURES
import java.io.File
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
internal object ProjectUtils {
const val HERMES_FALLBACK = true
internal fun Project.isNewArchEnabled(extension: ReactExtension): Boolean {
return (project.hasProperty(NEW_ARCH_ENABLED) &&
project.property(NEW_ARCH_ENABLED).toString().toBoolean()) ||
(project.hasProperty(SCOPED_NEW_ARCH_ENABLED) &&
project.property(SCOPED_NEW_ARCH_ENABLED).toString().toBoolean()) ||
shouldEnableNewArchForReactNativeVersion(project.reactNativeDir(extension))
}
internal val Project.isHermesEnabled: Boolean
get() =
if (project.hasProperty(HERMES_ENABLED) || project.hasProperty(SCOPED_HERMES_ENABLED)) {
val propertyString =
if (project.hasProperty(HERMES_ENABLED)) {
HERMES_ENABLED
} else {
SCOPED_HERMES_ENABLED
}
project
.property(propertyString)
.toString()
.lowercaseCompat()
.toBooleanStrictOrNullCompat() ?: true
} else if (project.extensions.extraProperties.has("react")) {
@Suppress("UNCHECKED_CAST")
val reactMap = project.extensions.extraProperties.get("react") as? Map<String, Any?>
when (val enableHermesKey = reactMap?.get("enableHermes")) {
is Boolean -> enableHermesKey
is String -> enableHermesKey.lowercaseCompat().toBooleanStrictOrNullCompat() ?: true
else -> HERMES_FALLBACK
}
} else {
HERMES_FALLBACK
}
internal fun Project.needsCodegenFromPackageJson(rootProperty: DirectoryProperty): Boolean {
val parsedPackageJson = readPackageJsonFile(this, rootProperty)
return needsCodegenFromPackageJson(parsedPackageJson)
}
internal fun Project.needsCodegenFromPackageJson(model: ModelPackageJson?): Boolean {
return model?.codegenConfig != null
}
internal fun Project.getReactNativeArchitectures(): List<String> {
val architectures = mutableListOf<String>()
if (project.hasProperty(REACT_NATIVE_ARCHITECTURES)) {
val architecturesString = project.property(REACT_NATIVE_ARCHITECTURES).toString()
architectures.addAll(architecturesString.split(",").filter { it.isNotBlank() })
} else if (project.hasProperty(SCOPED_REACT_NATIVE_ARCHITECTURES)) {
val architecturesString = project.property(SCOPED_REACT_NATIVE_ARCHITECTURES).toString()
architectures.addAll(architecturesString.split(",").filter { it.isNotBlank() })
}
return architectures
}
internal fun Project.reactNativeDir(extension: ReactExtension): String =
extension.reactNativeDir.get().asFile.absolutePath
internal fun shouldEnableNewArchForReactNativeVersion(reactNativeDir: String): Boolean {
val packageJsonFile = File(reactNativeDir, "package.json")
if (!packageJsonFile.exists()) {
return false
}
val rnPackageJson = JsonUtils.fromPackageJson(packageJsonFile)
if (rnPackageJson == null) {
return false
}
// This regex describe the version syntax for React Native in the shape of
// major.minor.patch[-<prerelease>[[-.]k]]
// Where
// major is a number
// minor is a number
// patch is a number
// <prerelease>[-.]k is optional, but if present is preceeded by a `-`
// the <prerelease> tag is a string.
// it can be followed by `-` or `.` and k is a number.
val regex = """^(\d+)\.(\d+)\.(\d+)(?:-(\w+(?:[-.]\d+)?))?$""".toRegex()
val matchResult = regex.find(rnPackageJson.version)
if (matchResult == null) {
return false
}
val prerelease = matchResult.groupValues[4].toString()
return prerelease.contains("prealpha")
}
internal fun Project.shouldWarnIfNewArchFlagIsSetInPrealpha(extension: ReactExtension): Boolean {
val propertySetToFalse =
(this.hasPropertySetToFalse(NEW_ARCH_ENABLED)) ||
(this.hasPropertySetToFalse(SCOPED_NEW_ARCH_ENABLED))
val shouldEnableNewArch =
shouldEnableNewArchForReactNativeVersion(this.reactNativeDir(extension))
return shouldEnableNewArch && propertySetToFalse
}
internal fun Project.hasPropertySetToFalse(property: String): Boolean =
this.hasProperty(property) && this.property(property).toString().toBoolean() == false
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
/** Collection of all the Gradle Properties that are accepted by React Native Gradle Plugin. */
object PropertyUtils {
/** Public property that toggles the New Architecture */
const val NEW_ARCH_ENABLED = "newArchEnabled"
const val SCOPED_NEW_ARCH_ENABLED = "react.newArchEnabled"
/** Public property that toggles the New Architecture */
const val HERMES_ENABLED = "hermesEnabled"
const val SCOPED_HERMES_ENABLED = "react.hermesEnabled"
/** Public property that allows to control which architectures to build for React Native. */
const val REACT_NATIVE_ARCHITECTURES = "reactNativeArchitectures"
const val SCOPED_REACT_NATIVE_ARCHITECTURES = "react.nativeArchitectures"
/**
* Internal Property that acts as a killswitch to configure the JDK version and align it for app
* and all the libraries.
*/
const val INTERNAL_DISABLE_JAVA_VERSION_ALIGNMENT = "react.internal.disableJavaVersionAlignment"
/**
* Internal Property that allows to specify a local Maven repository to use for React Native
* artifacts It's used on CI to test templates against a version of React Native built on the fly.
*/
const val INTERNAL_REACT_NATIVE_MAVEN_LOCAL_REPO = "react.internal.mavenLocalRepo"
/**
* Internal property used to specify where the Windows Bash executable is located. This is useful
* for contributors who are running Windows on their machine.
*/
const val INTERNAL_REACT_WINDOWS_BASH = "react.internal.windowsBashPath"
/**
* Internal property to force the build to use Hermes from the latest nightly. This speeds up the
* build at the cost of not testing the latest integration against Hermes.
*/
const val INTERNAL_USE_HERMES_NIGHTLY = "react.internal.useHermesNightly"
/** Internal property used to override the publishing group for the React Native artifacts. */
const val INTERNAL_PUBLISHING_GROUP = "react.internal.publishingGroup"
const val DEFAULT_INTERNAL_PUBLISHING_GROUP = "com.facebook.react"
/** Internal property used to control the version name of React Native */
const val INTERNAL_VERSION_NAME = "VERSION_NAME"
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.utils
internal fun windowsAwareCommandLine(vararg args: Any): List<Any> =
windowsAwareCommandLine(args.toList())
internal fun windowsAwareCommandLine(args: List<Any>): List<Any> =
if (Os.isWindows()) {
listOf("cmd", "/c") + args
} else {
args
}
internal fun windowsAwareBashCommandLine(
vararg args: String,
bashWindowsHome: String? = null
): List<String> =
if (Os.isWindows()) {
listOf(bashWindowsHome ?: "bash", "-c") + args
} else {
args.toList()
}