#!/usr/bin/env bash # # Check if we need to copy node_modules. # (e.g. in case it has been modified after last copy) # # The reasoning for a (mostly) read-only node_modules folder: # ideally we’d symlink the folder directly to the Nix store # so that we’re guaranteed to have a reproducible source. # Unfortunately react-native wants to build some stuff after the fact # and this is incompatible with the concept of a pure Nix package. # Therefore we copy the whole source to the repo directory, # allow writing only on the folders where it is absolutely required, # and therefore we still keep some peace of mind that the rest # of node_modules is unchanged the rest of the time. # set -Eeuo pipefail GIT_ROOT=$(cd "${BASH_SOURCE%/*}" && git rev-parse --show-toplevel) source "${GIT_ROOT}/scripts/colors.sh" # More concise output from 'time' export TIMEFORMAT="Done in: %Es" removeDir() { [[ ! -d "${tmp}" ]] && return chmod -R u+w "${tmp}" rm -rf "${tmp}" } copyNodeModules() { local src="${1}" local dst="${2}" # WARNING: The ../ is there to avoid a Nix builtins.path bug: # https://github.com/NixOS/nix/issues/3593 local tmp=$(mktemp -d -p "$(dirname "${dst}")/../") # We use a temporary directory to use mv as "atomic" change trap "removeDir ${tmp}" ERR INT HUP # WARNING: The -L here is crucial to let Metro find modules. cp -LRf ${src}/node_modules/. "${tmp}" chmod -R +w "${tmp}" # WARNING: We can't de-reference .bin symlinks cp -Rf ${src}/node_modules/.bin/. "${tmp}/.bin/" # Cmake called by react-native-reanimated 3.3.0 during build creates read-only temporary files. chmod -R u+w node_modules/react-native-reanimated/android/ -f || true rm -r "${dst}" mv -f "${tmp}" "${dst}" } # Find files that were modified and should cause a re-copying of node modules. # Some files are generated/modified by build processes and should be ignored. findFilesNewerThan() { local sentinel="${1}" local dir="${2}" find "${dir}" -type f -writable \ -newer "${sentinel}" \ -not -ipath "*/*android/build/*" -prune \ -not -ipath "*/xcuserdata/*" -prune \ -not -ipath "*/scripts/.packager.env" \ \ # generated at runtime by react-native \ \ # related code https://github.com/facebook/react-native/blob/v0.72.5/packages/react-native/ReactCommon/React-rncore.podspec#L19 \ -not -path "*/node_modules/react-native/ReactCommon/react/renderer/components/rncore/*" \ \ # generated at runtime by react-native-config \ \ # related code https://github.com/lugg/react-native-config/blob/v1.5.0/react-native-config.podspec#L52 \ -not -path "*/node_modules/react-native-config/ios/ReactNativeConfig/GeneratedDotEnv.m" \ \ # generated at runtime by react-native from v0.73.x onwards \ -not -path "*/node_modules/react-native/React/Fabric/*" \ -print } nodeModulesUnchanged() { local src="$1" local dst="$2" local sentinelFile="${dst}/.copied~" # Check if node_modules exists and is valid if [[ ! -f "${sentinelFile}" ]]; then # node_modules have not been created by this script echo -e "${YLW}Node modules not created by Nix${RST}" >&2 return 1 fi # Sentinel file holds location of the node_modules source in Nix store currentNixSrc="$(cat "${sentinelFile}")" if [ "${currentNixSrc}" != "${src}" ]; then echo -e "${YLW}Yarn modules changed, copying new version over${RST}" >&2 return 1 fi # Some build processes modify files in node_modules modifiedFiles=($(findFilesNewerThan "${sentinelFile}" "${dst}")) if [ ${#modifiedFiles[@]} -ne 0 ]; then echo -e "${YLW}Changes detected in node_modules:${RST} ${#modifiedFiles[@]}" >&2 # Print files that have changes for file in ${modifiedFiles[@]}; do echo "- $(realpath --relative-to="${dst}" "${file}")" >&2 done return 1 fi echo -e "${GRN}No changes detected.${RST}" >&2 return 0 } replaceNodeModules() { local src="$1" local dst="$2" local sentinelFile="${dst}/.copied~" if nodeModulesUnchanged "${src}" "${dst}"; then return fi # Replace node_modules if necessary echo "Copying node_modules from Nix store:" >&2 echo " - ${src}" >&2 copyNodeModules "${src}" "${dst}" echo -n "${src}" > "${sentinelFile}" } # Destination folder, Nix sets STATUS_MOBILE_HOME dst="$STATUS_MOBILE_HOME/node_modules" # Source of Node modules from /nix/store src="$1" if [[ ! -d ${src} ]]; then echo -e "${RED}No such folder:${RST} ${src}" >&2 exit 1 fi # Make those available in shell spawned by flock export -f replaceNodeModules nodeModulesUnchanged copyNodeModules findFilesNewerThan removeDir mkdir -p "${dst}" # Leverage file lock to create an exclusive lock. # Otherwise multiple calls to this script would clash. flock "${dst}/" sh -c "time replaceNodeModules '${src}' '${dst}'"