From 203040cd7f21e59db2d2752f9e96d0b4302586be Mon Sep 17 00:00:00 2001 From: Emily Boudreaux Date: Wed, 10 Jun 2026 15:32:56 -0400 Subject: [PATCH] build(check): added check removed install added check.sh to check dependencies --- check.sh | 388 ++++++++++++++ install-tui.sh | 3 - install.sh | 1378 ------------------------------------------------ notes.txt | 8 - 4 files changed, 388 insertions(+), 1389 deletions(-) create mode 100755 check.sh delete mode 100755 install-tui.sh delete mode 100755 install.sh delete mode 100644 notes.txt diff --git a/check.sh b/check.sh new file mode 100755 index 00000000..b3e47845 --- /dev/null +++ b/check.sh @@ -0,0 +1,388 @@ +#!/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 +int main() { std::println("ok {}", 23); } +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 \ No newline at end of file diff --git a/install-tui.sh b/install-tui.sh deleted file mode 100755 index 5e344919..00000000 --- a/install-tui.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -./install.sh --tui \ No newline at end of file diff --git a/install.sh b/install.sh deleted file mode 100755 index 0886e1ee..00000000 --- a/install.sh +++ /dev/null @@ -1,1378 +0,0 @@ -#!/usr/bin/env bash -# -# install.sh - Comprehensive interactive installation script for GridFire. -# -# This script performs the following actions: -# 1. Checks for essential system dependencies against required minimum versions. -# 2. If run with the --tui flag, it provides a comprehensive text-based user interface -# to select and install dependencies, configure the build, and run build steps. -# 3. If run without flags, it prompts the user interactively for each missing dependency. -# 4. Provides detailed installation instructions for various Linux distributions and macOS. -# 5. Once all dependencies are met, it can run the meson setup, compile, install, and test commands. -# 6. Logs all operations to a file for easy debugging. - -set -o pipefail - -# --- Default Configuration --- -LOGFILE="GridFire_Installer.log" -NOTES_FILE="notes.txt" -CONFIG_FILE="gridfire_build.conf" -VENV_DIR=".venv" -MIN_GCC_VER="13.0.0" -MIN_CLANG_VER="16.0.0" -MIN_MESON_VER="1.5.0" -BOOST_CHECKED=false -BOOST_OKAY=true -USING_VENV=false - -# --- Build Configuration Globals --- -BUILD_DIR="build" -INSTALL_PREFIX="/usr/local" -MESON_BUILD_TYPE="release" -MESON_LOG_LEVEL="info" -MESON_PKG_CONFIG="true" -MESON_NUM_CORES=$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1) -C_COMPILER="" -CC_COMPILER="" -FC_COMPILER="" -declare -A VALID_COMPILERS -declare -A MESON_ADVANCED_OPTS -# --- Initialize default advanced options --- -MESON_ADVANCED_OPTS["b_lto"]="false" -MESON_ADVANCED_OPTS["strip"]="false" -MESON_ADVANCED_OPTS["unity"]="off" -MESON_ADVANCED_OPTS["warning_level"]="1" -MESON_ADVANCED_OPTS["backend"]="ninja" -MESON_ADVANCED_OPTS["werror"]="false" -MESON_ADVANCED_OPTS["b_pch"]="true" -MESON_ADVANCED_OPTS["b_coverage"]="false" -MESON_ADVANCED_OPTS["default_library"]="shared" -MESON_ADVANCED_OPTS["optimization"]=2 - - -# --- ANSI Color Codes --- -RED="\033[0;31m" -GREEN="\033[0;32m" -YELLOW="\033[0;33m" -BLUE="\033[0;34m" -NC="\033[0m" # No Color - -# --- Logging and Utility Functions --- - -# Log a message to stdout and to the log file. -log() { - local message="$1" - # Print the colored message to the terminal. - echo -e "${message}" - # Strip ANSI color codes and append to the log file. - echo -e "${message}" | sed 's/\x1B\[[0-9;]*[mK]//g' >> "$LOGFILE" -} - -# Version comparison function. Returns 0 if v1=v2, 1 if v1>v2, 2 if v1 len2 )); then - for ((i=len2; i len1 )); then - for ((i=len1; i p2 )); then return 1; fi - if (( p1 < p2 )); then return 2; fi - done - - return 0 -} - -# Prompt the user for a yes/no answer. -prompt_yes_no() { - local prompt_msg="$1" - local answer - while true; do - read -p "$(echo -e "${YELLOW}${prompt_msg}${NC}") " answer - case "$answer" in - [Yy]* ) return 0;; - [Nn]* ) return 1;; - * ) echo "Please answer yes or no.";; - esac - done -} - - -# Show the help message and exit. -show_help() { - echo "Usage: ./install.sh [options]" - echo - echo "This script checks for dependencies, installs them, and builds the project." - echo - echo "Options:" - echo " --tui Run in Text-based User Interface mode for interactive dependency installation and build control." - echo " --help, -h Show this help message and exit." - echo " --clean Remove the build directory and log file before starting." - echo " --config FILE Load a configuration file on startup." - echo - echo "The script will automatically detect your OS and suggest the correct package manager commands." - echo "All output is logged to ${LOGFILE}." -} - -# --- OS Detection --- -OS_NAME=$(uname -s) -DISTRO_ID="unknown" -if [ -f /etc/os-release ]; then - . /etc/os-release - DISTRO_ID=$ID -fi -if [[ "$OS_NAME" == "Darwin" ]]; then - OS_NAME="macOS" -fi - -# --- Dependency Check Functions --- - -check_command() { - command -v "$1" &>/dev/null -} - -is_externally_managed() { - # Check for the PEP 668 marker file - local py_prefix - py_prefix=$(python3 -c "import sysconfig; print(sysconfig.get_path(\"stdlib\"))") - if [ -f "$py_prefix/EXTERNALLY-MANAGED" ]; then - log "${YELLOW}Python is externally managed${NC}" - USING_VENV=true - return 0 # 0 means true in bash - else - log "${YELLOW}Python is not externally managed${NC}" - return 1 # 1 means false - fi -} - -get_pip_cmd() { - if [ -d "$VENV_DIR" ]; then - echo "$VENV_DIR/bin/pip" - else - echo "python3 -m pip" - fi -} - -get_meson_cmd() { - if [ -d "$VENV_DIR" ]; then - echo "$VENV_DIR/bin/meson" - else - echo "meson" - fi -} - -set_compilers() { - if [[ "$CC_COMPILER" == *"clang++"* ]]; then - C_COMPILER=$(echo "$CC_COMPILER" | sed 's/clang++/clang/') - # Try to find a corresponding flang or fallback to gfortran - local fc_ver=$(echo "$C_COMPILER" | grep -oE '[0-9]+') - if check_command "flang-$fc_ver"; then - FC_COMPILER="flang-$fc_ver" - elif check_command "flang"; then - FC_COMPILER="flang" - elif check_command "gfortran"; then - FC_COMPILER="gfortran" - else - FC_COMPILER="" - fi - elif [[ "$CC_COMPILER" == *"g++"* ]]; then - C_COMPILER=$(echo "$CC_COMPILER" | sed 's/g++/gcc/') - FC_COMPILER=$(echo "$CC_COMPILER" | sed 's/g++/gfortran/') - if ! check_command "$FC_COMPILER"; then - # Fallback to generic gfortran if versioned one not found - if check_command "gfortran"; then - FC_COMPILER="gfortran" - else - FC_COMPILER="" - fi - fi - else - C_COMPILER="" - FC_COMPILER="" - fi -} - -check_compiler() { - VALID_COMPILERS=() - local potential_gccs=("g++-14" "g++-13" "g++") - local potential_clangs=("clang++-17" "clang++-16" "clang++") - - for cc in "${potential_gccs[@]}"; do - if check_command "$cc"; then - local ver; ver=$($cc -dumpversion | grep -oE '[0-9]+(\.[0-9]+)*' | head -n1) - if [[ -n "$ver" ]]; then - vercomp "$ver" "$MIN_GCC_VER" - if [[ $? -ne 2 ]]; then - VALID_COMPILERS["g++ ($ver)"]="$cc" - break # Found a valid one, stop searching for g++ - fi - fi - fi - done - - for cc in "${potential_clangs[@]}"; do - if check_command "$cc"; then - local ver; ver=$($cc --version | head -n1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1) - if [[ -n "$ver" ]]; then - vercomp "$ver" "$MIN_CLANG_VER" - if [[ $? -ne 2 ]]; then - VALID_COMPILERS["clang++ ($ver)"]="$cc" - break # Found a valid one - fi - fi - fi - done - - if [ ${#VALID_COMPILERS[@]} -gt 0 ]; then - if ! [[ " ${VALID_COMPILERS[*]} " =~ " ${CC_COMPILER} " ]]; then - # Correctly get the first value from the associative array - local keys=("${!VALID_COMPILERS[@]}") - local first_key="${keys[0]}" - CC_COMPILER="${VALID_COMPILERS[$first_key]}" - fi - set_compilers - log "${GREEN}[OK] Found valid C++ compiler(s). Using C: '${C_COMPILER}', C++: '${CC_COMPILER}', FC: '${FC_COMPILER}'.${NC}" - return 0 - else - log "${RED}[FAIL] No valid C++ compiler found (GCC >= ${MIN_GCC_VER} or Clang >= ${MIN_CLANG_VER}).${NC}" - CC_COMPILER="" - C_COMPILER="" - FC_COMPILER="" - return 1 - fi -} - -check_pip() { - if python3 -m pip --version &>/dev/null; then - log "${GREEN}[OK] Found pip.${NC}" - return 0 - else - log "${RED}[FAIL] pip not found.${NC}" - return 1 - fi -} - -check_python_dev() { - if check_command python3 && python3-config --includes &>/dev/null; then - log "${GREEN}[OK] Found Python 3 development headers.${NC}" - return 0 - else - log "${RED}[FAIL] Python 3 development headers not found.${NC}" - return 1 - fi -} - -check_meson_python() { - if [ -d "$VENV_DIR" ]; then - if "$VENV_DIR/bin/python3" -c "import mesonpy" &>/dev/null; then - log "${GREEN}[OK] Found meson-python package (in venv).${NC}" - return 0 - fi - fi - if python3 -c "import mesonpy" &>/dev/null; then - log "${GREEN}[OK] Found meson-python package.${NC}" - return 0 - else - log "${RED}[FAIL] meson-python package not found.${NC}" - return 1 - fi -} - -check_cmake() { - if check_command cmake; then - log "${GREEN}[OK] Found CMake: $(cmake --version | head -n1)${NC}" - return 0 - else - log "${RED}[FAIL] CMake not found.${NC}" - return 1 - fi -} - -check_meson() { - local meson_cmd="meson" - if [ -d "$VENV_DIR" ]; then - meson_cmd="$VENV_DIR/bin/meson" - fi - - if check_command "$meson_cmd"; then - local ver; ver=$($meson_cmd --version) - vercomp "$ver" "$MIN_MESON_VER" - if [[ $? -ne 2 ]]; then - log "${GREEN}[OK] Found Meson ${ver}.${NC}" - return 0 - else - log "${RED}[FAIL] Meson version ${ver} is too old. Need >= ${MIN_MESON_VER}.${NC}" - return 1 - fi - else - log "${RED}[FAIL] Meson not found.${NC}" - return 1 - fi -} - -check_ninja() { - if check_command ninja; then - log "${GREEN}[OK] Found Ninja: $(ninja --version | head -n1)${NC}" - return 0 - else - log "${RED}[FAIL] Ninja not found.${NC}" - return 1 - fi -} - -check_venv() { - if python3 -m venv --help >/dev/null 2>&1; then - return 0 - else - return 1 - fi -} - -check_boost() { - log "${BLUE}[Info] Performing comprehensive check for compatible Boost library...${NC}" - if [ -z "$CC_COMPILER" ]; then - log "${YELLOW}[Warn] C++ compiler not set, skipping comprehensive Boost check.${NC}" - return 1 - fi - - local test_dir="boost-compile-test" - rm -rf "$test_dir" && mkdir -p "$test_dir" - - # Create the test C++ file that uses problematic headers - cat > "$test_dir/test_boost.cpp" < -#include - -using state_type = boost::numeric::ublas::vector; - -void dummy_system(const state_type &x, state_type &dxdt, double t) { - if (x.size() > 0 && dxdt.size() > 0) { - dxdt[0] = 1.0; - } -} - -int main() { - boost::numeric::ublas::vector v(3); - v[0] = 1.23; - state_type x(1); - x[0] = 1.0; - boost::numeric::odeint::runge_kutta4 stepper; - stepper.do_step(dummy_system, x, 0.0, 0.1); - return 0; -} -EOF - - # Create the test meson.build file - cat > "$test_dir/meson.build" <> "$LOGFILE" 2>&1 && $mesonCMD compile -C "$test_dir/build" >> "$LOGFILE" 2>&1; then - log "${GREEN}[Success] Boost library is compatible with the current compiler and C++23 standard.${NC}" - rm -rf "$test_dir" - return 0 - else - log "${RED}[FAIL] System Boost library is NOT compatible with C++23 or is missing required components.${NC}" - log "${YELLOW}[Info] This is common on systems like Ubuntu with older Boost versions. A manual install of a newer Boost is likely required.${NC}" - rm -rf "$test_dir" - return 1 - fi -} - - -# --- Dependency Installation Functions --- - -get_compiler_install_cmd() { - local compiler_to_install="$1" # e.g., g++ or clang++ - local cmd="" - case "$OS_NAME" in - "macOS") - local brew_cmd; brew_cmd=$(command -v brew) - case "$compiler_to_install" in - "g++") cmd="$brew_cmd install gcc" ;; - "clang++") cmd="$brew_cmd install llvm" ;; # llvm provides clang - esac - ;; - "Linux") - case "$DISTRO_ID" in - "ubuntu"|"debian"|"linuxmint") - case "$compiler_to_install" in - "g++") cmd="sudo apt-get install -y g++-13 gfortran-13" ;; - "clang++") cmd="sudo apt-get install -y clang-16" ;; - esac - ;; - "fedora") - # Fedora usually has recent versions in main repos - case "$compiler_to_install" in - "g++") cmd="sudo dnf install -y gcc-c++ gcc-gfortran" ;; - "clang++") cmd="sudo dnf install -y clang" ;; - esac - ;; - "arch"|"manjaro") - # Arch is rolling release, should be fine - case "$compiler_to_install" in - "g++") cmd="sudo pacman -S --noconfirm gcc gcc-fortran" ;; - "clang++") cmd="sudo pacman -S --noconfirm clang" ;; - esac - ;; - esac - ;; - esac - echo "$cmd" -} - - -get_install_cmd() { - local dep_name="$1" - local cmd="" - case "$OS_NAME" in - "macOS") - [ ! -f /opt/homebrew/bin/brew ] && [ ! -f /usr/local/bin/brew ] && log "${YELLOW}Homebrew not found. Please install from https://brew.sh${NC}" - local brew_cmd - brew_cmd=$(command -v brew) - case "$dep_name" in - "compiler") cmd="$brew_cmd install gcc llvm" ;; # Install both - "python-dev") cmd="$brew_cmd install python3" ;; - "pip") cmd="python3 -m ensurepip --upgrade" ;; - "meson-python") cmd="python3 -m pip install meson-python" ;; - "meson") cmd="python3 -m pip install --upgrade meson" ;; - "cmake") cmd="$brew_cmd install cmake" ;; - "boost") cmd="$brew_cmd install boost" ;; - "dialog") cmd="$brew_cmd install dialog" ;; - "ninja") cmd="$brew_cmd install ninja" ;; - "python3-venv") cmd="$brew_cmd install python3" ;; # Homebrew installs venv with Python 3 - esac - ;; - "Linux") - case "$DISTRO_ID" in - "ubuntu"|"debian"|"linuxmint") - case "$dep_name" in - "compiler") cmd="sudo apt-get install -y g++-13 gfortran-13 clang-16" ;; - "python-dev") cmd="sudo apt-get install -y python3-dev" ;; - "pip") cmd="sudo apt-get install -y python3-pip" ;; - "meson-python") cmd="python3 -m pip install meson-python" ;; - "meson") cmd="python3 -m pip install --upgrade meson" ;; - "cmake") cmd="sudo apt-get install -y cmake" ;; - "boost") cmd="sudo apt-get install -y libboost-all-dev" ;; - "dialog") cmd="sudo apt-get install -y dialog" ;; - "ninja") cmd="sudo apt-get install -y ninja-build" ;; - "python3-venv") cmd="sudo apt-get install -y python3-venv" ;; # Ensure venv is available - esac - ;; - "fedora") - case "$dep_name" in - "compiler") cmd="sudo dnf install -y gcc-c++ gcc-gfortran clang" ;; - "python-dev") cmd="sudo dnf install -y python3-devel" ;; - "pip") cmd="sudo dnf install -y python3-pip" ;; - "meson-python") cmd="python3 -m pip install meson-python" ;; - "meson") cmd="python3 -m pip install --upgrade meson" ;; - "cmake") cmd="sudo dnf install -y cmake" ;; - "boost") cmd="sudo dnf install -y boost-devel" ;; - "dialog") cmd="sudo dnf install -y dialog" ;; - "ninja") cmd="sudo dnf install -y ninja-build" ;; - "python3-venv") cmd="sudo dnf install -y python3-venv" ;; # Ensure venv is available - esac - ;; - "arch"|"manjaro") - case "$dep_name" in - "compiler") cmd="sudo pacman -S --noconfirm gcc gcc-fortran clang" ;; - "python-dev") cmd="sudo pacman -S --noconfirm python" ;; - "pip") cmd="sudo pacman -S --noconfirm python-pip" ;; - "meson-python") cmd="python3 -m pip install meson-python" ;; - "meson") cmd="python3 -m pip install --upgrade meson" ;; - "cmake") cmd="sudo pacman -S --noconfirm cmake" ;; - "boost") cmd="sudo pacman -S --noconfirm boost" ;; - "dialog") cmd="sudo pacman -S --noconfirm dialog" ;; - "ninja") cmd="sudo pacman -S --noconfirm ninja" ;; - "python3-venv") cmd="sudo pacman -S --noconfirm python-virtualenv" ;; # Ensure venv is available - esac - ;; - *) log "${YELLOW}[Warn] Unsupported Linux distribution: ${DISTRO_ID}.${NC}" ;; - esac - ;; - *) log "${YELLOW}[Warn] Unsupported OS: ${OS_NAME}.${NC}" ;; - esac - echo "$cmd" -} - -# --- Build Functions --- - -run_meson_setup() { - log "\n${BLUE}--- Configuring Meson Build ---${NC}" - if [ ! -f "meson.build" ]; then - log "${RED}[FATAL] meson.build file not found. Cannot proceed.${NC}"; return 1; - fi - if [ -z "$CC_COMPILER" ] || [ -z "$C_COMPILER" ]; then - log "${RED}[FATAL] No valid C/C++ compiler selected. Configure one first.${NC}"; return 1; - fi - local reconfigure_flag="" - if [ -d "$BUILD_DIR" ]; then - reconfigure_flag="--reconfigure" - log "${YELLOW}[Info] Existing build directory found. Will reconfigure.${NC}" - fi - - local meson_opts=() - meson_opts+=("-Dbuildtype=${MESON_BUILD_TYPE}") - meson_opts+=("-Dlog_level=${MESON_LOG_LEVEL}") - meson_opts+=("-Dpkg-config=${MESON_PKG_CONFIG}") - meson_opts+=("--prefix=${INSTALL_PREFIX}") - - # Add advanced options - for key in "${!MESON_ADVANCED_OPTS[@]}"; do - meson_opts+=("-D${key}=${MESON_ADVANCED_OPTS[$key]}") - done - - log "${BLUE}[Info] Using C compiler: ${C_COMPILER}${NC}" - log "${BLUE}[Info] Using C++ compiler: ${CC_COMPILER}${NC}" - log "${BLUE}[Info] Using Fortran compiler: ${FC_COMPILER}${NC}" - log "${BLUE}[Info] Running meson setup with options: ${meson_opts[*]}${NC}" - - local mesonCMD=$(get_meson_cmd) - log "${BLUE}[Info] Using meson at ${mesonCMD}${NC}" - # Set CC, CXX, and FC environment variables for the meson command - if ! CC="${C_COMPILER}" CXX="${CC_COMPILER}" FC="${FC_COMPILER}" $mesonCMD setup "${BUILD_DIR}" "${meson_opts[@]}" ${reconfigure_flag}; then - log "${RED}[FATAL] Meson setup failed. See log for details.${NC}"; return 1; - fi - log "${GREEN}[Success] Meson setup complete.${NC}" -} - -run_meson_compile() { - log "\n${BLUE}--- Compiling Project ---${NC}" - if [ ! -d "$BUILD_DIR" ]; then - log "${RED}[FATAL] Build directory not found. Run setup first.${NC}"; return 1; - fi - log "${BLUE}[Info] Running meson compile with ${MESON_NUM_CORES} cores...${NC}" - - local mesonCMD=$(get_meson_cmd) - log "${BLUE}[Info] Using meson at ${mesonCMD}${NC}" - - if ! $mesonCMD compile -C "${BUILD_DIR}" -j "${MESON_NUM_CORES}"; then - log "${RED}[FATAL] Meson compile failed. See log for details.${NC}"; return 1; - fi - log "${GREEN}[Success] Meson compile complete.${NC}" -} - -run_meson_install() { - log "\n${BLUE}--- Installing Project ---${NC}" - if [ ! -d "$BUILD_DIR" ]; then - log "${RED}[FATAL] Build directory not found. Run setup and compile first.${NC}"; return 1; - fi - log "${BLUE}[Info] Running meson install (prefix: ${INSTALL_PREFIX})...${NC}" - - local mesonCMD=$(get_meson_cmd) - log "${BLUE}[Info] Using meson at ${mesonCMD}${NC}" - - if ! sudo $mesonCMD install -C "${BUILD_DIR}"; then - log "${RED}[FATAL] Meson install failed. See log for details.${NC}"; return 1; - fi - log "${GREEN}[Success] Meson install complete.${NC}" -} - -run_meson_tests() { - log "\n${BLUE}--- Running Tests ---${NC}" - if [ ! -d "$BUILD_DIR" ]; then - log "${RED}[FATAL] Build directory not found. Run setup and compile first.${NC}"; return 1; - fi - log "${BLUE}[Info] Running meson test...${NC}" - - local mesonCMD=$(get_meson_cmd) - log "${BLUE}[Info] Using meson at ${mesonCMD}${NC}" - - if ! $mesonCMD test -C "${BUILD_DIR}"; then - log "${RED}[FATAL] Meson tests failed. See log for details.${NC}"; return 1; - fi - log "${GREEN}[Success] Tests passed.${NC}" -} - -# --- TUI Functions --- - -check_dialog_installed() { - if ! check_command dialog; then - log "${YELLOW}[Warn] The 'dialog' utility is required for TUI mode.${NC}" - local install_cmd - install_cmd=$(get_install_cmd "dialog") - if [ -n "$install_cmd" ]; then - if prompt_yes_no "Attempt to install it now? (y/n):"; then - eval "$install_cmd" - if ! check_command dialog; then - log "${RED}[FATAL] Failed to install 'dialog'. Cannot run in TUI mode.${NC}" - return 1 - fi - else - log "${RED}[FATAL] Cannot run in TUI mode without 'dialog'. Exiting.${NC}" - return 1 - fi - else - log "${RED}[FATAL] Cannot auto-install 'dialog'. Please install it manually.${NC}" - return 1 - fi - fi - return 0 -} - -ensure_venv() { - if [ ! -d "$VENV_DIR" ]; then - if dialog --title "Virtual Environment" --yesno "A local Python virtual environment ('${VENV_DIR}') is required for this action. Create it now?" 8 70; then - log "${BLUE}[Info] Creating Python virtual environment in '${VENV_DIR}'...${NC}" - if ! python3 -m venv "$VENV_DIR"; then - dialog --msgbox "Failed to create virtual environment. Please ensure 'python4-venv' is installed." 8 60 - return 1 - fi - log "${GREEN}[Success] Virtual environment created.${NC}" - else - return 1 - fi - fi - return 0 -} - -run_dependency_installer_tui() { - # This function now just calls the check functions to populate status - declare -A DEP_STATUS - check_compiler >/dev/null; DEP_STATUS[compiler]=$? - check_pip >/dev/null; DEP_STATUS[pip]=$? - check_python_dev >/dev/null; DEP_STATUS[python-dev]=$? - check_venv >/dev/null; DEP_STATUS[python3-venv]=$? - check_meson_python >/dev/null; DEP_STATUS[meson-python]=$? - check_cmake >/dev/null; DEP_STATUS[cmake]=$? - check_meson >/dev/null; DEP_STATUS[meson]=$? - check_ninja >/dev/null; DEP_STATUS[ninja]=$? - if [[ $BOOST_OKAY = false ]]; then - DEP_STATUS[boost]=1 # Force boost to be "not okay" if previous check failed - fi - - log "${BLUE}[Info] compiler status code: ${DEP_STATUS[compiler]}${NC}" - log "${BLUE}[Info] pip status code: ${DEP_STATUS[pip]}${NC}" - log "${BLUE}[Info] python-dev status code: ${DEP_STATUS[python-dev]}${NC}" - log "${BLUE}[Info] python3-venv status code: ${DEP_STATUS[python3-venv]}${NC}" - log "${BLUE}[Info] meson-python status code: ${DEP_STATUS[meson-python]}${NC}" - log "${BLUE}[Info] cmake status code: ${DEP_STATUS[cmake]}${NC}" - log "${BLUE}[Info] meson status code: ${DEP_STATUS[meson]}${NC}" - log "${BLUE}[Info] ninja status code: ${DEP_STATUS[ninja]}${NC}" - log "${BLUE}[Info] boost status code: ${DEP_STATUS[boost]}${NC}" - - local choices - choices=$(dialog --clear --backtitle "Project Dependency Installer" \ - --title "Install System Dependencies" \ - --checklist "Select dependencies to install. Already found dependencies are unchecked." 20 70 7 \ - "compiler" "C++ Compilers (g++, clang++)" "$([[ ${DEP_STATUS[compiler]} -ne 0 ]] && echo "on" || echo "off")" \ - "pip" "Python Package Installer (pip)" "$([[ ${DEP_STATUS[pip]} -ne 0 ]] && echo "on" || echo "off")" \ - "python-dev" "Python 3 Dev Headers" "$([[ ${DEP_STATUS[python-dev]} -ne 0 ]] && echo "on" || echo "off")" \ - "python3-venv" "Python 3 Virtual Environment (venv)" "$([[ ${DEP_STATUS[python3-venv]} -ne 0 ]] && echo "on" || echo "off")" \ - "meson-python" "meson-python (for Python bindings)" "$([[ ${DEP_STATUS[meson-python]} -ne 0 ]] && echo "on" || echo "off")" \ - "cmake" "CMake" "$([[ ${DEP_STATUS[cmake]} -ne 0 ]] && echo "on" || echo "off")" \ - "meson" "Meson Build System (>=${MIN_MESON_VER})" "$([[ ${DEP_STATUS[meson]} -ne 0 ]] && echo "on" || echo "off")" \ - "ninja" "Ninja Build System" "$([[ ${DEP_STATUS[ninja]} -ne 0 ]] && echo "on" || echo "off")" \ - "boost" "Boost Libraries (system package)" "$([[ ${DEP_STATUS[boost]} -ne 0 ]] && echo "on" || echo "off")" \ - 3>&1 1>&2 2>&3) - - clear - if [ -z "$choices" ]; then log "${YELLOW}[Info] No dependencies selected.${NC}"; return; fi - - for choice in $choices; do - local dep; dep=$(echo "$choice" | tr -d '"') - log "\n${BLUE}--- Installing ${dep} ---${NC}" - - # Handle python packages specially - if [[ "$dep" == "meson-python" || "$dep" == "meson" ]]; then - if is_externally_managed; then - if ! ensure_venv; then - log "${YELLOW}[Skip] User cancelled venv creation. Skipping ${dep} installation.${NC}" - continue - fi - fi - local pip_cmd; pip_cmd=$(get_pip_cmd) - eval "$pip_cmd install --upgrade $dep" 2>&1 | tee -a "$LOGFILE" - else - local install_cmd; install_cmd=$(get_install_cmd "$dep") - if [ -n "$install_cmd" ]; then - eval "$install_cmd" 2>&1 | tee -a "$LOGFILE" - else - log "${RED}[Error] No automatic installation command for '${dep}'. Please install manually.${NC}" - dialog --msgbox "Could not find an automatic installation command for '${dep}' on your system. Please install it manually." 8 60 - fi - fi - done - # Re-run check to update status - check_compiler - BOOST_CHECKED=false # Force re-check of boost -} - -run_python_bindings_tui() { - if ! check_meson_python; then - dialog --msgbox "The 'meson-python' package is required to build Python bindings. Please install it from the 'Install System Dependencies' menu first." 10 70 - return - fi - if [ -z "$CC_COMPILER" ]; then - dialog --msgbox "No C++ compiler is selected. Please configure one from the 'Configure Build Options' menu before building the Python bindings." 10 70 - return - fi - - local python_exec - python_exec=$(command -v python3) - if [ -z "$python_exec" ]; then - dialog --msgbox "Could not find 'python3' executable. Please ensure Python 3 is installed and in your PATH." 8 60 - return - fi - - local pip_cmd="python3 -m pip" - local pip_opts="" - - if is_externally_managed; then - local env_choice - env_choice=$(dialog --clear --backtitle "Python Environment" \ - --title "Externally Managed Environment Detected" \ - --menu "This OS protects its system Python. How would you like to install the bindings?" 15 78 3 \ - "1" "Use a Virtual Environment (Recommended)" \ - "2" "Install to System with --break-system-packages (Advanced)" \ - "3" "Cancel" \ - 3>&1 1>&2 2>&3) - - case "$env_choice" in - 1) - if ! ensure_venv; then return; fi - pip_cmd="$VENV_DIR/bin/pip" - ;; - 2) - pip_opts="--break-system-packages" - ;; - *) - return - ;; - esac - fi - - local choice - choice=$(dialog --clear --backtitle "Python Bindings Installer" \ - --title "Install Python Bindings" \ - --menu "Using Python: ${python_exec}\nUsing C++ Compiler: ${CC_COMPILER}\n\nSelect installation mode:" 15 70 2 \ - "1" "Developer Mode (pip install -e .)" \ - "2" "User Mode (pip install .)" \ - 3>&1 1>&2 2>&3) - - clear - case "$choice" in - 1) - log "${BLUE}[Info] Installing Python bindings in Developer Mode...${NC}" - if ! CC="${C_COMPILER}" CXX="${CC_COMPILER}" FC="${FC_COMPILER}" $pip_cmd install $pip_opts -e . --no-build-isolation -vv; then - log "${RED}[Error] Failed to install Python bindings in developer mode.${NC}" - dialog --msgbox "Developer mode installation failed. Check the log for details." 8 60 - else - log "${GREEN}[Success] Python bindings installed in developer mode.${NC}" - dialog --msgbox "Successfully installed Python bindings in developer mode." 8 60 - fi - ;; - 2) - log "${BLUE}[Info] Installing Python bindings in User Mode...${NC}" - if ! CC="${C_COMPILER}" CXX="${CC_COMPILER}" FC="${FC_COMPILER}" $pip_cmd install $pip_opts .; then - log "${RED}[Error] Failed to install Python bindings in user mode.${NC}" - dialog --msgbox "User mode installation failed. Check the log for details." 8 60 - else - log "${GREEN}[Success] Python bindings installed in user mode.${NC}" - dialog --msgbox "Successfully installed Python bindings in user mode." 8 60 - fi - ;; - *) - log "${YELLOW}[Info] Python binding installation cancelled.${NC}" - ;; - esac -} - -run_compiler_help_tui() { - local compiler_name="$1" - local req_ver="" - local help_text="" - - if [[ "$compiler_name" == "g++" ]]; then - req_ver=$MIN_GCC_VER - help_text="The installer could not automatically install GCC >= ${req_ver}.\n\n" - help_text+="This can happen on older Linux distributions.\n\n" - help_text+="Recommended Solutions:\n" - help_text+="1. (Ubuntu/Debian) Add a PPA with newer compilers:\n" - help_text+=" sudo add-apt-repository ppa:ubuntu-toolchain-r/test\n" - help_text+=" sudo apt-get update\n" - help_text+=" sudo apt-get install g++-13\n\n" - help_text+="2. Download pre-built binaries from a trusted source.\n\n" - help_text+="3. Build GCC from source (advanced)." - - elif [[ "$compiler_name" == "clang++" ]]; then - req_ver=$MIN_CLANG_VER - help_text="The installer could not automatically install Clang >= ${req_ver}.\n\n" - help_text+="This is common on systems like Ubuntu 22.04.\n\n" - help_text+="Recommended Solution (Ubuntu/Debian):\n" - help_text+="Use the official LLVM APT repository. Run this command in your terminal:\n\n" - help_text+=' bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"' - help_text+="\n\nThis script will set up the repository and install the latest version of Clang. After running it, re-run this installer." - fi - - dialog --title "Compiler Installation Failed" --msgbox "$help_text" 25 78 -} - - -run_compiler_selection_tui() { - local gpp_ver; gpp_ver=$(g++ -dumpversion 2>/dev/null | grep -oE '[0-9]+(\.[0-9]+)*' | head -n1) - local clang_ver; clang_ver=$(clang++ --version 2>/dev/null | head -n1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1) - - local gpp_ok=false - local clang_ok=false - if [[ -n "$gpp_ver" ]]; then vercomp "$gpp_ver" "$MIN_GCC_VER"; [[ $? -ne 2 ]] && gpp_ok=true; fi - if [[ -n "$clang_ver" ]]; then vercomp "$clang_ver" "$MIN_CLANG_VER"; [[ $? -ne 2 ]] && clang_ok=true; fi - - if ! $gpp_ok && ! $clang_ok; then - # No valid compilers found - local choices - choices=$(dialog --title "Compiler Installation" --checklist "No valid C++ compiler found. Please select which to install:" 15 70 2 \ - "g++" "GNU C++ Compiler (>=${MIN_GCC_VER})" "on" \ - "clang++" "Clang C++ Compiler (>=${MIN_CLANG_VER}, often faster)" "off" 3>&1 1>&2 2>&3) - if [ -n "$choices" ]; then - for choice in $choices; do - local compiler_to_install; compiler_to_install=$(echo "$choice" | tr -d '"') - local install_cmd; install_cmd=$(get_compiler_install_cmd "$compiler_to_install") - if [ -n "$install_cmd" ]; then - eval "$install_cmd" 2>&1 | tee -a "$LOGFILE" - # Post-install check - check_compiler - if ! check_command "${VALID_COMPILERS[g++*]}" && [[ "$compiler_to_install" == "g++" ]]; then run_compiler_help_tui "g++"; fi - if ! check_command "${VALID_COMPILERS[clang++*]}" && [[ "$compiler_to_install" == "clang++" ]]; then run_compiler_help_tui "clang++"; fi - fi - done - fi - elif ! $gpp_ok && [[ -n "$gpp_ver" ]]; then - # g++ found but too old - if dialog --title "Compiler Update" --yesno "Found g++ version ${gpp_ver}, but require >= ${MIN_GCC_VER}.\n\nAttempt to install a compatible version?" 10 70; then - local install_cmd; install_cmd=$(get_compiler_install_cmd "g++") - if [ -n "$install_cmd" ]; then - eval "$install_cmd" 2>&1 | tee -a "$LOGFILE" - check_compiler - if ! $gpp_ok; then run_compiler_help_tui "g++"; fi - fi - fi - elif ! $clang_ok && [[ -n "$clang_ver" ]]; then - # clang++ found but too old - if dialog --title "Compiler Update" --yesno "Found clang++ version ${clang_ver}, but require >= ${MIN_CLANG_VER}.\n\nAttempt to install a compatible version?" 10 70; then - local install_cmd; install_cmd=$(get_compiler_install_cmd "clang++") - if [ -n "$install_cmd" ]; then - eval "$install_cmd" 2>&1 | tee -a "$LOGFILE" - check_compiler - if ! $clang_ok; then run_compiler_help_tui "clang++"; fi - fi - fi - fi - - # Re-check compilers and let user choose if multiple are available - check_compiler - if [ ${#VALID_COMPILERS[@]} -gt 0 ]; then - local menu_items=() - for name in "${!VALID_COMPILERS[@]}"; do - menu_items+=("$name" "") - done - local compiler_choice_key - compiler_choice_key=$(dialog --title "Select C++ Compiler" --menu "Select the C++ compiler to use:" 15 70 ${#VALID_COMPILERS[@]} "${menu_items[@]}" 3>&1 1>&2 2>&3) - if [ -n "$compiler_choice_key" ]; then - CC_COMPILER="${VALID_COMPILERS[$compiler_choice_key]}" - set_compilers - log "${BLUE}[Config] Set C compiler to: ${C_COMPILER}${NC}" - log "${BLUE}[Config] Set C++ compiler to: ${CC_COMPILER}${NC}" - log "${BLUE}[Config] Set Fortran compiler to: ${FC_COMPILER}${NC}" - fi - else - dialog --msgbox "No valid C++ compiler could be found or installed. Please install one manually that meets the version requirements." 8 70 - fi -} - -run_advanced_options_tui() { - while true; do - local choice - choice=$(dialog --clear --backtitle "Advanced Build Options" \ - --title "Advanced Meson Core & Base Options" \ - --menu "Select an option to configure:" 20 78 10 \ - "1" "Backend (current: ${MESON_ADVANCED_OPTS[backend]})" \ - "2" "Default Library (current: ${MESON_ADVANCED_OPTS[default_library]})" \ - "3" "LTO (Link-Time Opt) (current: ${MESON_ADVANCED_OPTS[b_lto]})" \ - "4" "PCH (Precompiled H) (current: ${MESON_ADVANCED_OPTS[b_pch]})" \ - "5" "Coverage (current: ${MESON_ADVANCED_OPTS[b_coverage]})" \ - "6" "Strip on install (current: ${MESON_ADVANCED_OPTS[strip]})" \ - "7" "Unity build (current: ${MESON_ADVANCED_OPTS[unity]})" \ - "8" "Warning Level (current: ${MESON_ADVANCED_OPTS[warning_level]})" \ - "9" "Warnings as Errors (current: ${MESON_ADVANCED_OPTS[werror]})" \ - "O" "Optimization Level (current: ${MESON_ADVANCED_OPTS[optimization]})" \ - "Q" "Back to main config" \ - 3>&1 1>&2 2>&3) - - case "$choice" in - 1) - local backend_choice - backend_choice=$(dialog --title "Select Backend" --menu "" 15 70 4 \ - "ninja" "Ninja build system" \ - "vs" "Visual Studio" \ - "xcode" "Xcode" \ - "none" "No backend" 3>&1 1>&2 2>&3) - if [ -n "$backend_choice" ]; then MESON_ADVANCED_OPTS["backend"]="$backend_choice"; fi - ;; - 2) - local lib_choice - lib_choice=$(dialog --title "Default Library Type" --menu "" 15 70 3 \ - "shared" "Shared (.so, .dylib)" \ - "static" "Static (.a)" \ - "both" "Build both types" 3>&1 1>&2 2>&3) - if [ -n "$lib_choice" ]; then MESON_ADVANCED_OPTS["default_library"]="$lib_choice"; fi - ;; - 3) - if dialog --title "LTO (Link-Time Optimization)" --yesno "Enable Link-Time Optimization?\n(Can improve performance but increases link time)" 8 70; then - MESON_ADVANCED_OPTS["b_lto"]="true" - else - MESON_ADVANCED_OPTS["b_lto"]="false" - fi - ;; - 4) - if dialog --title "PCH (Precompiled Headers)" --yesno "Use precompiled headers?\n(Can speed up compilation)" 8 70; then - MESON_ADVANCED_OPTS["b_pch"]="true" - else - MESON_ADVANCED_OPTS["b_pch"]="false" - fi - ;; - 5) - if dialog --title "Enable Coverage Tracking" --yesno "Enable code coverage tracking?\n(For generating coverage reports)" 8 70; then - MESON_ADVANCED_OPTS["b_coverage"]="true" - else - MESON_ADVANCED_OPTS["b_coverage"]="false" - fi - ;; - 6) - if dialog --title "Strip on Install" --yesno "Strip binaries on install?\n(Reduces size, removes debug symbols)" 8 70; then - MESON_ADVANCED_OPTS["strip"]="true" - else - MESON_ADVANCED_OPTS["strip"]="false" - fi - ;; - 7) - local unity_choice - unity_choice=$(dialog --title "Unity Build" --menu "" 15 70 3 \ - "on" "Enable for all targets" \ - "subprojects" "Enable for subprojects only" \ - "off" "Disable unity builds" 3>&1 1>&2 2>&3) - if [ -n "$unity_choice" ]; then MESON_ADVANCED_OPTS["unity"]="$unity_choice"; fi - ;; - 8) - local warn_choice - warn_choice=$(dialog --title "Compiler Warning Level" --menu "" 15 70 5 \ - "0" "No warnings" \ - "1" "Normal warnings" \ - "2" "More warnings" \ - "3" "All warnings" \ - "everything" "Pedantic warnings" 3>&1 1>&2 2>&3) - if [ -n "$warn_choice" ]; then MESON_ADVANCED_OPTS["warning_level"]="$warn_choice"; fi - ;; - 9) - if dialog --title "Treat Warnings as Errors" --yesno "Enable -Werror?\n(Build will fail on any compiler warning)" 8 70; then - MESON_ADVANCED_OPTS["werror"]="true" - else - MESON_ADVANCED_OPTS["werror"]="false" - fi - ;; - O) - if dialog --title "Optimization Level" --yesno "Set optimization level?\n(affects performance and debug info)" 8 70; then - local opt_choice - opt_choice=$(dialog --title "Select Optimization Level" --menu "" 15 70 5 \ - "plain" "No optimization at all (no meson flags)" \ - "0" "No optimization (debug)" \ - "g" "optimization for debugging" \ - "1" "Basic optimization (default)" \ - "2" "More optimization" \ - "3" "Full optimization" \ - "s" "Size optimization (strip debug symbols)" 3>&1 1>&2 2>&3) - if [ -n "$opt_choice" ]; then MESON_ADVANCED_OPTS["optimization"]="$opt_choice"; fi - else - MESON_ADVANCED_OPTS["optimization"]="0" - fi - ;; - Q) break ;; - *) break ;; - esac - done -} - - -run_build_config_tui() { - local choice - choice=$(dialog --clear --backtitle "Build Configuration" \ - --title "Configure Build Options" \ - --menu "Select an option to configure:" 20 70 8 \ - "1" "Build Directory (current: ${BUILD_DIR})" \ - "2" "Install Prefix (current: ${INSTALL_PREFIX})" \ - "3" "Manage & Select C/C++/Fortran Compiler" \ - "4" "Build Type (current: ${MESON_BUILD_TYPE})" \ - "5" "Log Level (current: ${MESON_LOG_LEVEL})" \ - "6" "Generate pkg-config (current: ${MESON_PKG_CONFIG})" \ - "7" "Number of Cores (current: ${MESON_NUM_CORES})" \ - "8" "Advanced Build Options" \ - 3>&1 1>&2 2>&3) - - clear - case "$choice" in - 1) - local new_dir - new_dir=$(dialog --title "Set Build Directory" --inputbox "Enter new build directory name:" 10 60 "${BUILD_DIR}" 3>&1 1>&2 2>&3) - if [ -n "$new_dir" ]; then - BUILD_DIR="$new_dir" - log "${BLUE}[Config] Set build directory to: ${BUILD_DIR}${NC}" - fi - ;; - 2) - local new_prefix - new_prefix=$(dialog --title "Set Install Prefix" --inputbox "Enter absolute path for installation prefix:" 10 60 "${INSTALL_PREFIX}" 3>&1 1>&2 2>&3) - if [ -n "$new_prefix" ]; then - INSTALL_PREFIX="$new_prefix" - log "${BLUE}[Config] Set install prefix to: ${INSTALL_PREFIX}${NC}" - fi - ;; - 3) - run_compiler_selection_tui - ;; - 4) - local build_type_choice - build_type_choice=$(dialog --title "Select Build Type" --menu "" 15 70 4 \ - "debug" "No optimizations, with debug symbols" \ - "release" "Optimized for performance" \ - "debugoptimized" "With debug symbols and optimization" \ - "plain" "Custom flags only" \ - 3>&1 1>&2 2>&3) - if [ -n "$build_type_choice" ]; then - MESON_BUILD_TYPE="$build_type_choice" - log "${BLUE}[Config] Set build type to: ${MESON_BUILD_TYPE}${NC}" - fi - ;; - 5) - local log_level_choice - log_level_choice=$(dialog --title "Select Log Level" --menu "" 15 70 8 \ - "traceL3" "" "traceL2" "" "traceL1" "" "debug" "" "info" "" "warning" "" "error" "" "critical" "" \ - 3>&1 1>&2 2>&3) - if [ -n "$log_level_choice" ]; then - MESON_LOG_LEVEL="$log_level_choice" - log "${BLUE}[Config] Set log level to: ${MESON_LOG_LEVEL}${NC}" - fi - ;; - 6) - if dialog --title "Generate pkg-config" --yesno "Generate gridfire.pc file?" 7 60; then - MESON_PKG_CONFIG="true" - else - MESON_PKG_CONFIG="false" - fi - log "${BLUE}[Config] Set pkg-config generation to: ${MESON_PKG_CONFIG}${NC}" - ;; - 7) - local max_cores; max_cores=$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1) - local core_choice - core_choice=$(dialog --title "Set Number of Cores" --inputbox "Enter number of cores for compilation.\nAvailable: ${max_cores}" 10 60 "${MESON_NUM_CORES}" 3>&1 1>&2 2>&3) - if [[ "$core_choice" =~ ^[0-9]+$ ]] && [ "$core_choice" -gt 0 ]; then - MESON_NUM_CORES="$core_choice" - log "${BLUE}[Config] Set number of cores to: ${MESON_NUM_CORES}${NC}" - elif [ -n "$core_choice" ]; then - dialog --msgbox "Invalid input. Please enter a positive number." 6 40 - fi - ;; - 8) - run_advanced_options_tui - ;; - esac -} - -run_notes_tui() { - if [ ! -f "$NOTES_FILE" ]; then - dialog --msgbox "Notes file '${NOTES_FILE}' not found." 8 50 - return - fi - - local notes_content="" - local counter=1 - # Read file, filter comments, and process non-empty lines - while IFS= read -r line; do - # Skip empty or comment lines - if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then - continue - fi - notes_content+="${counter}. ${line}\n\n" - ((counter++)) - done < "$NOTES_FILE" - - if [ -z "$notes_content" ]; then - dialog --msgbox "No notes found in '${NOTES_FILE}'." 8 50 - else - dialog --title "Installer Notes" --msgbox "$notes_content" 20 70 - fi -} - -run_save_config_tui() { - local file_to_save - file_to_save=$(dialog --title "Save Configuration" --inputbox "Enter filename to save configuration:" 10 60 "$CONFIG_FILE" 3>&1 1>&2 2>&3) - if [ -z "$file_to_save" ]; then - dialog --msgbox "Save cancelled." 8 50 - return - fi - - # Write main settings - { - echo "# GridFire Build Configuration" - echo "BUILD_DIR=\"$BUILD_DIR\"" - echo "INSTALL_PREFIX=\"$INSTALL_PREFIX\"" - echo "MESON_BUILD_TYPE=\"$MESON_BUILD_TYPE\"" - echo "MESON_LOG_LEVEL=\"$MESON_LOG_LEVEL\"" - echo "MESON_PKG_CONFIG=\"$MESON_PKG_CONFIG\"" - echo "MESON_NUM_CORES=\"$MESON_NUM_CORES\"" - echo "C_COMPILER=\"$C_COMPILER\"" - echo "CC_COMPILER=\"$CC_COMPILER\"" - echo "FC_COMPILER=\"$FC_COMPILER\"" - echo "" - echo "# Advanced Meson Options" - for key in "${!MESON_ADVANCED_OPTS[@]}"; do - echo "MESON_ADVANCED_OPTS[\"$key\"]=\"${MESON_ADVANCED_OPTS[$key]}\"" - done - } > "$file_to_save" - - dialog --title "Success" --msgbox "Configuration saved to:\n${file_to_save}" 8 60 -} - -run_load_config_tui() { - local file_to_load - file_to_load=$(dialog --title "Load Configuration" --inputbox "Enter filename to load configuration:" 10 60 "$CONFIG_FILE" 3>&1 1>&2 2>&3) - if [ -z "$file_to_load" ]; then - dialog --msgbox "Load cancelled." 8 50 - return - fi - - if [ ! -f "$file_to_load" ]; then - dialog --msgbox "Error: File not found:\n${file_to_load}" 8 60 - return - fi - - # Source the file to load the variables - # We need to re-declare the associative array for it to be populated by source - declare -A MESON_ADVANCED_OPTS - # shellcheck source=/dev/null - . "$file_to_load" - - # Re-validate the compiler - check_compiler - if ! check_command "$CC_COMPILER"; then - dialog --title "Warning" --msgbox "Compiler '${CC_COMPILER}' from config file not found.\nRe-detecting a valid compiler." 10 60 - check_compiler - fi - - dialog --title "Success" --msgbox "Configuration loaded from:\n${file_to_load}" 8 60 -} - -run_boost_help_tui() { - local help_text="The version of the Boost library found on your system is not compatible with the selected C++ compiler and the C++23 standard. This is a common issue on distributions like Ubuntu that may have older versions of Boost in their package repositories.\n\n" - help_text+="To resolve this, you need to manually download, build, and install a newer version of Boost (e.g., 1.83.0 or newer).\n\n" - help_text+="Recommended Steps:\n" - help_text+="1. Download the latest Boost source from boost.org.\n" - help_text+="2. Follow their instructions to build it. A typical sequence is:\n" - help_text+=" ./bootstrap.sh\n" - help_text+=" sudo ./b2 install\n" - help_text+="Meson should then automatically find and use your manually installed version." - - dialog --title "Boost Compatibility Issue" --msgbox "$help_text" 25 78 -} - - -run_main_tui() { - if ! check_dialog_installed; then return 1; fi - # Initial check to populate compiler list and set a default - check_compiler - - local sudo_status="User Mode" - if [ "$EUID" -eq 0 ]; then - sudo_status="Root/Sudo Mode" - fi - - while true; do - # Re-check boost status to update menu dynamically - if [[ $BOOST_CHECKED = false ]]; then - BOOST_OKAY=true - log "${BLUE}[Info] Checking Boost library status (this may take a minute)...${NC}" - # If BOOST_CHECKED is set, we assume Boost was checked previously - check_boost >/dev/null 2>&1 || BOOST_OKAY=false - BOOST_CHECKED=true - fi - - local menu_items=( - "1" "Install System Dependencies" - "2" "Configure Build Options" - "3" "Install Python Bindings" - ) - if $BOOST_OKAY; then - menu_items+=( - "4" "Run Full Build (Setup + Compile)" - "5" "Run Meson Setup/Reconfigure" - "6" "Run Meson Compile" - "7" "Run Meson Install (requires sudo)" - "8" "Run Tests" - "S" "Save Configuration" - "L" "Load Configuration" - ) - fi - if ! $BOOST_OKAY; then - menu_items+=("B" "Boost Error Detected! Help with Boost Issues") - fi - menu_items+=( - "N" "View Notes" - "Q" "Exit" - ) - - - local choice - choice=$(dialog --clear --backtitle "GridFire Installer - [${sudo_status}]" \ - --title "Main Menu" \ - --menu "C: ${C_COMPILER:-N/A} C++: ${CC_COMPILER:-N/A} FC: ${FC_COMPILER:-N/A}\nDIR: ${BUILD_DIR} | TYPE: ${MESON_BUILD_TYPE} | CORES: ${MESON_NUM_CORES}\nPREFIX: ${INSTALL_PREFIX}\nLOG: ${MESON_LOG_LEVEL} | PKG-CONFIG: ${MESON_PKG_CONFIG}" 24 78 14 \ - "${menu_items[@]}" \ - 3>&1 1>&2 2>&3) - - clear - case "$choice" in - 1) - run_dependency_installer_tui - BOOST_CHECKED=false # Force re-check after installing - ;; - 2) run_build_config_tui ;; - 3) run_python_bindings_tui ;; - 4) run_meson_setup && run_meson_compile ;; - 5) run_meson_setup ;; - 6) run_meson_compile ;; - 7) run_meson_install ;; - 8) run_meson_tests ;; - N) run_notes_tui ;; - S) run_save_config_tui ;; - L) run_load_config_tui ;; - B) run_boost_help_tui ;; - Q) break ;; - *) log "${YELLOW}[Info] TUI cancelled.${NC}"; break ;; - esac - done - clear -} - -# --- Script Entry Point --- -main() { - if [[ " $@ " =~ " --help " ]] || [[ " $@ " =~ " -h " ]]; then show_help; exit 0; fi - if [[ " $@ " =~ " --clean " ]]; then log "${BLUE}[Info] Cleaning up...${NC}"; rm -rf "$BUILD_DIR" "$LOGFILE"; fi - - local useTUI=0 - if [[ " $@ " =~ " --tui " ]]; then - useTUI=1 - log "${BLUE}[Info] Using TUI mode...${NC}" - fi - - # Handle --config argument - while [ $# -gt 0 ]; do - case "$1" in - --config) - if [ -f "$2" ]; then - log "${BLUE}[Info] Loading configuration from $2...${NC}" - declare -A MESON_ADVANCED_OPTS - # shellcheck source=/dev/null - . "$2" - else - log "${RED}[Error] Configuration file not found: $2${NC}" - exit 1 - fi - shift 2 - ;; - *) - shift - ;; - esac - done - - if [[ $useTUI -eq 1 ]]; then - run_main_tui - log "${GREEN}Exited TUI mode.${NC}" - exit 0 - fi - - - echo "" > "$LOGFILE" # Clear log file - log "--- GridFire Installation Log ---" - log "Date: $(date)" - log "OS: ${OS_NAME}, Distro: ${DISTRO_ID}" - - - - - # --- Non-TUI path --- - log "\n${BLUE}--- Checking System Dependencies (CLI Mode) ---${NC}" - # Run check_compiler first to set the default - check_compiler - declare -A CHECKS=( - [python-dev]="check_python_dev" [meson-python]="check_meson_python" [cmake]="check_cmake" - [meson]="check_meson" [boost]="check_boost" - ) - if ! check_compiler; then - # Handle case where no compiler is found - local install_cmd; install_cmd=$(get_install_cmd "compiler") - if [ -n "$install_cmd" ]; then - if prompt_yes_no "Dependency 'compiler' is missing. Attempt to install? (y/n):"; then - eval "$install_cmd" 2>&1 | tee -a "$LOGFILE" - fi - else - log "${RED}[Error] No automatic installation for 'compiler'. Please install manually.${NC}" - fi - fi - - for dep in "${!CHECKS[@]}"; do - if ! ${CHECKS[$dep]}; then - local install_cmd; install_cmd=$(get_install_cmd "$dep") - if [ -n "$install_cmd" ]; then - if prompt_yes_no "Dependency '${dep}' is missing. Attempt to install? (y/n):"; then - eval "$install_cmd" 2>&1 | tee -a "$LOGFILE" - fi - else - log "${RED}[Error] No automatic installation for '${dep}'. Please install manually.${NC}" - fi - fi - done - - log "\n${BLUE}--- Re-checking all dependencies ---${NC}" - local final_fail=false - # Re-add compiler check to the list for final verification - CHECKS[compiler]="check_compiler" - for dep in "${!CHECKS[@]}"; do - if ! ${CHECKS[$dep]}; then - log "${RED}[FATAL] Dependency still missing: ${dep}${NC}" - final_fail=true - fi - done - - if $final_fail; then - log "${RED}Please install missing dependencies and re-run.${NC}" - exit 1 - fi - - log "\n${GREEN}--- All dependencies met. Proceeding with build. ---${NC}" - run_meson_setup && run_meson_compile -} - -main "$@" diff --git a/notes.txt b/notes.txt deleted file mode 100644 index e6e712d7..00000000 --- a/notes.txt +++ /dev/null @@ -1,8 +0,0 @@ -The logical flow of using this installer is (for C++ usage): install system dependencies > "Run Full Build" > "Run Meson Install" -The logical flow of using this installer is (for Python usage): install system dependencies > "Install Python Bindings" (note this will take a while). -When building in release mode (default), static analysis of CppAD throws a few bounds warnings. Testing indicates that these are not problems. These warnings do not get identified in debug builds. -GridFire requires gcc >= 13.0.0 or clang >= 16.0.0 -A directory named meson-boost-test will be created in the build directory. This is used to test the boost installation and may be deleted after the build is complete. -GridFire requires a version of boost compatible with C++23. Certain versions of Ubuntu prior released prior to 24.04 do not have a compatible version of boost in their repositories. In this case, you will need to install boost from source. -GridFire is authored by the 4D-STAR collaboration and Emily M. Boudreaux. -4D-STAR is funded by European Research Council (ERC) under the Horizon Europe programme (Synergy Grant agreement No. 101071505: 4D-STAR). Work for this project is funded by the European Union. Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Research Council.