#!/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 # --- Configuration --- LOGFILE="GridFire_Installer.log" NOTES_FILE="notes.txt" CONFIG_FILE="gridfire_build.conf" MIN_GCC_VER="13.0.0" MIN_CLANG_VER="16.0.0" MIN_MESON_VER="1.5.0" # --- Build Configuration Globals --- BUILD_DIR="build" INSTALL_PREFIX="/usr/local" MESON_BUILD_TYPE="debug" # Default to debug to match Meson's default and avoid optimization warnings 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" # --- 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 } # 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 } 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_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 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() { if check_command meson; then local ver; ver=$(meson --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_boost() { log "${BLUE}[Info] Checking for Boost dependency using Meson...${NC}" local test_dir="meson-boost-test" rm -rf "$test_dir" && mkdir -p "$test_dir" cat > "$test_dir/meson.build" </dev/null; then log "${GREEN}[OK] Found Boost libraries.${NC}" rm -rf "$test_dir" return 0 else log "${RED}[FAIL] Boost libraries not found by Meson.${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" ;; "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" ;; 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" ;; "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" ;; 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" ;; "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" ;; 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" ;; "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" ;; 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}" # Set CC, CXX, and FC environment variables for the meson command if ! CC="${C_COMPILER}" CXX="${CC_COMPILER}" FC="${FC_COMPILER}" meson 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}" if ! meson 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}" if ! sudo meson 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}" if ! meson 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 } 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_python_dev >/dev/null; DEP_STATUS[python-dev]=$? check_meson_python >/dev/null; DEP_STATUS[meson-python]=$? check_cmake >/dev/null; DEP_STATUS[cmake]=$? check_meson >/dev/null; DEP_STATUS[meson]=$? check_boost >/dev/null; DEP_STATUS[boost]=$? 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 6 \ "compiler" "C++ Compilers (g++, clang++)" "$([[ ${DEP_STATUS[compiler]} -ne 0 ]] && echo "on" || echo "off")" \ "python-dev" "Python 3 Dev Headers" "$([[ ${DEP_STATUS[python-dev]} -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")" \ "boost" "Boost Libraries" "$([[ ${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}" 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 done # Re-run check to update status check_compiler } 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 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 install -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 install .; 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_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"; 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"; 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"; 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]})" \ "10" "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 ;; 10) 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_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 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 13 \ "1" "Install System Dependencies" \ "2" "Configure Build Options" \ "3" "Install Python Bindings" \ "4" "Run Full Build (Setup + Compile)" \ "5" "Run Meson Setup/Reconfigure" \ "6" "Run Meson Compile" \ "7" "Run Meson Install (requires sudo)" \ "8" "Run Tests" \ "9" "View Notes" \ "10" "Save Configuration" \ "11" "Load Configuration" \ "12" "Exit" \ 3>&1 1>&2 2>&3) clear case "$choice" in 1) run_dependency_installer_tui ;; 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 ;; 9) run_notes_tui ;; 10) run_save_config_tui ;; 11) run_load_config_tui ;; 12) break ;; *) log "${YELLOW}[Info] TUI cancelled.${NC}"; break ;; esac done clear } # --- Script Entry Point --- main() { # shellcheck disable=SC2199 if [[ " $@ " =~ " --help " ]] || [[ " $@ " =~ " -h " ]]; then show_help; exit 0; fi # shellcheck disable=SC2199 if [[ " $@ " =~ " --clean " ]]; then log "${BLUE}[Info] Cleaning up...${NC}"; rm -rf "$BUILD_DIR" "$LOGFILE"; fi echo "" > "$LOGFILE" # Clear log file log "--- GridFire Installation Log ---" log "Date: $(date)" log "OS: ${OS_NAME}, Distro: ${DISTRO_ID}" if [[ " $@ " =~ " --tui " ]]; then log "${BLUE}[Info] Running in TUI mode...${NC}" run_main_tui exit 0 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 # --- 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 "$@"