|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# Regression guard for https://github.com/getsentry/sentry-react-native/issues/6398 |
| 4 | +# |
| 5 | +# `libsentry-tm-perf-logger.so` is compiled from source in the consuming app and |
| 6 | +# references `facebook::react::TurboModulePerfLogger::enableLogging`, which lives |
| 7 | +# in React Native's `reactnative` prefab. On some New Architecture setups (e.g. |
| 8 | +# Expo builds on armeabi-v7a) that reference is not satisfied at link time, and |
| 9 | +# because the NDK/AGP toolchain links with `-Wl,-z,defs` (`--no-undefined`) the |
| 10 | +# unresolved symbol becomes a fatal error that breaks the whole Android build. |
| 11 | +# The fix (see src/main/jni/CMakeLists.txt) appends `-Wl,-z,undefs` so undefined |
| 12 | +# symbols are non-fatal and resolve at load time instead. |
| 13 | +# |
| 14 | +# This check reproduces that exact link condition for armeabi-v7a using the real |
| 15 | +# sources and the real link flags declared in CMakeLists.txt: |
| 16 | +# 1. Control — link WITHOUT the fix flags: must FAIL (proves the reproduction |
| 17 | +# is still valid; if it starts passing the check is stale). |
| 18 | +# 2. Fixed — link WITH the flags CMakeLists.txt actually declares: must PASS |
| 19 | +# (fails if `-Wl,-z,undefs` is ever removed from CMakeLists.txt). |
| 20 | +# |
| 21 | +# Requires an Android NDK (found via $ANDROID_NDK_* / $ANDROID_HOME/ndk) and a |
| 22 | +# React Native install (resolved from packages/core, or $REACT_NATIVE_DIR). |
| 23 | + |
| 24 | +set -euo pipefail |
| 25 | + |
| 26 | +repo_root="$(cd "$(dirname "$0")/.." && pwd)" |
| 27 | +cmakelists="$repo_root/packages/core/android/src/main/jni/CMakeLists.txt" |
| 28 | +cpp_dir="$repo_root/packages/core/cpp" |
| 29 | +jni_dir="$repo_root/packages/core/android/src/main/jni" |
| 30 | + |
| 31 | +# --- Resolve the React Native source tree (for ReactCommon headers) ---------- |
| 32 | +rn_dir="${REACT_NATIVE_DIR:-}" |
| 33 | +if [[ -z "$rn_dir" ]]; then |
| 34 | + rn_dir="$(cd "$repo_root/packages/core" && node -p "require('path').dirname(require.resolve('react-native/package.json'))" 2>/dev/null || true)" |
| 35 | +fi |
| 36 | +if [[ -z "$rn_dir" || ! -d "$rn_dir/ReactCommon" ]]; then |
| 37 | + echo "error: could not locate react-native (set \$REACT_NATIVE_DIR to its root)" >&2 |
| 38 | + exit 2 |
| 39 | +fi |
| 40 | + |
| 41 | +# --- Locate the NDK clang++ -------------------------------------------------- |
| 42 | +find_clang() { |
| 43 | + local host |
| 44 | + case "$(uname -s)" in |
| 45 | + Darwin) host="darwin-x86_64" ;; |
| 46 | + *) host="linux-x86_64" ;; |
| 47 | + esac |
| 48 | + local roots=() |
| 49 | + [[ -n "${ANDROID_NDK_HOME:-}" ]] && roots+=("$ANDROID_NDK_HOME") |
| 50 | + [[ -n "${ANDROID_NDK_LATEST_HOME:-}" ]] && roots+=("$ANDROID_NDK_LATEST_HOME") |
| 51 | + [[ -n "${ANDROID_NDK_ROOT:-}" ]] && roots+=("$ANDROID_NDK_ROOT") |
| 52 | + for base in "${ANDROID_HOME:-}" "${ANDROID_SDK_ROOT:-}" "$HOME/Library/Android/sdk" "$HOME/Android/Sdk"; do |
| 53 | + [[ -d "$base/ndk" ]] && while IFS= read -r d; do roots+=("$d"); done < <(find "$base/ndk" -maxdepth 1 -mindepth 1 -type d | sort -r) |
| 54 | + done |
| 55 | + for r in "${roots[@]}"; do |
| 56 | + local c="$r/toolchains/llvm/prebuilt/$host/bin/clang++" |
| 57 | + [[ -x "$c" ]] && { echo "$c"; return 0; } |
| 58 | + done |
| 59 | + return 1 |
| 60 | +} |
| 61 | +clang="$(find_clang || true)" |
| 62 | +if [[ -z "$clang" ]]; then |
| 63 | + echo "error: could not find an Android NDK clang++ (set \$ANDROID_NDK_HOME)" >&2 |
| 64 | + exit 2 |
| 65 | +fi |
| 66 | + |
| 67 | +# --- Extract the link flags the CMakeLists.txt actually declares -------------- |
| 68 | +# Everything the target passes via target_link_options(... "-Wl,..."), verbatim. |
| 69 | +fix_flags=() |
| 70 | +while IFS= read -r f; do fix_flags+=("$f"); done < <(grep -oE '"-Wl,[^"]+"' "$cmakelists" | tr -d '"') |
| 71 | +if [[ ${#fix_flags[@]} -eq 0 ]]; then |
| 72 | + echo "error: no -Wl link flags found in $cmakelists (has the target changed?)" >&2 |
| 73 | + exit 2 |
| 74 | +fi |
| 75 | + |
| 76 | +workdir="$(mktemp -d)" |
| 77 | +trap 'rm -rf "$workdir"' EXIT |
| 78 | + |
| 79 | +includes=( |
| 80 | + -I "$cpp_dir" |
| 81 | + -I "$rn_dir/ReactCommon/react/nativemodule/core" |
| 82 | + -I "$rn_dir/ReactCommon/reactperflogger" |
| 83 | + -I "$rn_dir/ReactCommon" |
| 84 | + -I "$rn_dir/ReactCommon/callinvoker" |
| 85 | +) |
| 86 | +sources=("$cpp_dir/SentryTurboModulePerfLogger.cpp" "$jni_dir/OnLoad.cpp") |
| 87 | + |
| 88 | +# Reproduce the failing condition: armeabi-v7a, -shared, `--no-undefined`, and |
| 89 | +# the `reactnative` prefab NOT linked (so `enableLogging` is unresolved). |
| 90 | +link() { |
| 91 | + "$clang" --target=armv7-none-linux-androideabi21 -std=c++20 -fPIC -shared \ |
| 92 | + -DRCT_NEW_ARCH_ENABLED=1 "${includes[@]}" "${sources[@]}" \ |
| 93 | + -Wl,--no-undefined "$@" -o "$workdir/out.so" 2>"$workdir/err.txt" |
| 94 | +} |
| 95 | + |
| 96 | +echo "NDK clang: $clang" |
| 97 | +echo "React Native: $rn_dir" |
| 98 | +echo "CMakeLists link flags: ${fix_flags[*]}" |
| 99 | +echo |
| 100 | + |
| 101 | +# --- 1) Control: without the fix flags, the link MUST fail ------------------- |
| 102 | +rm -f "$workdir/out.so" |
| 103 | +if link; then |
| 104 | + echo "✖ CONTROL UNEXPECTEDLY LINKED — the reproduction is stale." >&2 |
| 105 | + echo " '$0' no longer exercises the #6398 condition (did RN inline the symbol," >&2 |
| 106 | + echo " or did the toolchain stop passing --no-undefined?). Update this check." >&2 |
| 107 | + exit 1 |
| 108 | +fi |
| 109 | +echo "✔ control link fails as expected (undefined symbol without the fix)" |
| 110 | + |
| 111 | +# --- 2) Fixed: with the flags CMakeLists declares, the link MUST succeed ------ |
| 112 | +rm -f "$workdir/out.so" |
| 113 | +if ! link "${fix_flags[@]}"; then |
| 114 | + echo "✖ FIXED LINK FAILED — CMakeLists.txt no longer prevents the #6398 build break." >&2 |
| 115 | + echo " Ensure the sentry-tm-perf-logger target keeps '-Wl,-z,undefs'." >&2 |
| 116 | + echo " Linker output:" >&2 |
| 117 | + sed 's/^/ /' "$workdir/err.txt" >&2 |
| 118 | + exit 1 |
| 119 | +fi |
| 120 | +echo "✔ fixed link succeeds with CMakeLists flags (${fix_flags[*]})" |
| 121 | +echo |
| 122 | +echo "✔ #6398 link regression guard passed" |
0 commit comments