2026-06-10 15:32:56 -04:00
#!/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<n; i++) ) ; do
ai = ${ a [i] :- 0 } ; bi = ${ b [i] :- 0 }
ai = ${ ai %%[!0-9]* } ; bi = ${ bi %%[!0-9]* }
ai = ${ ai :- 0 } ; bi = ${ bi :- 0 }
if ( ( 10#$ai > 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 <print>
#include <format>
2026-06-10 16:04:21 -04:00
#include <expected>
2026-06-10 16:05:21 -04:00
int main( ) {
std::println( "ok {}" , 23) ;
std::expected<int, int> test;
}
2026-06-10 15:32:56 -04:00
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