fold/setup.sh
trvon 53d258563f
fix(build): harden setup.sh meson native-file array + hooks path
Use a bash array for MESON_NATIVE_FILES instead of a space-joined string
(safe word-splitting/quoting), and set core.hooksPath to .githooks so the
tracked lint/pre-push hooks are active after setup.
2026-08-16 16:41:03 -06:00

267 lines
9.5 KiB
Bash
Executable file

#!/bin/bash
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
git -C "$ROOT" config core.hooksPath .githooks
BUILD_TYPE="release"
if [ "$#" -gt 0 ]; then
BUILD_TYPE="$1"
shift
fi
if [ "$BUILD_TYPE" != "release" ] && [ "$BUILD_TYPE" != "debug" ]; then
echo "ERROR: build type must be 'release' or 'debug' (got '$BUILD_TYPE')." >&2
exit 2
fi
BUILD_DIR="$ROOT/build/${BUILD_TYPE}"
OS="$(uname -s)"
# --- find LLVM ---
# Order: env override → macOS Homebrew Cellar (versioned dirs) → macOS
# brew --prefix llvm@N → Linux apt /usr/lib/llvm-N → Linux unversioned
# llvm-config on PATH. Sets LLVM_BIN (directory containing llvm-config)
# and LLVM_CONFIG (full path to the binary).
find_llvm_darwin() {
for ver in 22 21 20 19 18; do
local candidate="/opt/homebrew/Cellar/llvm/${ver}.${ver}.0/bin"
if [ -d "$candidate" ]; then
echo "$candidate"
return
fi
done
for ver in 22 21 20 19 18; do
local found
found=$(find /opt/homebrew/Cellar/llvm -maxdepth 1 -name "${ver}.*" -type d 2>/dev/null | head -1)
if [ -n "$found" ]; then
echo "$found/bin"
return
fi
done
# brew --prefix llvm@N covers Intel macOS + custom prefixes
for ver in 22 21 20 19 18; do
if command -v brew >/dev/null 2>&1; then
local prefix
prefix=$(brew --prefix "llvm@${ver}" 2>/dev/null || true)
if [ -n "$prefix" ] && [ -d "$prefix/bin" ]; then
echo "$prefix/bin"
return
fi
fi
done
echo ""
}
find_llvm_linux() {
# Apt-installed clang lives at /usr/lib/llvm-N/bin/ with
# llvm-config-N on PATH. Probe newest-first.
for ver in 22 21 20 19 18; do
if [ -x "/usr/lib/llvm-${ver}/bin/llvm-config" ]; then
echo "/usr/lib/llvm-${ver}/bin"
return
fi
if command -v "llvm-config-${ver}" >/dev/null 2>&1; then
dirname "$(command -v "llvm-config-${ver}")"
return
fi
done
# Last-ditch: bare llvm-config (some distros symlink it).
if command -v llvm-config >/dev/null 2>&1; then
dirname "$(command -v llvm-config)"
return
fi
echo ""
}
if [ -n "${LLVM_BIN:-}" ] && [ -d "$LLVM_BIN" ]; then
: # honor caller-provided LLVM_BIN
elif [ "$OS" = "Darwin" ]; then
LLVM_BIN=$(find_llvm_darwin)
elif [ "$OS" = "Linux" ]; then
LLVM_BIN=$(find_llvm_linux)
else
LLVM_BIN=""
fi
if [ -z "$LLVM_BIN" ]; then
if [ "$OS" = "Linux" ]; then
echo "ERROR: No LLVM installation found." >&2
echo " Install with: sudo apt-get install clang-19 llvm-19-dev libclang-19-dev libclang-cpp19-dev" >&2
echo " Or set LLVM_BIN=/path/to/llvm/bin to override." >&2
else
echo "ERROR: No Homebrew LLVM installation found." >&2
echo " Install with: brew install llvm" >&2
echo " Or set LLVM_BIN=/path/to/llvm/bin to override." >&2
fi
exit 1
fi
LLVM_CONFIG="$LLVM_BIN/llvm-config"
if [ ! -x "$LLVM_CONFIG" ]; then
# Versioned binary (llvm-config-19) — pick it up.
LLVM_CONFIG=$(find "$LLVM_BIN" -maxdepth 1 -name 'llvm-config*' -type f -perm -u+x 2>/dev/null | head -1)
fi
if [ -z "$LLVM_CONFIG" ] || [ ! -x "$LLVM_CONFIG" ]; then
echo "ERROR: Found LLVM_BIN=$LLVM_BIN but no executable llvm-config inside it." >&2
exit 1
fi
# Find llvm-ar and create a native file so meson/ninja can archive
# static libraries without the LLVM bin dir on PATH.
MESON_NATIVE_FILES=()
LLVM_AR="$LLVM_BIN/llvm-ar"
if [ ! -x "$LLVM_AR" ]; then
# Versioned fallback: llvm-ar-22, llvm-ar-21, etc.
LLVM_VER=$(echo "$LLVM_VERSION" | cut -d. -f1)
LLVM_AR="$LLVM_BIN/llvm-ar-$LLVM_VER"
fi
if [ -x "$LLVM_AR" ]; then
# Ensure the build dir exists before writing the native file into it.
# `conan install -of "$BUILD_DIR"` (below) also creates it, but that runs
# later — on a fresh checkout this write would otherwise fail.
mkdir -p "$BUILD_DIR"
cat >"$BUILD_DIR/llvm_ar.ini" <<EOF
[binaries]
ar = '$LLVM_AR'
EOF
echo " Using llvm-ar: $LLVM_AR"
MESON_NATIVE_FILES+=(--native-file "$BUILD_DIR/llvm_ar.ini")
fi
echo "Using LLVM: $LLVM_BIN (llvm-config=$LLVM_CONFIG)"
export PATH="$LLVM_BIN:$PATH"
export LLVM_CONFIG
# Default CC/CXX to clang from the discovered LLVM_BIN when unset; this
# lets Conan and meson pick up matching binaries on Linux apt installs
# where the unversioned `clang` may not exist.
if [ -z "${CC:-}" ]; then
if [ -x "$LLVM_BIN/clang" ]; then
export CC="$LLVM_BIN/clang"
else
CC_CANDIDATE=$(find "$LLVM_BIN" -maxdepth 1 -name 'clang-[0-9]*' -type f -perm -u+x 2>/dev/null | head -1)
[ -n "$CC_CANDIDATE" ] && export CC="$CC_CANDIDATE"
fi
fi
if [ -z "${CXX:-}" ]; then
if [ -x "$LLVM_BIN/clang++" ]; then
export CXX="$LLVM_BIN/clang++"
else
CXX_CANDIDATE=$(find "$LLVM_BIN" -maxdepth 1 -name 'clang++-[0-9]*' -type f -perm -u+x 2>/dev/null | head -1)
[ -n "$CXX_CANDIDATE" ] && export CXX="$CXX_CANDIDATE"
fi
fi
# --- Conan (vendors spdlog, CLI11, nlohmann_json, sqlite3, Z3) ---
echo "Installing Conan dependencies..."
conan install "$ROOT" \
-of "$BUILD_DIR" \
--build=missing \
-s build_type="$(if [ "$BUILD_TYPE" = "debug" ]; then echo Debug; else echo Release; fi)" \
-s compiler.cppstd=23 \
-o "build_tests=True" ||
echo "Conan install had warnings (non-fatal)"
# --- configure ---
# Darwin: z3 and radare2 come from Homebrew/source, not Conan. Symlink
# their .pc files into the build dir because MesonToolchain's
# conan_meson_native.ini sets a scoped pkg_config_path that overrides
# the PKG_CONFIG_PATH env var.
if [ "$OS" = "Darwin" ] && command -v brew >/dev/null 2>&1; then
Z3_PC="$(brew --prefix z3 2>/dev/null)/lib/pkgconfig"
if [ -f "$Z3_PC/z3.pc" ]; then
ln -sf "$Z3_PC/z3.pc" "$BUILD_DIR/z3.pc"
echo " Z3 via pkg-config: $Z3_PC/z3.pc (symlinked to $BUILD_DIR/z3.pc)"
fi
# radare2 may be installed at /usr/local (not Homebrew). The r_core
# package requires its sub-packages (r_config, r_cons, etc.) so symlink
# all r_*.pc files.
if ls /usr/local/lib/pkgconfig/r_*.pc >/dev/null 2>&1; then
ln -sf /usr/local/lib/pkgconfig/r_*.pc "$BUILD_DIR/"
echo " radare2 via pkg-config: /usr/local/lib/pkgconfig/r_*.pc (symlinked)"
fi
fi
# Linux: meson can't find z3.pc by default because libz3-dev installs it
# under /usr/lib/<arch>-linux-gnu/pkgconfig/. Symlink into the conan-
# generated build dir so meson's pkg-config search picks it up. Arch is
# detected at runtime (x86_64-linux-gnu on amd64, aarch64-linux-gnu on
# arm64).
if [ "$OS" = "Linux" ]; then
for d in "/usr/lib/$(uname -m)-linux-gnu" /usr/lib/x86_64-linux-gnu \
/usr/lib/aarch64-linux-gnu /usr/lib; do
if [ -f "$d/pkgconfig/z3.pc" ]; then
ln -sf "$d/pkgconfig/z3.pc" "$BUILD_DIR/z3.pc" || true
break
fi
done
fi
# Meson + Ninja resolution.
#
# Stamp the build dir with the *system* meson the user will invoke later, so a
# bare `meson install` matches it. Capture meson's absolute path BEFORE touching
# the Conan env, then call it explicitly below — Conan ships its own (often
# older) meson via conanbuild.sh, and letting that shadow the system meson is
# exactly what stamps the dir with a version a newer system meson then refuses.
#
# Ninja: prefer a system ninja; only when none exists do we source Conan's env
# to borrow its pinned ninja (still using the system meson by absolute path).
# Guarded against set -u (Conan's env scripts reference vars that may be unset).
MESON_BIN="$(command -v meson || true)"
SOURCED_CONAN_ENV=0
if ! command -v ninja >/dev/null 2>&1 && [ -f "$BUILD_DIR/conanbuild.sh" ]; then
set +u
# shellcheck disable=SC1091
. "$BUILD_DIR/conanbuild.sh"
set -u
SOURCED_CONAN_ENV=1
echo " No system ninja found; borrowing Conan's ninja ($(command -v ninja 2>/dev/null || echo 'not found'))"
fi
# Fall back to whatever meson is now on PATH only if there was no system meson.
[ -z "$MESON_BIN" ] && MESON_BIN="$(command -v meson || echo meson)"
echo " Using meson: $MESON_BIN ($("$MESON_BIN" --version 2>/dev/null || echo '?'))"
echo "Configuring Meson (cpp_std=c++23, buildtype=${BUILD_TYPE})..."
# Capstone-backed ROP gadget discovery: -Dcapstone=auto + --wrap-mode=default.
# auto tries system pkg-config, then builds the vendored CMake wrap
# (subprojects/capstone.wrap); if neither is possible (offline / no cmake) it
# degrades to the r2 /R search with no build failure. --wrap-mode=default is
# required to override the wrap_mode=nofallback that Conan's native file sets,
# which would otherwise block the wrap. Meson prints the resolved status as
# "capstone gadget discovery: enabled|disabled".
"$MESON_BIN" setup "$BUILD_DIR" \
--reconfigure \
--native-file "$BUILD_DIR/conan_meson_native.ini" \
"${MESON_NATIVE_FILES[@]}" \
--wrap-mode=default \
-Dcpp_std=c++23 \
-Dbuildtype="${BUILD_TYPE}" \
-Dbuild-tests=true \
-Dghidra=auto \
-Dcapstone=auto \
-Dllvm-config-path="$LLVM_CONFIG"
# --- build ---
ninja -C "$BUILD_DIR" "$@"
echo ""
echo "Build complete. Binary at $BUILD_DIR/src/cli/fold"
# Surface the resolved optional-backend status (the meson messages above scroll
# past during a long build).
if grep -q 'FOLD_HAVE_GHIDRA' "$BUILD_DIR/build.ninja" 2>/dev/null; then
echo "Ghidra P-Code binary backend: ENABLED (vendored)"
else
echo "Ghidra P-Code binary backend: disabled (r2-disasm fallback)"
fi
if grep -q 'FOLD_HAVE_CAPSTONE' "$BUILD_DIR/build.ninja" 2>/dev/null; then
echo "Capstone ROP gadget discovery: ENABLED"
else
echo "Capstone ROP gadget discovery: disabled (r2 /R fallback) — needs cmake + network to build the wrap, or 'brew install capstone'"
fi
# The dir is stamped with the system meson ($MESON_BIN), so a plain install
# matches it. --no-rebuild avoids needing ninja (sudo's PATH usually lacks it).
echo "Install: sudo $MESON_BIN install -C $BUILD_DIR --no-rebuild"
if [ "$SOURCED_CONAN_ENV" = "1" ]; then
echo " (no system ninja was found — Conan's was borrowed for this build."
echo " For 'meson test' / 'ninja -C $BUILD_DIR' later, install one: brew install ninja)"
fi