From a3f4840dd20eec9b0a9b2ca7a75d935ed879c96e Mon Sep 17 00:00:00 2001 From: Harry Yu Date: Mon, 17 Nov 2025 01:08:07 -0800 Subject: [PATCH 1/2] Create scripts for building patched React Native --- build.gradle.kts | 16 ++ .../react-native/ReactAndroid/publish.gradle | 21 +++ scripts/build-package.js | 95 ++++++++++++ scripts/move-react-native-package.sh | 138 ++++++++++++++++++ 4 files changed, 270 insertions(+) create mode 100644 scripts/build-package.js create mode 100755 scripts/move-react-native-package.sh diff --git a/build.gradle.kts b/build.gradle.kts index 4ed9ea7dc9a2..f5e150bf738f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -100,6 +100,22 @@ tasks.register("publishAllToMavenTempLocal") { ":packages:react-native:ReactAndroid:hermes-engine:publishAllPublicationsToMavenTempLocalRepository") } +// Added to allow us to publish patched Android JARs to Gitlab. This is part +// of the "Patching React Native" process at: +// https://www.notion.so/wanderlog/Patching-React-Native-17b4797ed24d481eb2155c9daec1ba98?source=copy_link +tasks.register("publishAllToGitLab") { + description = "Publish all the release artifacts to a GitLab Maven Repository." + + val gitlabKey = project.property("gitlabPrivateToken") as? String + if (gitlabKey == null) { + throw IllegalStateException("gitlabPrivateToken property is not set. Set it in your ~/.gradle/gradle.properties file to a full-access token") + } + + // The Release pubName is derived by searching for MavenPublication in the codebase + dependsOn(":packages:react-native:ReactAndroid:publishAllPublicationsToGitLabRepository") + dependsOn(":packages:react-native:ReactAndroid:hermes-engine:publishAllPublicationsToGitLabRepository") +} + tasks.register("publishAndroidToSonatype") { description = "Publish the Android artifacts to Sonatype (Maven Central or Snapshot repository)" dependsOn(":packages:react-native:ReactAndroid:publishToSonatype") diff --git a/packages/react-native/ReactAndroid/publish.gradle b/packages/react-native/ReactAndroid/publish.gradle index 32287a7c8139..99c190415f72 100644 --- a/packages/react-native/ReactAndroid/publish.gradle +++ b/packages/react-native/ReactAndroid/publish.gradle @@ -68,6 +68,27 @@ publishing { name = "mavenTempLocal" url = mavenTempLocalUrl } + + // Added to allow us to publish patched Android JARs to Gitlab. This + // is part of the "Patching React Native" process at: + // https://www.notion.so/wanderlog/Patching-React-Native-17b4797ed24d481eb2155c9daec1ba98?source=copy_link + // + // Added based on https://docs.gitlab.com/ee/user/packages/gradle_repository/#authenticate-with-a-personal-access-token-or-deploy-token-in-gradle + // to publish our forked package to Maven + maven { + // URL modified based on https://docs.gitlab.com/ee/api/packages/maven.html#upload-a-package-file + // Project ID is obtained from the itineraries project on Gitlab + url = "https://gitlab.com/api/v4/projects/9347980/packages/maven" + name = "GitLab" + // Modified based on https://stackoverflow.com/a/61339469 + credentials(HttpHeaderCredentials) { + name = "Private-Token" + value = project.property("gitlabPrivateToken") + } + authentication { + header(HttpHeaderAuthentication) + } + } } if (signingKey && signingPwd) { diff --git a/scripts/build-package.js b/scripts/build-package.js new file mode 100644 index 000000000000..8046005d55c1 --- /dev/null +++ b/scripts/build-package.js @@ -0,0 +1,95 @@ +// @flow +const { + downloadHermesSourceTarball, + expandHermesSourceTarball, +} = require('../packages/react-native/scripts/hermes/hermes-utils'); +const { REPO_ROOT } = require('./consts'); +const fs = require('fs'); +const { rm } = require('fs/promises'); +const {cp, exec} = require('shelljs'); + +/** + * This script is used as a part of Patching React Native. See: + * https://www.notion.so/wanderlog/Patching-React-Native-17b4797ed24d481eb2155c9daec1ba98?source=copy_link + * + * This section goes over how we created it: + * ["How did we figure out the steps for Create scripts for building patched React Native](https://www.notion.so/wanderlog/Patching-React-Native-17b4797ed24d481eb2155c9daec1ba98?source=copy_link#2aef9a6861f68016a0def8edec2d3d5a) + * + * Much of this code is copied from buildArtifactsLocally in + * scripts/release-testing/utils/testing-utils.js + * + * The main goal is to build hermesc so it can be checked in to our built + * version. + * + * If this script fails, we can debug it by running it with `CI=true` to see + * all the intermediate commands the Shell scripts run, since the scripts + * enable `set -x` when `CI` is set. + */ +async function buildPackage() { + const reactNativePackagePath = `${REPO_ROOT}/packages/react-native`; + + // Start copied from scripts/release-testing/utils/testing-utils.js + const hermesCoreSourceFolder = `${reactNativePackagePath}/sdks/hermes`; + + if (!fs.existsSync(hermesCoreSourceFolder)) { + console.info('The Hermes source folder is missing. Downloading...'); + downloadHermesSourceTarball(); + expandHermesSourceTarball(); + } + + // need to move the scripts inside the local hermes cloned folder + // cp sdks/hermes-engine/utils/*.sh /utils/. + cp( + `${reactNativePackagePath}/sdks/hermes-engine/hermes-engine.podspec`, + `${reactNativePackagePath}/sdks/hermes/hermes-engine.podspec`, + ); + cp( + `${reactNativePackagePath}/sdks/hermes-engine/hermes-utils.rb`, + `${reactNativePackagePath}/sdks/hermes/hermes-utils.rb`, + ); + cp( + `${reactNativePackagePath}/sdks/hermes-engine/utils/*.sh`, + `${reactNativePackagePath}/sdks/hermes/utils/.`, + ); + // End copied from scripts/release-testing/utils/testing-utils.js + + // Builds the types_generated. We figured this out by asking: + // "What code in this project creates the types_generated directory?" + exec('yarn build-types', {cwd: REPO_ROOT}); + // Builds the FBReactNativeSpec. We figured this out by asking: + // "What code in this project creates the FBReactNativeSpec directory?" + exec('yarn install && yarn prepack', {cwd: reactNativePackagePath}); + // We don't need the README.md file + await rm(`${reactNativePackagePath}/README.md`); + + process.env.MAC_DEPLOYMENT_TARGET = "10.15"; + + // This command builds hermes and hermesc binaries for MacOS. I figured out + // this by: + // + // 1. Running `find . -name hermes` and seeing that the file was in + // `./packages/react-native/sdks/hermes/build_macosx/bin/hermes` + // 2. Finding references to build_macosx and seeing it was likely created + // by the build_apple_framework function + // 3. Finding callers of the build_apple_framework function + exec( + 'bash ./utils/build-mac-framework.sh', + {cwd: `${reactNativePackagePath}/sdks/hermes`}, + ); + + // We figured out the files to copy by running: + // `find . -name hermesc` and and `find . -name hermes | grep bin` + // + // We figured out the destination directory by looking at the the paths in + // the official React Native NPM package: https://www.npmjs.com/package/react-native/v/0.81.5?activeTab=code + const hermesDir = `${reactNativePackagePath}/sdks/hermes/build_macosx/bin`; + const hermescDir = `${reactNativePackagePath}/sdks/hermes/build_host_hermesc/bin`; + const destinationDir = `${reactNativePackagePath}/sdks/hermesc/osx-bin`; + console.info(`Copying files from ${hermesDir} to ${destinationDir}`); + console.info(`Copying files from ${hermescDir} to ${destinationDir}`); + await fs.promises.mkdir(destinationDir, { recursive: true }); + cp('-r', `${hermesDir}/*`, `${destinationDir}/`); + cp('-r', `${hermescDir}/*`, `${destinationDir}/`); +} + +buildPackage(); diff --git a/scripts/move-react-native-package.sh b/scripts/move-react-native-package.sh new file mode 100755 index 000000000000..fb9537decfb7 --- /dev/null +++ b/scripts/move-react-native-package.sh @@ -0,0 +1,138 @@ +#!/bin/bash + +set -e + +SAVED_ROOT_FILES_NAME="saved-root-files" + +# Parse command line arguments +help_text_and_exit() { + cat < originalFiles.txt` + # 3. Installing our fork of react-native + # 4. Running the same command to list the files again + # 5. Diffing the two files to see what was missed + sed -E -i '' '/^\/?vendor/d' "$REPO_ROOT/.gitignore" + sed -E -i '' '/^\/?sdks\/hermesc/d' "$REPO_ROOT/.gitignore" + sed -i '' '/types_generated/d' "$REPO_ROOT/.gitignore" + sed -i '' '/FBReactNativeSpec/d' "$REPO_ROOT/.gitignore" + echo "/saved-root-files/" >> "$REPO_ROOT/.gitignore" + + echo "Moving all files to $SAVED_ROOT_FILES_PATH" + for item in "$REPO_ROOT"/*; do + item_name=$(basename "$item") + # Skip the directory itself + if [ "$item_name" != "$SAVED_ROOT_FILES_NAME" ]; then + echo "- Moving $item_name to $SAVED_ROOT_FILES_PATH" + mv -f "$item" "$SAVED_ROOT_FILES_PATH/" + fi + done + + echo "Moving /packages/react-native contents to root..." + + mv -f "$SAVED_ROOT_FILES_PATH/packages/react-native"/* "$REPO_ROOT/" + + echo "Script completed successfully" + +# Reverse moves +else + echo "Reversing operations: moving files from $SAVED_ROOT_FILES_NAME back to root..." + + if [ ! -d "$SAVED_ROOT_FILES_PATH" ]; then + echo "Error: $SAVED_ROOT_FILES_NAME directory not found. Nothing to reverse." + exit 1 + fi + + echo "Moving react-native package contents to within saved-root-files/packages" # Ensure the directory exists + mkdir -p "$SAVED_ROOT_FILES_PATH/packages/react-native" + + for item in "$REPO_ROOT"/*; do + item_name=$(basename "$item") + if [ "$item_name" != "$SAVED_ROOT_FILES_NAME" ]; then + echo "- Moving $item_name back to packages/react-native" + mv -f "$item" "$SAVED_ROOT_FILES_NAME/packages/react-native/" + fi + done + + echo "Moving .gitignore back to root" + mv -f "$SAVED_ROOT_FILES_PATH/.gitignore" "$REPO_ROOT/.gitignore" + + echo "Moving all files back to root" + mv -f "$SAVED_ROOT_FILES_PATH"/* "$REPO_ROOT/" + + echo "Deleting $SAVED_ROOT_FILES_NAME" + rm -rf "$SAVED_ROOT_FILES_PATH" + + echo "Reverse operation completed successfully" + exit 0 +fi From 5de30e918627da9ee594ea861c8909a4d27ec2fc Mon Sep 17 00:00:00 2001 From: Harry Yu Date: Tue, 18 Nov 2025 16:53:04 -0800 Subject: [PATCH 2/2] Improve comments in build-package.js --- scripts/build-package.js | 61 ++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/scripts/build-package.js b/scripts/build-package.js index 8046005d55c1..9ad557f2f0b4 100644 --- a/scripts/build-package.js +++ b/scripts/build-package.js @@ -15,11 +15,7 @@ const {cp, exec} = require('shelljs'); * This section goes over how we created it: * ["How did we figure out the steps for Create scripts for building patched React Native](https://www.notion.so/wanderlog/Patching-React-Native-17b4797ed24d481eb2155c9daec1ba98?source=copy_link#2aef9a6861f68016a0def8edec2d3d5a) * - * Much of this code is copied from buildArtifactsLocally in - * scripts/release-testing/utils/testing-utils.js - * - * The main goal is to build hermesc so it can be checked in to our built - * version. + * This code is largely copied from other files per the comments below. * * If this script fails, we can debug it by running it with `CI=true` to see * all the intermediate commands the Shell scripts run, since the scripts @@ -28,6 +24,7 @@ const {cp, exec} = require('shelljs'); async function buildPackage() { const reactNativePackagePath = `${REPO_ROOT}/packages/react-native`; + // This part of the code downloads the Hermes source code and expands it. // Start copied from scripts/release-testing/utils/testing-utils.js const hermesCoreSourceFolder = `${reactNativePackagePath}/sdks/hermes`; @@ -56,27 +53,37 @@ async function buildPackage() { // Builds the types_generated. We figured this out by asking: // "What code in this project creates the types_generated directory?" exec('yarn build-types', {cwd: REPO_ROOT}); + // Builds the FBReactNativeSpec. We figured this out by asking: // "What code in this project creates the FBReactNativeSpec directory?" exec('yarn install && yarn prepack', {cwd: reactNativePackagePath}); // We don't need the README.md file await rm(`${reactNativePackagePath}/README.md`); - process.env.MAC_DEPLOYMENT_TARGET = "10.15"; - - // This command builds hermes and hermesc binaries for MacOS. I figured out - // this by: + // build-mac-framework.sh builds hermes and hermesc binaries for MacOS. + // I figured this out by: // - // 1. Running `find . -name hermes` and seeing that the file was in + // 1. Running the code in `buildAllArtifacts` below (uncomment the line) + // below + // 2. Running `find . -name hermes` and seeing that the file was in // `./packages/react-native/sdks/hermes/build_macosx/bin/hermes` - // 2. Finding references to build_macosx and seeing it was likely created + // 3. Finding references to build_macosx and seeing it was likely created // by the build_apple_framework function - // 3. Finding callers of the build_apple_framework function + // 4. Finding callers of the build_apple_framework function + + // MAC_DEPLOYMENT_TARGET is used by get_mac_deployment_target in + // build-mac-framework.sh. The value to set is taken from + // publish-release.yml + process.env.MAC_DEPLOYMENT_TARGET = "10.15"; exec( 'bash ./utils/build-mac-framework.sh', {cwd: `${reactNativePackagePath}/sdks/hermes`}, ); + // Uncomment this to build all the artifacts for Hermes, including ones that we + // don't need. + // buildAllArtifacts(hermesCoreSourceFolder); + // We figured out the files to copy by running: // `find . -name hermesc` and and `find . -name hermes | grep bin` // @@ -92,4 +99,34 @@ async function buildPackage() { cp('-r', `${hermescDir}/*`, `${destinationDir}/`); } +/** + * This function builds all the artifacts for Hermes, including ones that we + * don't need. We used it initially to figure out which subtasks built the hermes + * and hermesc binaries. + */ +// eslint-disable-next-line no-unused-vars +function buildAllArtifacts(hermesCoreSourceFolder) { + // These versions were copied from publish-release.yml + process.env.IOS_DEPLOYMENT_TARGET = '15.1'; + process.env.MAC_DEPLOYMENT_TARGET = '10.15'; + process.env.XROS_DEPLOYMENT_TARGET = '1.0'; + + // Start copied from scripts/release-testing/utils/testing-utils.js + const jsiFolder = `${reactNativePackagePath}/ReactCommon/jsi`; + + const buildTypeiOSArtifacts = 'Debug'; + + // the android ones get set into /private/tmp/maven-local + const localMavenPath = '/private/tmp/maven-local'; + + // Generate native files for iOS + generateiOSArtifacts( + jsiFolder, + hermesCoreSourceFolder, + buildTypeiOSArtifacts, + localMavenPath, + ); + // End copied from scripts/release-testing/utils/testing-utils.js +} + buildPackage();