diff --git a/README.md b/README.md index cdaa97b1..3f1828fe 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,21 @@ only and do not necessarily reflect those of the European Union or the European ## Build and Installation Instructions +The easiest way to build GridFire is using the `install.sh` or `install-tui.sh` scripts in the root directory. To use +these scripts, simply run: + +```bash +./install.sh +# or +./install-tui.sh +``` + +The regular installation script will select a standard "ideal" set of build options for you. If you want more control +over the build options, you can use the `install-tui.sh` script, which will provide a text-based user interface to +select the build options you want. + +Generally, both are intended to be easy to use and will prompt you automatically to install any missing dependencies. + ### Prerequisites - C++ compiler supporting C++23 standard - Meson build system (>= 1.5.0) diff --git a/install-tui.sh b/install-tui.sh new file mode 100755 index 00000000..5e344919 --- /dev/null +++ b/install-tui.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +./install.sh --tui \ No newline at end of file diff --git a/install.sh b/install.sh index 1bd38c71..38ba11f0 100755 --- a/install.sh +++ b/install.sh @@ -1,26 +1,28 @@ #!/usr/bin/env bash # -# install.sh - Comprehensive interactive installation script for GridFire +# install.sh - Comprehensive interactive installation script for GridFire. # # This script performs the following actions: -# 1. Checks for essential system dependencies: -# - A C++ compiler (g++ or clang++) -# - Python 3 development headers -# - CMake -# - Meson -# - Boost libraries -# 2. If run with the --tui flag, it provides a text-based user interface -# to select and install missing dependencies. +# 1. Checks for essential system dependencies. +# 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 runs the meson setup and build commands. +# 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" + +# --- 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) # --- ANSI Color Codes --- RED="\033[0;31m" @@ -61,7 +63,7 @@ show_help() { 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." + 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 @@ -82,15 +84,16 @@ fi # --- Dependency Check Functions --- -# Check for a C++ compiler. +check_command() { + command -v "$1" &>/dev/null +} + check_compiler() { - if command -v g++ &>/dev/null; then - COMPILER_CMD="g++" - log "${GREEN}[OK] Found C++ compiler: $(g++ --version | head -n1)${NC}" + if check_command g++; then + log "${GREEN}[OK] Found C++ compiler: g++${NC}" return 0 - elif command -v clang++ &>/dev/null; then - COMPILER_CMD="clang++" - log "${GREEN}[OK] Found C++ compiler: $(clang++ --version | head -n1)${NC}" + elif check_command clang++; then + log "${GREEN}[OK] Found C++ compiler: clang++${NC}" return 0 else log "${RED}[FAIL] No C++ compiler (g++ or clang++) found.${NC}" @@ -98,9 +101,8 @@ check_compiler() { fi } -# Check for Python 3 development headers. check_python_dev() { - if python3-config --includes &>/dev/null; then + if check_command python3 && python3-config --includes &>/dev/null; then log "${GREEN}[OK] Found Python 3 development headers.${NC}" return 0 else @@ -109,9 +111,8 @@ check_python_dev() { fi } -# Check for CMake. check_cmake() { - if command -v cmake &>/dev/null; then + if check_command cmake; then log "${GREEN}[OK] Found CMake: $(cmake --version | head -n1)${NC}" return 0 else @@ -120,9 +121,8 @@ check_cmake() { fi } -# Check for Meson. check_meson() { - if command -v meson &>/dev/null; then + if check_command meson; then log "${GREEN}[OK] Found Meson: $(meson --version)${NC}" return 0 else @@ -131,17 +131,10 @@ check_meson() { fi } -# Check if Boost is installed and available to Meson. check_boost() { log "${BLUE}[Info] Checking for Boost dependency using Meson...${NC}" - if meson introspect --dependencies -C "$BUILD_DIR" 2>/dev/null | grep -q "boost.*found: YES"; then - log "${GREEN}[OK] Boost dependency is already met in the Meson build directory.${NC}" - return 0 - fi - - # A simple meson dependency check as a fallback local test_dir="meson-boost-test" - rm -rf "$test_dir" && mkdir "$test_dir" + rm -rf "$test_dir" && mkdir -p "$test_dir" cat > "$test_dir/meson.build" </dev/null; then - log "${YELLOW}[Warn] The 'dialog' utility is not installed. It is required for TUI mode.${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 "Would you like to install it now? (y/n):"; then + if prompt_yes_no "Attempt to install it now? (y/n):"; then eval "$install_cmd" - if ! command -v dialog &>/dev/null; then + if ! check_command dialog; then log "${RED}[FATAL] Failed to install 'dialog'. Cannot run in TUI mode.${NC}" - exit 1 + return 1 fi else log "${RED}[FATAL] Cannot run in TUI mode without 'dialog'. Exiting.${NC}" - exit 1 + return 1 fi else - log "${RED}[FATAL] Cannot automatically install 'dialog' on your system. Please install it manually and try again.${NC}" - exit 1 + log "${RED}[FATAL] Cannot auto-install 'dialog'. Please install it manually.${NC}" + return 1 fi fi - - local choices - choices=$(dialog --clear --backtitle "Project Dependency Installer" \ - --title "Install Dependencies" \ - --checklist "Select dependencies to install. Dependencies marked with (*) are missing." 20 70 5 \ - "compiler" "C++ Compiler (g++ or clang++)" "$([[ ${DEP_STATUS[compiler]} == "FAIL" ]] && echo "on" || echo "off")" \ - "python-dev" "Python 3 Dev Headers" "$([[ ${DEP_STATUS[python-dev]} == "FAIL" ]] && echo "on" || echo "off")" \ - "cmake" "CMake" "$([[ ${DEP_STATUS[cmake]} == "FAIL" ]] && echo "on" || echo "off")" \ - "meson" "Meson Build System" "$([[ ${DEP_STATUS[meson]} == "FAIL" ]] && echo "on" || echo "off")" \ - "boost" "Boost Libraries" "$([[ ${DEP_STATUS[boost]} == "FAIL" ]] && echo "on" || echo "off")" \ - 3>&1 1>&2 2>&3) - - clear - - if [ -z "$choices" ]; then - log "${YELLOW}[Info] No dependencies selected for installation.${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 - run_install_cmd "$install_cmd" - else - log "${RED}[Error] No automatic installation command available for '${dep}' on your system.${NC}" - log "${RED}Please install it manually.${NC}" - fi - done + return 0 } -# --- Script Entry Point --- -main() { - # Handle command-line arguments - if [[ " $@ " =~ " --help " ]] || [[ " $@ " =~ " -h " ]]; then - show_help - exit 0 - fi - - if [[ " $@ " =~ " --clean " ]]; then - log "${BLUE}[Info] Cleaning up previous build...${NC}" - rm -rf "$BUILD_DIR" "$LOGFILE" - fi - - # Start logging - echo "" > "$LOGFILE" - log "--- Project Installation Log ---" - log "Date: $(date)" - log "OS: ${OS_NAME}, Distro: ${DISTRO_ID}" - - # Perform all dependency checks - log "\n${BLUE}--- Checking System Dependencies ---${NC}" +run_dependency_installer_tui() { declare -A DEP_STATUS check_compiler; DEP_STATUS[compiler]=$? check_python_dev; DEP_STATUS[python-dev]=$? @@ -343,68 +317,243 @@ main() { check_meson; DEP_STATUS[meson]=$? check_boost; DEP_STATUS[boost]=$? - local all_deps_met=0 - for status in "${DEP_STATUS[@]}"; do - if [ "$status" -ne 0 ]; then - all_deps_met=1 - break + 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 5 \ + "compiler" "C++ Compiler (g++ or 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")" \ + "cmake" "CMake" "$([[ ${DEP_STATUS[cmake]} -ne 0 ]] && echo "on" || echo "off")" \ + "meson" "Meson Build System" "$([[ ${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 +} - if [ $all_deps_met -eq 0 ]; then - log "\n${GREEN}--- All dependencies are met! ---${NC}" - run_meson_build +run_python_bindings_tui() { + 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}\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 ! 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 ! 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_build_config_tui() { + local choice + choice=$(dialog --clear --backtitle "Build Configuration" \ + --title "Configure Build Options" \ + --menu "Select an option to configure:" 20 70 6 \ + "1" "Build Directory (current: ${BUILD_DIR})" \ + "2" "Install Prefix (current: ${INSTALL_PREFIX})" \ + "3" "Build Type (current: ${MESON_BUILD_TYPE})" \ + "4" "Log Level (current: ${MESON_LOG_LEVEL})" \ + "5" "Generate pkg-config (current: ${MESON_PKG_CONFIG})" \ + "6" "Number of Cores (current: ${MESON_NUM_CORES})" \ + 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) + local build_type_choice + build_type_choice=$(dialog --title "Select Build Type" --menu "" 15 70 3 \ + "release" "Optimized for performance" \ + "debug" "With debug symbols, no optimization" \ + "debugoptimized" "With debug symbols and optimization" \ + 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 + ;; + 4) + 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 + ;; + 5) + 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}" + ;; + 6) + 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 + ;; + esac +} + +run_main_tui() { + if ! check_dialog_installed; then return 1; fi + while true; do + local build_dir_status="Not Found" + [ -d "$BUILD_DIR" ] && build_dir_status="Found" + + local choice + choice=$(dialog --clear --backtitle "GridFire Installation and Build Manager" \ + --title "Main Menu" \ + --menu "DIR: ${BUILD_DIR} | TYPE: ${MESON_BUILD_TYPE} | CORES: ${MESON_NUM_CORES}\nPREFIX: ${INSTALL_PREFIX}\nLOG: ${MESON_LOG_LEVEL} | PKG-CONFIG: ${MESON_PKG_CONFIG}" 20 70 10 \ + "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" "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) 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 + + echo "" > "$LOGFILE" # Clear log file + log "--- GridFire Installation Log ---" + log "Date: $(date)" + log "OS: ${OS_NAME}, Distro: ${DISTRO_ID}" + + if [[ " $@ " =~ " --tui " ]]; then + run_main_tui + log "${GREEN}Exited TUI mode.${NC}" exit 0 fi - log "\n${YELLOW}--- Some dependencies are missing ---${NC}" - - if [[ " $@ " =~ " --tui " ]]; then - run_tui - else - # Interactive command-line prompts - for dep in "${!DEP_STATUS[@]}"; do - if [ "${DEP_STATUS[$dep]}" -ne 0 ]; 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 it now? (y/n):"; then - run_install_cmd "$install_cmd" + # --- Non-TUI path --- + log "\n${BLUE}--- Checking System Dependencies (CLI Mode) ---${NC}" + declare -A CHECKS=( + [compiler]="check_compiler" [python-dev]="check_python_dev" + [cmake]="check_cmake" [meson]="check_meson" [boost]="check_boost" + ) + local all_deps_met=true + for dep in "${!CHECKS[@]}"; do + if ! ${CHECKS[$dep]}; then + all_deps_met=false + 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 + run_install_cmd "$install_cmd" + fi + else + log "${RED}[Error] No automatic installation for '${dep}'. Please install manually.${NC}" fi - else - log "${RED}[Error] No automatic installation command available for '${dep}' on your system. Please install it manually.${NC}" - fi fi - done - fi - - # Final check before build - log "\n${BLUE}--- Re-checking dependencies after installation attempts ---${NC}" - check_compiler; DEP_STATUS[compiler]=$? - check_python_dev; DEP_STATUS[python-dev]=$? - check_cmake; DEP_STATUS[cmake]=$? - check_meson; DEP_STATUS[meson]=$? - check_boost; DEP_STATUS[boost]=$? - - all_deps_met=0 - local failed_deps="" - for dep in "${!DEP_STATUS[@]}"; do - if [ "${DEP_STATUS[$dep]}" -ne 0 ]; then - all_deps_met=1 - failed_deps+="${dep} " - fi done - if [ $all_deps_met -eq 0 ]; then - log "\n${GREEN}--- All dependencies are now met! ---${NC}" - run_meson_build - else - log "\n${RED}[FATAL] The following dependencies are still missing: ${failed_deps}${NC}" - log "${RED}Please install them manually and re-run this script.${NC}" - log "${RED}Check ${LOGFILE} for detailed error messages.${NC}" - exit 1 + log "\n${BLUE}--- Re-checking all dependencies ---${NC}" + local final_fail=false + 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 } -# Run the main function main "$@"