Files
ffx/tools/setup.sh
2026-04-09 13:34:38 +02:00

396 lines
9.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -u
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
VENV_DIR="${HOME}/.local/share/ffx.venv"
VENV_BIN_DIR="${VENV_DIR}/bin"
VENV_PYTHON="${VENV_BIN_DIR}/python"
VENV_PIP="${VENV_BIN_DIR}/pip"
VENV_FFX="${VENV_BIN_DIR}/ffx"
BASHRC_FILE="${HOME}/.bashrc"
ALIAS_BLOCK_BEGIN="# >>> ffx alias >>>"
ALIAS_BLOCK_END="# <<< ffx alias <<<"
ALIAS_LINE="alias ffx=\"${VENV_FFX}\""
CHECK_ONLY=0
WITH_TESTS=0
READINESS_FAILURES=0
INSTALL_FAILURES=0
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 <<EOF
Usage: $(basename "$0") [--check] [--with-tests] [--help]
Prepare the persistent FFX bundle installation at:
${VENV_DIR}
Actions:
- create or reuse ${VENV_DIR}
- install this repository into the venv with pip --editable
- ensure ${BASHRC_FILE} exposes alias ffx -> ${VENV_FFX}
- optionally install Python packages required for modern tests
Options:
--check Report readiness only. Do not create or modify anything.
--with-tests Also install and verify Python packages required for modern tests.
--help Show this help text.
Notes:
- This is the first installation step.
- tools/configure_workstation.sh is the second step and configures system dependencies plus local user files.
EOF
}
status_ok() {
printf '%sok%s' "${COLOR_GREEN}" "${COLOR_RESET}"
}
status_warn() {
printf '%swarn%s' "${COLOR_YELLOW}" "${COLOR_RESET}"
}
status_fail() {
printf '%sfailed%s' "${COLOR_RED}" "${COLOR_RESET}"
}
report_component() {
local level="$1"
local label="$2"
local detail="$3"
local rendered_status=""
case "${level}" in
ok)
rendered_status="$(status_ok)"
;;
warn)
rendered_status="$(status_warn)"
;;
*)
rendered_status="$(status_fail)"
;;
esac
printf '[%s] %s%s\n' "${rendered_status}" "${label}" "${detail:+: $detail}"
}
command_exists() {
command -v "$1" >/dev/null 2>&1
}
check_python3() {
command_exists python3
}
check_venv_dir() {
[ -x "${VENV_PYTHON}" ]
}
check_venv_pip() {
check_venv_dir && "${VENV_PIP}" --version >/dev/null 2>&1
}
check_venv_ffx() {
[ -x "${VENV_FFX}" ]
}
check_venv_pytest() {
check_venv_dir && "${VENV_PYTHON}" -m pytest --version >/dev/null 2>&1
}
check_bashrc_file() {
[ -f "${BASHRC_FILE}" ]
}
check_bashrc_alias() {
check_bashrc_file && grep -Fqx "${ALIAS_LINE}" "${BASHRC_FILE}"
}
detail_python3() {
command -v python3 || printf "command 'python3' not found"
}
detail_venv_dir() {
if check_venv_dir; then
printf '%s' "${VENV_DIR}"
else
printf 'missing %s' "${VENV_DIR}"
fi
}
detail_venv_pip() {
if check_venv_pip; then
"${VENV_PIP}" --version
else
printf 'missing pip in %s' "${VENV_DIR}"
fi
}
detail_venv_ffx() {
if check_venv_ffx; then
printf '%s' "${VENV_FFX}"
else
printf 'missing %s' "${VENV_FFX}"
fi
}
detail_venv_pytest() {
if check_venv_pytest; then
"${VENV_PYTHON}" -m pytest --version 2>/dev/null | head -n 1
else
printf 'missing pytest in %s' "${VENV_DIR}"
fi
}
detail_bashrc_file() {
if check_bashrc_file; then
printf '%s' "${BASHRC_FILE}"
else
printf 'missing %s; prep can create it' "${BASHRC_FILE}"
fi
}
detail_bashrc_alias() {
if check_bashrc_alias; then
printf '%s' "${ALIAS_LINE}"
else
printf 'missing alias line for %s' "${VENV_FFX}"
fi
}
print_status_report() {
READINESS_FAILURES=0
echo "Dependency status:"
if check_python3; then
report_component ok "python3" "$(detail_python3)"
else
report_component failed "python3" "$(detail_python3)"
READINESS_FAILURES=$((READINESS_FAILURES + 1))
fi
echo
echo "Bundle venv status:"
if check_venv_dir; then
report_component ok "bundle virtualenv" "$(detail_venv_dir)"
else
report_component failed "bundle virtualenv" "$(detail_venv_dir)"
READINESS_FAILURES=$((READINESS_FAILURES + 1))
fi
if check_venv_pip; then
report_component ok "bundle pip" "$(detail_venv_pip)"
else
report_component failed "bundle pip" "$(detail_venv_pip)"
READINESS_FAILURES=$((READINESS_FAILURES + 1))
fi
if check_venv_ffx; then
report_component ok "bundle ffx" "$(detail_venv_ffx)"
else
report_component failed "bundle ffx" "$(detail_venv_ffx)"
READINESS_FAILURES=$((READINESS_FAILURES + 1))
fi
if [ "${WITH_TESTS}" -eq 1 ]; then
echo
echo "Bundle test package status:"
if check_venv_pytest; then
report_component ok "bundle pytest" "$(detail_venv_pytest)"
else
report_component failed "bundle pytest" "$(detail_venv_pytest)"
READINESS_FAILURES=$((READINESS_FAILURES + 1))
fi
fi
echo
echo "Shell exposure status:"
if check_bashrc_file; then
report_component ok ".bashrc" "$(detail_bashrc_file)"
else
report_component warn ".bashrc" "$(detail_bashrc_file)"
fi
if check_bashrc_alias; then
report_component ok "ffx alias" "$(detail_bashrc_alias)"
else
report_component failed "ffx alias" "$(detail_bashrc_alias)"
READINESS_FAILURES=$((READINESS_FAILURES + 1))
fi
}
ensure_bundle_venv() {
mkdir -p "${HOME}/.local/share"
if ! check_venv_dir; then
printf 'Creating bundle virtualenv at %s...\n' "${VENV_DIR}"
if ! python3 -m venv "${VENV_DIR}"; then
printf 'Failed to create 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
if [ "${WITH_TESTS}" -eq 1 ]; then
printf 'Installing FFX package and test extras into %s...\n' "${VENV_DIR}"
if ! (
cd "${ROOT_DIR}" &&
"${VENV_PIP}" install --editable '.[test]'
); then
printf 'Failed to install FFX package and test extras into %s.\n' "${VENV_DIR}" >&2
INSTALL_FAILURES=$((INSTALL_FAILURES + 1))
return 1
fi
else
printf 'Installing FFX package into %s...\n' "${VENV_DIR}"
if ! "${VENV_PIP}" install --editable "${ROOT_DIR}"; then
printf 'Failed to install FFX package into %s.\n' "${VENV_DIR}" >&2
INSTALL_FAILURES=$((INSTALL_FAILURES + 1))
return 1
fi
fi
return 0
}
write_alias_block() {
local bashrc_dir
bashrc_dir="$(dirname "${BASHRC_FILE}")"
mkdir -p "${bashrc_dir}"
touch "${BASHRC_FILE}"
if grep -Fq "${ALIAS_BLOCK_BEGIN}" "${BASHRC_FILE}" || grep -Fq "${ALIAS_BLOCK_END}" "${BASHRC_FILE}"; then
if ! python3 - "${BASHRC_FILE}" "${ALIAS_BLOCK_BEGIN}" "${ALIAS_BLOCK_END}" "${ALIAS_LINE}" <<'PY'
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
begin = sys.argv[2]
end = sys.argv[3]
alias_line = sys.argv[4]
content = path.read_text()
block = f"{begin}\n{alias_line}\n{end}\n"
start = content.find(begin)
stop = content.find(end)
if start != -1 and stop != -1 and stop >= start:
stop += len(end)
if stop < len(content) and content[stop] == "\n":
stop += 1
content = content[:start] + block + content[stop:]
else:
if content and not content.endswith("\n"):
content += "\n"
content += block
path.write_text(content)
PY
then
printf 'Failed to update managed alias block in %s.\n' "${BASHRC_FILE}" >&2
INSTALL_FAILURES=$((INSTALL_FAILURES + 1))
return 1
fi
elif check_bashrc_alias; then
:
else
{
if [ -s "${BASHRC_FILE}" ] && [ "$(tail -c 1 "${BASHRC_FILE}" 2>/dev/null || true)" != "" ]; then
printf '\n'
fi
printf '%s\n' "${ALIAS_BLOCK_BEGIN}"
printf '%s\n' "${ALIAS_LINE}"
printf '%s\n' "${ALIAS_BLOCK_END}"
} >>"${BASHRC_FILE}" || {
printf 'Failed to append alias block to %s.\n' "${BASHRC_FILE}" >&2
INSTALL_FAILURES=$((INSTALL_FAILURES + 1))
return 1
}
fi
return 0
}
ensure_bashrc_alias() {
printf 'Ensuring ffx alias in %s...\n' "${BASHRC_FILE}"
write_alias_block
}
parse_args() {
while [ "$#" -gt 0 ]; do
case "$1" in
--check)
CHECK_ONLY=1
;;
--with-tests)
WITH_TESTS=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 ! check_python3; then
printf '\npython3 is required before the bundle venv can be prepared.\n' >&2
exit 1
fi
echo
ensure_bundle_venv
ensure_bashrc_alias
echo
print_status_report
fi
echo
if [ "${INSTALL_FAILURES}" -gt 0 ]; then
echo "One or more bundle preparation steps failed; see the status checks above." >&2
exit 1
fi
if [ "${READINESS_FAILURES}" -gt 0 ]; then
echo "The FFX bundle virtualenv and/or alias setup is incomplete." >&2
exit 1
fi
echo "The FFX bundle virtualenv is ready."
}
main "$@"