status-mobile/nix/scripts/node_modules.sh

143 lines
4.8 KiB
Bash
Raw Normal View History

#!/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 wed symlink the folder directly to the Nix store
# so that were 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}'"