#!/usr/bin/env bash set -u SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)" VENV_DIR="${FFX_TEST_VENV_DIR:-${ROOT_DIR}/.venv}" VENV_BIN_DIR="${VENV_DIR}/bin" VENV_PYTHON="${VENV_BIN_DIR}/python" VENV_PIP="${VENV_BIN_DIR}/pip" CHECK_ONLY=0 READINESS_FAILURES=0 INSTALL_FAILURES=0 MISSING_REQUIRED_SYSTEM=() COLOR_RESET="" COLOR_GREEN="" COLOR_YELLOW="" COLOR_RED="" if [ -t 1 ]; then COLOR_RESET="$(printf '\033[0m')" COLOR_GREEN="$(printf '\033[32m')" COLOR_YELLOW="$(printf '\033[33m')" COLOR_RED="$(printf '\033[31m')" fi usage() { cat </dev/null 2>&1 } check_python_venv_support() { python3 -m venv --help >/dev/null 2>&1 } check_system_command() { command_exists "$1" } check_venv_python() { [ -x "${VENV_PYTHON}" ] } check_venv_pip() { check_venv_python && "${VENV_PIP}" --version >/dev/null 2>&1 } check_venv_ffx() { check_venv_python && "${VENV_PYTHON}" -m ffx version >/dev/null 2>&1 } check_venv_pytest() { check_venv_python && "${VENV_PYTHON}" -m pytest --version >/dev/null 2>&1 } check_venv_sphinx() { check_venv_python && "${VENV_BIN_DIR}/sphinx-build" --version >/dev/null 2>&1 } check_venv_docs_packages() { check_venv_python && "${VENV_PYTHON}" - <<'PY' >/dev/null 2>&1 import esbonio import sphinx import sphinx_rtd_theme PY } check_editable_install() { check_venv_python && FFX_REPO_ROOT="${ROOT_DIR}" "${VENV_PYTHON}" - <<'PY' >/dev/null 2>&1 from __future__ import annotations import os from pathlib import Path import ffx repo_root = Path(os.environ["FFX_REPO_ROOT"]).resolve() package_path = Path(ffx.__file__).resolve() raise SystemExit(0 if repo_root in package_path.parents else 1) PY } check_python_environment_ready() { check_venv_python && check_venv_pip && check_venv_pytest && check_venv_sphinx && check_venv_docs_packages && check_venv_ffx && check_editable_install } command_detail() { command -v "$1" || printf "command '%s' not found" "$1" } python_venv_detail() { if check_python_venv_support; then printf 'python3 -m venv is available' else printf 'python3 venv support is unavailable' fi } venv_python_detail() { if check_venv_python; then printf '%s' "${VENV_PYTHON}" else printf 'missing %s' "${VENV_PYTHON}" fi } venv_pip_detail() { if check_venv_pip; then "${VENV_PIP}" --version else printf 'missing pip in %s' "${VENV_DIR}" fi } venv_ffx_detail() { if check_venv_ffx; then printf 'ffx import and CLI entry are available' else printf 'ffx is not installed in %s' "${VENV_DIR}" fi } venv_pytest_detail() { if check_venv_pytest; then "${VENV_PYTHON}" -m pytest --version 2>/dev/null | head -n 1 else printf 'pytest is not installed in %s' "${VENV_DIR}" fi } venv_sphinx_detail() { if check_venv_sphinx; then "${VENV_BIN_DIR}/sphinx-build" --version 2>&1 else printf 'sphinx-build is not installed in %s' "${VENV_DIR}" fi } venv_docs_packages_detail() { if check_venv_docs_packages; then printf 'Sphinx, Read the Docs theme, and Esbonio packages are importable' else printf 'one or more docs packages are missing in %s' "${VENV_DIR}" fi } editable_install_detail() { if check_editable_install; then printf 'ffx resolves from %s' "${ROOT_DIR}" else printf 'ffx does not resolve from the project source tree' fi } report_required_command() { local label="$1" local command_name="$2" if check_system_command "${command_name}"; then report_component ok "${label}" "$(command_detail "${command_name}")" else report_component failed "${label}" "$(command_detail "${command_name}")" MISSING_REQUIRED_SYSTEM+=("${command_name}") READINESS_FAILURES=$((READINESS_FAILURES + 1)) fi } print_system_status() { MISSING_REQUIRED_SYSTEM=() echo "System toolchain status:" report_required_command "git" "git" report_required_command "python3" "python3" if check_system_command "python3" && check_python_venv_support; then report_component ok "python3 venv" "$(python_venv_detail)" else report_component failed "python3 venv" "$(python_venv_detail)" MISSING_REQUIRED_SYSTEM+=("python3-venv") READINESS_FAILURES=$((READINESS_FAILURES + 1)) fi report_required_command "ffmpeg" "ffmpeg" report_required_command "ffprobe" "ffprobe" report_required_command "cpulimit" "cpulimit" } print_python_status() { echo "Repo test and docs virtualenv status:" if check_venv_python; then report_component ok "test virtualenv" "$(venv_python_detail)" else report_component failed "test virtualenv" "$(venv_python_detail)" READINESS_FAILURES=$((READINESS_FAILURES + 1)) fi if check_venv_pip; then report_component ok "test pip" "$(venv_pip_detail)" else report_component failed "test pip" "$(venv_pip_detail)" READINESS_FAILURES=$((READINESS_FAILURES + 1)) fi if check_venv_pytest; then report_component ok "test pytest" "$(venv_pytest_detail)" else report_component failed "test pytest" "$(venv_pytest_detail)" READINESS_FAILURES=$((READINESS_FAILURES + 1)) fi if check_venv_sphinx; then report_component ok "docs sphinx" "$(venv_sphinx_detail)" else report_component failed "docs sphinx" "$(venv_sphinx_detail)" READINESS_FAILURES=$((READINESS_FAILURES + 1)) fi if check_venv_docs_packages; then report_component ok "docs packages" "$(venv_docs_packages_detail)" else report_component failed "docs packages" "$(venv_docs_packages_detail)" READINESS_FAILURES=$((READINESS_FAILURES + 1)) fi if check_venv_ffx; then report_component ok "test ffx" "$(venv_ffx_detail)" else report_component failed "test ffx" "$(venv_ffx_detail)" READINESS_FAILURES=$((READINESS_FAILURES + 1)) fi if check_editable_install; then report_component ok "editable source" "$(editable_install_detail)" else report_component failed "editable source" "$(editable_install_detail)" READINESS_FAILURES=$((READINESS_FAILURES + 1)) fi } print_status_report() { READINESS_FAILURES=0 print_system_status echo print_python_status } detect_package_manager() { if command_exists apt-get; then printf 'apt-get\n' return 0 fi if command_exists pacman; then printf 'pacman\n' return 0 fi return 1 } run_root_command() { if [ "${EUID}" -eq 0 ]; then "$@" elif command_exists sudo; then sudo -n "$@" else return 1 fi } install_system_requirements() { local package_manager if [ "${#MISSING_REQUIRED_SYSTEM[@]}" -eq 0 ]; then return 0 fi if ! package_manager="$(detect_package_manager)"; then printf 'No supported package manager found for automatic system preparation.\n' >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi case "${package_manager}" in apt-get) printf 'Installing required system dependencies via apt-get...\n' if ! run_root_command apt-get update; then printf 'apt-get update failed or requires interactive sudo.\n' >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi if ! run_root_command apt-get install -y git python3 python3-venv ffmpeg cpulimit; then printf 'apt-get install failed or requires interactive sudo.\n' >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi ;; pacman) printf 'Installing required system dependencies via pacman...\n' if ! run_root_command pacman -Sy --noconfirm git python ffmpeg cpulimit; then printf 'pacman install failed or requires interactive sudo.\n' >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi ;; esac return 0 } ensure_test_venv() { if ! check_venv_python; then printf 'Creating repo test virtualenv at %s...\n' "${VENV_DIR}" if ! python3 -m venv "${VENV_DIR}"; then printf 'Failed to create test virtualenv at %s.\n' "${VENV_DIR}" >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi fi if ! check_venv_pip; then printf 'Missing pip in %s.\n' "${VENV_DIR}" >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi printf 'Installing FFX package with test and docs extras into %s...\n' "${VENV_DIR}" if ! ( cd "${ROOT_DIR}" && "${VENV_PIP}" install --editable '.[test,docs]' ); then printf 'Failed to install FFX package with test and docs extras into %s.\n' "${VENV_DIR}" >&2 INSTALL_FAILURES=$((INSTALL_FAILURES + 1)) return 1 fi return 0 } parse_args() { while [ "$#" -gt 0 ]; do case "$1" in --check) CHECK_ONLY=1 ;; --help|-h) usage exit 0 ;; *) printf 'Unknown option: %s\n\n' "$1" >&2 usage >&2 exit 2 ;; esac shift done } main() { parse_args "$@" print_status_report if [ "${CHECK_ONLY}" -eq 0 ]; then if [ "${#MISSING_REQUIRED_SYSTEM[@]}" -gt 0 ]; then echo install_system_requirements fi if check_python_environment_ready; then echo report_component ok "Python package install" "repo test and docs virtualenv is already ready" elif check_system_command "python3" && check_python_venv_support; then echo ensure_test_venv fi echo print_status_report fi echo if [ "${INSTALL_FAILURES}" -gt 0 ]; then echo "One or more test preparation steps failed; see the status checks above." >&2 exit 1 fi if [ "${READINESS_FAILURES}" -gt 0 ]; then if [ "${CHECK_ONLY}" -eq 1 ]; then echo "The FFX test and docs environment is incomplete." >&2 else echo "Required test or docs components are still missing after preparation." >&2 fi exit 1 fi if [ "${CHECK_ONLY}" -eq 1 ]; then echo "The FFX test and docs environment is ready." else echo "The FFX test and docs environment is prepared." fi } main "$@"