#!/usr/bin/env bash # For more detailed debugging, uncomment the next line # set -x # Explicitly set CMAKE_VERSION from first argument, default to "none" CMAKE_VERSION=${1:-"none"} # --- Configuration --- DOWNLOAD_ATTEMPTS=3 DOWNLOAD_RETRY_DELAY=5 # seconds # --- Global Variables --- TMP_DIR="" # Initialize TMP_DIR, will be set by mktemp # Filenames used within TMP_DIR CMAKE_INSTALLER_SCRIPT_LOCAL_NAME="cmake-installer.sh" CMAKE_CHECKSUM_FILE_LOCAL_NAME="cmake-checksums.txt" # This will be the actual name of the CMake binary, derived from version and arch # It's important for matching against the checksum file. DERIVED_CMAKE_BINARY_FILENAME="" # --- Cleanup Function --- # This trap will execute on EXIT, ERR, SIGINT, SIGTERM # It's crucial for debugging to see the state of TMP_DIR if things go wrong. cleanup() { # $? is the exit code of the last command before the trap was triggered # or the argument to exit if the script called exit explicitly. LAST_EXIT_CODE=$? echo # Newline for readability # Only proceed with detailed cleanup if TMP_DIR was actually created if [[ -n "${TMP_DIR}" && -d "${TMP_DIR}" ]]; then echo "--- Cleanup: Temporary Directory Inspector (${TMP_DIR}) ---" echo "Listing contents of TMP_DIR:" ls -la "${TMP_DIR}" # Check and display checksum file content if [[ -f "${TMP_DIR}/${CMAKE_CHECKSUM_FILE_LOCAL_NAME}" ]]; then echo "--- Content of downloaded checksum file (${CMAKE_CHECKSUM_FILE_LOCAL_NAME}) ---" cat "${TMP_DIR}/${CMAKE_CHECKSUM_FILE_LOCAL_NAME}" echo "--- End of checksum file ---" else echo "Checksum file (${CMAKE_CHECKSUM_FILE_LOCAL_NAME}) not found in TMP_DIR." fi # Check and display head of (potentially) installer script # Useful to see if it's an HTML error page if [[ -f "${TMP_DIR}/${CMAKE_INSTALLER_SCRIPT_LOCAL_NAME}" ]]; then echo "--- First 10 lines of downloaded installer script (${CMAKE_INSTALLER_SCRIPT_LOCAL_NAME}) ---" head -n 10 "${TMP_DIR}/${CMAKE_INSTALLER_SCRIPT_LOCAL_NAME}" echo "--- End of installer script head ---" elif [[ -f "${TMP_DIR}/${DERIVED_CMAKE_BINARY_FILENAME}" ]]; then # If it was renamed echo "--- First 10 lines of downloaded installer script (${DERIVED_CMAKE_BINARY_FILENAME}) ---" head -n 10 "${TMP_DIR}/${DERIVED_CMAKE_BINARY_FILENAME}" echo "--- End of installer script head ---" else echo "Installer script not found in TMP_DIR (checked for ${CMAKE_INSTALLER_SCRIPT_LOCAL_NAME} and ${DERIVED_CMAKE_BINARY_FILENAME})." fi echo "Attempting to remove temporary directory: ${TMP_DIR}" rm -Rf "${TMP_DIR}" echo "Temporary directory removed." echo "--- End of Cleanup ---" else echo "--- Cleanup: TMP_DIR was not set or not a directory, no temp files to inspect or clean. ---" fi # Ensure the script exits with the LAST_EXIT_CODE observed by the trap # unless it was 0 and the script is exiting due to an explicit non-zero exit. # The 'exit' command within the trap will override the script's natural exit code. # So, if the script was going to exit 0, but cleanup had an issue, this could change it. # However, for debugging an exit code 8, we want to preserve the code that *caused* the trap. echo "Script finished with exit code: ${LAST_EXIT_CODE}." exit "${LAST_EXIT_CODE}" } trap cleanup EXIT ERR SIGINT SIGTERM # Immediately turn on `set -e` after trap setup set -e # --- Helper Functions --- # Function to download a file with retries and basic validation download_file() { local url="$1" local output_filename="$2" local attempts_left=$DOWNLOAD_ATTEMPTS local wget_exit_code=0 while [ $attempts_left -gt 0 ]; do echo "Downloading: ${url}" echo "Saving to: ${TMP_DIR}/${output_filename}" echo "Attempt $((DOWNLOAD_ATTEMPTS - attempts_left + 1)) of ${DOWNLOAD_ATTEMPTS}..." # Use wget with: # -O: specify output file # --timeout: connection/read timeout # --tries: number of retries (wget's own retry, distinct from this loop) # --quiet: suppress normal output, but errors still go to stderr # --show-progress: if not quiet, shows a progress bar (optional) # Using -q for less verbose logs, but on failure, we need to know. wget -O "${TMP_DIR}/${output_filename}" --timeout=30 --tries=1 "${url}" wget_exit_code=$? if [ ${wget_exit_code} -eq 0 ]; then echo "Download command successful for ${output_filename}." if [ -s "${TMP_DIR}/${output_filename}" ]; then # -s: file exists and has a size greater than 0 # Basic check for common HTML error page indicators # This is a heuristic and might not catch all error pages. if head -n 5 "${TMP_DIR}/${output_filename}" | grep -Eiq ' /dev/null; then echo "wget not found. Attempting to install wget via apt-get..." if command -v apt-get > /dev/null; then apt-get update -y apt-get install -y --no-install-recommends wget echo "wget installed." else echo "ERROR: apt-get not found. Cannot install wget. Please install wget manually." exit 1 fi else echo "wget is available." fi # 2. (Optional) Remove existing CMake installed via apt echo "Attempting to remove any existing CMake installed via apt..." if command -v apt-get > /dev/null; then if dpkg -s cmake &> /dev/null; then # Check if cmake package is actually installed apt-get -y purge --auto-remove cmake echo "cmake package purged." else echo "cmake package not found via dpkg, skipping purge." fi else echo "apt-get not found, skipping removal of CMake via apt." fi # 3. Create installation and temporary directories echo "Creating CMake installation directory /opt/cmake..." mkdir -p /opt/cmake TMP_DIR=$(mktemp -d -t cmake-install-XXXXXXXXXX) echo "Temporary directory created: ${TMP_DIR}" # Crucial: subsequent operations needing temp files should happen in or relative to TMP_DIR # We will cd into TMP_DIR later, or use full paths like ${TMP_DIR}/filename # 4. Determine system architecture echo "Determining system architecture..." architecture=$(dpkg --print-architecture) case "${architecture}" in arm64) ARCH="aarch64" ;; amd64) ARCH="x86_64" ;; *) echo "ERROR: Unsupported architecture '${architecture}' reported by dpkg." exit 1 ;; esac echo "Detected architecture: ${architecture} (mapped to CMake arch: ${ARCH})" # 5. Define CMake download URLs and the filename expected by checksum DERIVED_CMAKE_BINARY_FILENAME="cmake-${CMAKE_VERSION}-linux-${ARCH}.sh" CMAKE_BINARY_URL="https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${DERIVED_CMAKE_BINARY_FILENAME}" CMAKE_CHECKSUM_FILE_URL="https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/cmake-${CMAKE_VERSION}-SHA-256.txt" # 6. Download CMake binary and checksum file echo "--- Downloading Files ---" if ! download_file "${CMAKE_BINARY_URL}" "${CMAKE_INSTALLER_SCRIPT_LOCAL_NAME}"; then echo "ERROR: Failed to download CMake binary installer. See messages above." exit 1 fi if ! download_file "${CMAKE_CHECKSUM_FILE_URL}" "${CMAKE_CHECKSUM_FILE_LOCAL_NAME}"; then echo "ERROR: Failed to download CMake checksum file. See messages above." exit 1 fi echo "Downloads complete." echo "--- End of Downloading Files ---" echo # Newline for readability # Before checksum, rename the downloaded installer to its derived name, # as the checksum file refers to this specific name. echo "Renaming downloaded installer from '${CMAKE_INSTALLER_SCRIPT_LOCAL_NAME}' to '${DERIVED_CMAKE_BINARY_FILENAME}' for checksum verification." mv "${TMP_DIR}/${CMAKE_INSTALLER_SCRIPT_LOCAL_NAME}" "${TMP_DIR}/${DERIVED_CMAKE_BINARY_FILENAME}" if [ ! -f "${TMP_DIR}/${DERIVED_CMAKE_BINARY_FILENAME}" ]; then echo "ERROR: Failed to rename installer script for checksum. File '${TMP_DIR}/${DERIVED_CMAKE_BINARY_FILENAME}' does not exist after move." exit 1 fi # 7. Verify checksum echo "--- Verifying Checksum ---" echo "Checksum file is: ${TMP_DIR}/${CMAKE_CHECKSUM_FILE_LOCAL_NAME}" echo "Binary file to check is: ${TMP_DIR}/${DERIVED_CMAKE_BINARY_FILENAME}" # Ensure the checksum file actually contains an entry for our binary # This is important because the SHA-256.txt file contains checksums for *all* release assets echo "Checking if checksum file contains entry for '${DERIVED_CMAKE_BINARY_FILENAME}'..." if ! grep -q "${DERIVED_CMAKE_BINARY_FILENAME}" "${TMP_DIR}/${CMAKE_CHECKSUM_FILE_LOCAL_NAME}"; then echo "ERROR: The downloaded checksum file '${CMAKE_CHECKSUM_FILE_LOCAL_NAME}' does NOT contain an entry for '${DERIVED_CMAKE_BINARY_FILENAME}'." echo "This strongly suggests that the CMAKE_VERSION ('${CMAKE_VERSION}') or ARCH ('${ARCH}') is incorrect, or the specified version does not provide a .sh installer for this architecture." echo "Please verify the version and available files at https://github.com/Kitware/CMake/releases/tag/v${CMAKE_VERSION}" exit 1 fi echo "Checksum file contains an entry for '${DERIVED_CMAKE_BINARY_FILENAME}'." # Perform the checksum. We need to be in the directory where the files are. echo "Changing directory to ${TMP_DIR} for checksum verification." cd "${TMP_DIR}" # <<<<<<< IMPORTANT: sha256sum -c needs to find files echo "Verifying checksum of '${DERIVED_CMAKE_BINARY_FILENAME}' using '${CMAKE_CHECKSUM_FILE_LOCAL_NAME}'..." # The --ignore-missing flag is good, as the .txt file has many checksums. # The --strict flag would cause it to error if there are improperly formatted lines. # We rely on the grep check above to ensure our specific file is mentioned. if sha256sum -c --ignore-missing "${CMAKE_CHECKSUM_FILE_LOCAL_NAME}"; then echo "Checksum verification successful for '${DERIVED_CMAKE_BINARY_FILENAME}'." else SHA_EXIT_CODE=$? echo "ERROR: Checksum verification FAILED for '${DERIVED_CMAKE_BINARY_FILENAME}' with exit code ${SHA_EXIT_CODE}." # Cleanup trap will show file contents. exit 1 # Critical failure fi echo "--- End of Verifying Checksum ---" echo # Newline for readability # 8. Install CMake echo "--- Installing CMake ---" echo "Making the CMake installer script '${DERIVED_CMAKE_BINARY_FILENAME}' executable..." chmod +x "${DERIVED_CMAKE_BINARY_FILENAME}" # Still in TMP_DIR echo "Executing CMake installer script: ./${DERIVED_CMAKE_BINARY_FILENAME} --prefix=/opt/cmake --skip-license" # Execute the script. If this script exits with 8, this is our culprit. if ./"${DERIVED_CMAKE_BINARY_FILENAME}" --prefix=/opt/cmake --skip-license; then echo "CMake installer script executed successfully." else INSTALLER_EXIT_CODE=$? echo "ERROR: CMake installer script FAILED with exit code ${INSTALLER_EXIT_CODE}." # This is the most likely place for an exit code 8 if downloads and checksums were okay. # The trap will handle cleanup. The script will exit with INSTALLER_EXIT_CODE due to the trap. exit ${INSTALLER_EXIT_CODE} # Explicitly exit with the installer's code fi echo "--- End of Installing CMake ---" echo # Newline for readability # 9. Create symlinks echo "Creating symbolic links for cmake and ctest in /usr/local/bin/..." ln -sf /opt/cmake/bin/cmake /usr/local/bin/cmake ln -sf /opt/cmake/bin/ctest /usr/local/bin/ctest echo "Symbolic links created." echo # Newline for readability echo "SUCCESS: CMake ${CMAKE_VERSION} installation and setup complete." # The script will exit with 0 here. The trap will run, see $? is 0, and then exit 0. exit 0