#!/usr/bin/env bash # # install.sh -- GridFire build-dependency checker. # # This script does NOT install anything. It detects your operating system, # checks each build dependency, and prints the exact commands to run if # something is missing or too old. Nothing on your system is modified. # # Dependencies checked: # 1. Meson >= 1.6.0 # 2. Ninja (any recent) # 3. CMake >= 3.16 # 4. pkg-config (or pkgconf) # 5. A C and C++ toolchain: GCC >= 14 OR Clang >= 17 (either is fine), # with a working C++23 standard library # 6. Fortran (gfortran >= 14) -- only when --fortran is passed # # Supported platforms: Linux (apt / dnf / pacman / zypper) and macOS on # Apple Silicon. macOS x86_64 and Windows are NOT supported. # # Usage: # ./install.sh check core dependencies # ./install.sh --fortran also check for a Fortran compiler # ./install.sh --help show this help set -u MESON_MIN="1.6.0" CMAKE_MIN="3.16" GCC_MIN="14" CLANG_MIN="17" GFORTRAN_MIN="14" if [ -t 1 ]; then RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[1;33m' BLUE=$'\033[0;34m'; BOLD=$'\033[1m'; NC=$'\033[0m' else RED=""; GREEN=""; YELLOW=""; BLUE=""; BOLD=""; NC="" fi ok() { printf ' %s[ OK ]%s %s\n' "$GREEN" "$NC" "$1"; } bad() { printf ' %s[FAIL]%s %s\n' "$RED" "$NC" "$1"; } warn() { printf ' %s[WARN]%s %s\n' "$YELLOW" "$NC" "$1"; } info() { printf ' %s[INFO]%s %s\n' "$BLUE" "$NC" "$1"; } hdr() { printf '\n%s%s%s\n' "$BOLD" "$1" "$NC"; } FIX_NOTES="" FAIL_COUNT=0 add_fix() { FIX_NOTES="${FIX_NOTES}$1\n"; } note_fail() { FAIL_COUNT=$((FAIL_COUNT + 1)); } WANT_FORTRAN=0 for arg in "$@"; do case "$arg" in --fortran) WANT_FORTRAN=1 ;; -h|--help) sed -n '2,/^$/p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) printf '%sUnknown argument: %s%s\n' "$RED" "$arg" "$NC"; exit 2 ;; esac done version_ge() { local a b ai bi n i IFS='.' read -ra a <<< "$1" IFS='.' read -ra b <<< "$2" n=${#a[@]}; [ ${#b[@]} -gt "$n" ] && n=${#b[@]} for ((i=0; i 10#$bi)); then return 0; fi if ((10#$ai < 10#$bi)); then return 1; fi done return 0 } extract_version() { printf '%s' "$1" | grep -oE '[0-9]+(\.[0-9]+){0,3}' | head -n1 } OS="$(uname -s)" ARCH="$(uname -m)" PKG_MGR="" DISTRO_NAME="" detect_platform() { case "$OS" in Darwin) if [ "$ARCH" != "arm64" ]; then printf '%sUnsupported platform.%s\n' "$RED" "$NC" printf 'GridFire supports macOS on Apple Silicon (arm64) only; detected macOS on %s.\n' "$ARCH" printf 'macOS x86_64 (Intel) is not supported.\n' exit 1 fi DISTRO_NAME="macOS (Apple Silicon)" if command -v brew >/dev/null 2>&1; then PKG_MGR="brew" else PKG_MGR="brew-missing" fi ;; Linux) if command -v apt-get >/dev/null 2>&1; then PKG_MGR="apt" elif command -v dnf >/dev/null 2>&1; then PKG_MGR="dnf" elif command -v pacman >/dev/null 2>&1; then PKG_MGR="pacman" elif command -v zypper >/dev/null 2>&1; then PKG_MGR="zypper" else PKG_MGR="unknown"; fi if [ -r /etc/os-release ]; then # shellcheck disable=SC1091 DISTRO_NAME="$(. /etc/os-release; printf '%s' "${PRETTY_NAME:-Linux}")" else DISTRO_NAME="Linux" fi ;; *) printf '%sUnsupported platform: %s.%s\n' "$RED" "$OS" "$NC" printf 'GridFire supports Linux and macOS (Apple Silicon). Windows is not supported.\n' printf 'On Windows, use WSL2 with a supported Linux distribution.\n' exit 1 ;; esac } pkg_cmd() { case "$PKG_MGR" in apt) case "$1" in ninja) echo "sudo apt install ninja-build" ;; cmake) echo "sudo apt install cmake" ;; pkgconfig) echo "sudo apt install pkg-config" ;; gcc) echo "sudo apt install g++-14 gcc-14" ;; clang) echo "sudo apt install clang-18 libstdc++-14-dev" ;; gfortran) echo "sudo apt install gfortran-14" ;; esac ;; dnf) case "$1" in ninja) echo "sudo dnf install ninja-build" ;; cmake) echo "sudo dnf install cmake" ;; pkgconfig) echo "sudo dnf install pkgconf-pkg-config" ;; gcc) echo "sudo dnf install gcc-c++" ;; clang) echo "sudo dnf install clang" ;; gfortran) echo "sudo dnf install gcc-gfortran" ;; esac ;; pacman) case "$1" in ninja) echo "sudo pacman -S ninja" ;; cmake) echo "sudo pacman -S cmake" ;; pkgconfig) echo "sudo pacman -S pkgconf" ;; gcc) echo "sudo pacman -S gcc" ;; clang) echo "sudo pacman -S clang" ;; gfortran) echo "sudo pacman -S gcc-fortran" ;; esac ;; zypper) case "$1" in ninja) echo "sudo zypper install ninja" ;; cmake) echo "sudo zypper install cmake" ;; pkgconfig) echo "sudo zypper install pkg-config" ;; gcc) echo "sudo zypper install gcc14-c++" ;; clang) echo "sudo zypper install clang" ;; gfortran) echo "sudo zypper install gcc14-fortran" ;; esac ;; brew|brew-missing) case "$1" in ninja) echo "brew install ninja" ;; cmake) echo "brew install cmake" ;; pkgconfig) echo "brew install pkg-config" ;; gcc) echo "brew install gcc" ;; clang) echo "brew install llvm # then use /opt/homebrew/opt/llvm/bin/clang++" ;; gfortran) echo "brew install gcc # provides gfortran" ;; esac ;; *) echo "(install '$1' using your system package manager)" ;; esac } check_meson() { if command -v meson >/dev/null 2>&1; then local v; v="$(extract_version "$(meson --version 2>/dev/null)")" if [ -n "$v" ] && version_ge "$v" "$MESON_MIN"; then ok "Meson $v (>= $MESON_MIN)" else bad "Meson ${v:-?} found but GridFire needs >= $MESON_MIN" note_fail add_fix "Meson upgrade (isolated, recommended):\n pipx upgrade meson || pipx install meson\n or with pip:\n python3 -m pip install --user --upgrade meson" fi else bad "Meson not found (need >= $MESON_MIN)" note_fail add_fix "Install Meson (isolated, recommended):\n pipx install meson\n If pipx is unavailable:\n python3 -m pip install --user meson\n (pipx itself: ${PKG_MGR/brew-missing/brew} install pipx; on Linux: python3 -m pip install --user pipx)" fi } check_ninja() { if command -v ninja >/dev/null 2>&1; then ok "Ninja $(extract_version "$(ninja --version 2>/dev/null)")" elif command -v samu >/dev/null 2>&1; then ok "samurai (ninja-compatible) found" else bad "Ninja not found" note_fail add_fix "Install Ninja (isolated, recommended):\n pipx inject meson ninja # if meson via pipx\n or:\n python3 -m pip install --user ninja\n or via system packages:\n $(pkg_cmd ninja)" fi } check_cmake() { if command -v cmake >/dev/null 2>&1; then local v; v="$(extract_version "$(cmake --version 2>/dev/null)")" if [ -n "$v" ] && version_ge "$v" "$CMAKE_MIN"; then ok "CMake $v (>= $CMAKE_MIN)" else bad "CMake ${v:-?} found but >= $CMAKE_MIN required (SUNDIALS/CppAD subprojects)" note_fail add_fix "Upgrade CMake:\n $(pkg_cmd cmake)\n or isolated: python3 -m pip install --user --upgrade cmake" fi else bad "CMake not found (required to build the SUNDIALS and CppAD subprojects)" note_fail add_fix "Install CMake:\n $(pkg_cmd cmake)\n or isolated: python3 -m pip install --user cmake" fi } check_pkgconfig() { if command -v pkg-config >/dev/null 2>&1; then ok "pkg-config $(extract_version "$(pkg-config --version 2>/dev/null)")" elif command -v pkgconf >/dev/null 2>&1; then ok "pkgconf $(extract_version "$(pkgconf --version 2>/dev/null)") (pkg-config compatible)" else bad "pkg-config not found" note_fail add_fix "Install pkg-config:\n $(pkg_cmd pkgconfig)" fi } probe_cxx23() { local cxx="$1" dir src out rc command -v "$cxx" >/dev/null 2>&1 || return 2 dir="$(mktemp -d 2>/dev/null)" || return 3 src="$dir/gf_probe.cpp" out="$dir/gf_probe.out" cat > "$src" <<'EOF' #include #include #include int main() { std::println("ok {}", 23); std::expected test; } EOF "$cxx" -std=c++23 "$src" -o "$out" >/dev/null 2>&1 rc=$? rm -rf "$dir" return $rc } check_toolchain() { local found_ok=0 detail="" local candidates=() [ -n "${CXX:-}" ] && candidates+=("$CXX") candidates+=(clang++ clang++-21 clang++-20 clang++-19 clang++-18 clang++-17 g++ g++-16 g++-15 g++-14) if [ "$OS" = "Darwin" ]; then candidates+=(/opt/homebrew/opt/llvm/bin/clang++ /usr/local/opt/llvm/bin/clang++) fi local seen="" cxx ver id minreq for cxx in "${candidates[@]}"; do command -v "$cxx" >/dev/null 2>&1 || continue local rp; rp="$(command -v "$cxx")" case " $seen " in *" $rp "*) continue ;; esac seen="$seen $rp" ver="$("$cxx" --version 2>/dev/null | head -n1)" if printf '%s' "$ver" | grep -qiE 'clang'; then id="clang"; minreq="$CLANG_MIN" elif printf '%s' "$ver" | grep -qiE 'g\+\+|gcc|free software foundation'; then id="gcc"; minreq="$GCC_MIN" else id="unknown"; minreq="" fi local vnum; vnum="$(extract_version "$ver")" if probe_cxx23 "$cxx"; then if [ -n "$minreq" ] && [ -n "$vnum" ] && ! version_ge "$vnum" "$minreq"; then warn "$cxx ($id $vnum): C++23 probe passed but below tested floor ($minreq) -- usable, upgrade if you hit errors" else ok "$cxx ($id ${vnum:-?}): C++23 standard library working" fi found_ok=1 [ -z "${WORKING_CXX:-}" ] && WORKING_CXX="$rp" else detail="${detail}\n - $cxx ($id ${vnum:-?}): present but failed the C++23 compile probe" fi done local have_cc=0 for c in "${CC:-}" cc gcc clang; do [ -n "$c" ] && command -v "$c" >/dev/null 2>&1 && { have_cc=1; break; } done if [ "$found_ok" -eq 1 ] && [ "$have_cc" -eq 1 ]; then ok "C toolchain present" [ -n "${WORKING_CXX:-}" ] && info "Suggested: CXX=$WORKING_CXX meson setup build" return fi if [ "$found_ok" -ne 1 ]; then bad "No C++ compiler with working C++23 support found (need GCC >= $GCC_MIN or Clang >= $CLANG_MIN)" note_fail [ -n "$detail" ] && printf ' Compilers seen but rejected:%b\n' "$detail" fi if [ "$have_cc" -ne 1 ]; then bad "No C compiler found" note_fail fi local fix="Install a C/C++ toolchain (Clang >= $CLANG_MIN or GCC >= $GCC_MIN):\n $(pkg_cmd clang)\n or GCC:\n $(pkg_cmd gcc)" # local fix="Install a C/C++ toolchain (GCC >= $GCC_MIN or Clang >= $CLANG_MIN):\n $(pkg_cmd gcc)\n or Clang:\n $(pkg_cmd clang)" if [ "$OS" = "Linux" ]; then fix="$fix\n Note: on Linux, Clang uses the system libstdc++ -- if a new Clang fails the\n C++23 probe, install a newer libstdc++ (e.g. via g++-14) and Clang will use it." elif [ "$OS" = "Darwin" ]; then fix="$fix\n Apple Clang: update Xcode / Command Line Tools to Xcode 16+\n xcode-select --install (or update via the App Store)" fi add_fix "$fix" } check_fortran() { [ "$WANT_FORTRAN" -eq 1 ] || return 0 if command -v gfortran >/dev/null 2>&1; then local v; v="$(extract_version "$(gfortran --version 2>/dev/null | head -n1)")" if [ -n "$v" ] && version_ge "$v" "$GFORTRAN_MIN"; then ok "gfortran $v (>= $GFORTRAN_MIN)" else bad "gfortran ${v:-?} found but >= $GFORTRAN_MIN required" note_fail add_fix "Upgrade gfortran:\n $(pkg_cmd gfortran)" fi else bad "gfortran not found (required by --fortran / -Dbuild_fortran=true)" note_fail add_fix "Install gfortran:\n $(pkg_cmd gfortran)\n (GridFire only supports gfortran; other Fortran compilers need -Dunsafe_fortran=true)" fi } printf '%s======================================================%s\n' "$BOLD" "$NC" printf '%s GridFire build-dependency check%s\n' "$BOLD" "$NC" printf '%s======================================================%s\n' "$BOLD" "$NC" detect_platform info "Platform: $DISTRO_NAME ($ARCH)" case "$PKG_MGR" in brew-missing) warn "Homebrew not found. Install it first: https://brew.sh" warn "All macOS remediation commands below assume Homebrew." ;; unknown) warn "Could not identify a supported package manager (apt/dnf/pacman/zypper)." ;; *) info "Package manager: $PKG_MGR" ;; esac hdr "Build tools" check_meson check_ninja check_cmake check_pkgconfig hdr "Compiler toolchain" check_toolchain if [ "$WANT_FORTRAN" -eq 1 ]; then hdr "Fortran (requested via --fortran)" check_fortran fi printf '\n%s======================================================%s\n' "$BOLD" "$NC" if [ "$FAIL_COUNT" -eq 0 ]; then printf '%s All dependencies satisfied.%s\n' "$GREEN" "$NC" printf '%s======================================================%s\n' "$BOLD" "$NC" printf '\nYou can build GridFire with:\n\n' if [ -n "${WORKING_CXX:-}" ]; then printf ' CXX=%s meson setup build\n' "$WORKING_CXX" else printf ' meson setup build\n' fi printf ' meson compile -C build\n' printf ' sudo meson install -C build\n\n' exit 0 else printf '%s %d dependency problem(s) found -- see below.%s\n' "$RED" "$FAIL_COUNT" "$NC" printf '%s======================================================%s\n' "$BOLD" "$NC" printf '\n%sThis script does not install anything. Run the commands below yourself:%s\n\n' "$BOLD" "$NC" printf '%b\n' "$FIX_NOTES" printf 'Re-run %s./install.sh%s after installing to confirm.\n\n' "$BOLD" "$NC" exit 1 fi