setup-deps.sh 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. #!/usr/bin/env bash
  2. # Standard-of-Iron — dependency checker and auto-installer (Debian/Ubuntu family)
  3. #
  4. # Verifies required toolchain and Qt/QML runtime modules and installs any missing ones.
  5. # Safe to run multiple times. Requires sudo privileges for installation.
  6. #
  7. # Usage:
  8. # ./scripts/setup-deps.sh # interactive install as needed
  9. # ./scripts/setup-deps.sh --yes # non-interactive (assume yes)
  10. # ./scripts/setup-deps.sh --dry-run # show actions without installing
  11. # ./scripts/setup-deps.sh --no-install # only verify, do not install
  12. # ./scripts/setup-deps.sh --allow-similar # allow proceeding on similar distros
  13. #
  14. set -euo pipefail
  15. MIN_CMAKE="3.21.0"
  16. MIN_GXX="10.0.0"
  17. ASSUME_YES=false
  18. DRY_RUN=false
  19. NO_INSTALL=false
  20. ALLOW_SIMILAR=false
  21. for arg in "$@"; do
  22. case "$arg" in
  23. -y|--yes) ASSUME_YES=true ;;
  24. --dry-run) DRY_RUN=true ;;
  25. --no-install) NO_INSTALL=true ;;
  26. --allow-similar) ALLOW_SIMILAR=true ;;
  27. -h|--help)
  28. grep '^#' "$0" | sed -e 's/^# \{0,1\}//'
  29. exit 0
  30. ;;
  31. *) echo "Unknown option: $arg" >&2; exit 2 ;;
  32. esac
  33. done
  34. info() { echo -e "\033[1;34m[i]\033[0m $*"; }
  35. ok() { echo -e "\033[1;32m[✓]\033[0m $*"; }
  36. warn() { echo -e "\033[1;33m[!]\033[0m $*"; }
  37. err() { echo -e "\033[1;31m[x]\033[0m $*"; }
  38. need_cmd() {
  39. if ! command -v "$1" >/dev/null 2>&1; then
  40. err "Required command '$1' not found"
  41. return 1
  42. fi
  43. }
  44. semver_ge() {
  45. # returns 0 if $1 >= $2
  46. local IFS=. a1 a2 a3 b1 b2 b3
  47. set -- $1; a1=${1:-0}; a2=${2:-0}; a3=${3:-0}
  48. set -- $2; b1=${1:-0}; b2=${2:-0}; b3=${3:-0}
  49. if [ "$a1" -gt "$b1" ]; then return 0; fi
  50. if [ "$a1" -lt "$b1" ]; then return 1; fi
  51. if [ "$a2" -gt "$b2" ]; then return 0; fi
  52. if [ "$a2" -lt "$b2" ]; then return 1; fi
  53. if [ "$a3" -ge "$b3" ]; then return 0; fi
  54. return 1
  55. }
  56. read_os_release() {
  57. # shellcheck disable=SC1091
  58. if [ -f /etc/os-release ]; then
  59. . /etc/os-release
  60. echo "ID=${ID:-unknown}"
  61. echo "ID_LIKE=${ID_LIKE:-}"
  62. echo "PRETTY_NAME=${PRETTY_NAME:-}"
  63. else
  64. echo "ID=unknown"
  65. echo "ID_LIKE="
  66. echo "PRETTY_NAME="
  67. fi
  68. }
  69. has_apt() { command -v apt-get >/dev/null 2>&1; }
  70. is_deb_family_exact() {
  71. case "$1" in
  72. ubuntu|debian) return 0 ;;
  73. *) return 1 ;;
  74. esac
  75. }
  76. is_deb_family_like() {
  77. # $1: ID_LIKE string
  78. case " $1 " in
  79. *" debian "*|*" ubuntu "*) return 0 ;;
  80. *) return 1 ;;
  81. esac
  82. }
  83. detect_distro() {
  84. local id like pretty
  85. while IFS='=' read -r k v; do
  86. case "$k" in
  87. ID) id=${v} ;;
  88. ID_LIKE) like=${v} ;;
  89. PRETTY_NAME) pretty=${v} ;;
  90. esac
  91. done < <(read_os_release)
  92. # strip quotes if present
  93. id=${id%\"}; id=${id#\"}
  94. like=${like%\"}; like=${like#\"}
  95. pretty=${pretty%\"}; pretty=${pretty#\"}
  96. echo "$id" "$like" "$pretty"
  97. }
  98. # Base toolchain and common libs
  99. APT_PKGS=(
  100. build-essential
  101. cmake
  102. git
  103. pkg-config
  104. libgl1-mesa-dev
  105. mesa-utils
  106. libglx-mesa0
  107. mesa-vulkan-drivers
  108. libegl1
  109. )
  110. # Qt6 development headers/tools (filtered for availability later)
  111. QT6_DEV_PKGS=(
  112. qt6-base-dev
  113. qt6-base-dev-tools
  114. qt6-declarative-dev
  115. qt6-tools-dev
  116. qt6-tools-dev-tools
  117. )
  118. # Qt6 QML runtime modules (filtered for availability)
  119. QT6_QML_RUN_PKGS=(
  120. qml6-module-qtqml
  121. qml6-module-qtquick
  122. qml6-module-qtquick-window
  123. qml6-module-qtquick-layouts
  124. qml6-module-qtquick-controls
  125. qml6-module-qtqml-workerscript
  126. qml6-module-qtquick-templates
  127. )
  128. # Fallback Qt5 QML runtime modules (only installed if present in repos)
  129. QT5_QML_RUN_PKGS=(
  130. qml-module-qtqml
  131. qml-module-qtquick2
  132. qml-module-qtquick-window2
  133. qml-module-qtquick-layouts
  134. qml-module-qtquick-controls2
  135. qtbase5-dev
  136. qtdeclarative5-dev
  137. )
  138. apt_pkg_available() {
  139. apt-cache show "$1" >/dev/null 2>&1
  140. }
  141. filter_available_pkgs() {
  142. local out=() p
  143. for p in "$@"; do
  144. if apt_pkg_available "$p"; then
  145. out+=("$p")
  146. fi
  147. done
  148. printf '%s\n' "${out[@]}"
  149. }
  150. check_tool_versions() {
  151. info "Checking toolchain versions"
  152. need_cmd cmake
  153. local cmv
  154. cmv=$(cmake --version | head -n1 | awk '{print $3}')
  155. if semver_ge "$cmv" "$MIN_CMAKE"; then
  156. ok "cmake $cmv (>= $MIN_CMAKE)"
  157. else
  158. warn "cmake $cmv (< $MIN_CMAKE) — will attempt to install newer via apt"
  159. fi
  160. need_cmd g++
  161. local gxv
  162. gxv=$(g++ --version | head -n1 | awk '{print $NF}')
  163. if semver_ge "$gxv" "$MIN_GXX"; then
  164. ok "g++ $gxv (>= $MIN_GXX)"
  165. else
  166. warn "g++ $gxv (< $MIN_GXX) — will attempt to install build-essential"
  167. fi
  168. need_cmd git; ok "git $(git --version | awk '{print $3}')"
  169. need_cmd pkg-config; ok "pkg-config $(pkg-config --version)"
  170. }
  171. dpkg_installed() {
  172. dpkg -s "$1" >/dev/null 2>&1
  173. }
  174. apt_update_once() {
  175. if [ "${_APT_UPDATED:-0}" != 1 ]; then
  176. info "Updating apt package lists"
  177. if $DRY_RUN; then
  178. echo "sudo apt-get update -y"
  179. else
  180. sudo apt-get update -y
  181. fi
  182. _APT_UPDATED=1
  183. fi
  184. }
  185. apt_install() {
  186. local to_install=()
  187. for pkg in "$@"; do
  188. if [ -z "${pkg:-}" ]; then
  189. continue
  190. fi
  191. if dpkg_installed "$pkg"; then
  192. ok "$pkg already installed"
  193. else
  194. to_install+=("$pkg")
  195. fi
  196. done
  197. if [ ${#to_install[@]} -gt 0 ]; then
  198. if $NO_INSTALL; then
  199. warn "Missing packages: ${to_install[*]} (skipping install due to --no-install)"
  200. return 0
  201. fi
  202. if ! $ASSUME_YES; then
  203. echo
  204. read -r -p "Install missing packages: ${to_install[*]} ? [Y/n] " ans
  205. case "${ans:-Y}" in
  206. y|Y) ;;
  207. *) warn "User declined install"; return 0 ;;
  208. esac
  209. fi
  210. apt_update_once
  211. info "Installing: ${to_install[*]}"
  212. if $DRY_RUN; then
  213. echo "sudo apt-get install -y ${to_install[*]}"
  214. else
  215. DEBIAN_FRONTEND=noninteractive sudo apt-get install -y "${to_install[@]}"
  216. fi
  217. fi
  218. }
  219. check_qt_runtime() {
  220. info "Installing base toolchain"
  221. apt_install "${APT_PKGS[@]}"
  222. info "Installing Qt6 SDK/dev packages"
  223. mapfile -t _qt6dev < <(filter_available_pkgs "${QT6_DEV_PKGS[@]}")
  224. apt_install "${_qt6dev[@]}"
  225. info "Installing Qt6 QML runtime modules"
  226. mapfile -t _qt6qml < <(filter_available_pkgs "${QT6_QML_RUN_PKGS[@]}")
  227. apt_install "${_qt6qml[@]}"
  228. info "Installing Qt5 QML runtime modules (fallback, if available)"
  229. mapfile -t _qt5qml < <(filter_available_pkgs "${QT5_QML_RUN_PKGS[@]}")
  230. apt_install "${_qt5qml[@]}"
  231. }
  232. main() {
  233. local id like pretty
  234. read -r id like pretty < <(detect_distro)
  235. info "Detected system: ${pretty:-$id} (ID=$id; ID_LIKE='${like:-}')."
  236. if is_deb_family_exact "$id"; then
  237. info "Exact Debian/Ubuntu family detected ($id)."
  238. elif is_deb_family_like "${like:-}" && has_apt; then
  239. warn "No exact match, but this system is *similar* to Debian/Ubuntu and has apt-get."
  240. if $ALLOW_SIMILAR || $ASSUME_YES; then
  241. info "Proceeding with Debian/Ubuntu-compatible steps due to --allow-similar/--yes."
  242. else
  243. echo
  244. read -r -p "Proceed using Debian/Ubuntu package set on this similar distro? [Y/n] " ans
  245. case "${ans:-Y}" in
  246. y|Y) info "Continuing with Debian/Ubuntu-compatible steps." ;;
  247. *) err "User declined proceeding on a similar distro."; exit 1 ;;
  248. esac
  249. fi
  250. else
  251. warn "Unsupported distro '$id'. This script targets apt-based Debian/Ubuntu family."
  252. if has_apt; then
  253. warn "apt-get is present, but ID/ID_LIKE do not indicate Debian/Ubuntu."
  254. if $ALLOW_SIMILAR || $ASSUME_YES; then
  255. info "Proceeding anyway due to --allow-similar/--yes."
  256. else
  257. read -r -p "Proceed anyway using apt? (may or may not work) [y/N] " ans
  258. case "${ans:-N}" in
  259. y|Y) info "Continuing with apt-based steps." ;;
  260. *) err "Exiting to avoid misconfiguration. Use --allow-similar to override."; exit 1 ;;
  261. esac
  262. fi
  263. else
  264. err "No apt-get found and distro is not Debian/Ubuntu-like. Please install equivalent packages manually:
  265. ${APT_PKGS[*]} ${QT6_DEV_PKGS[*]} ${QT6_QML_RUN_PKGS[*]}"
  266. exit 1
  267. fi
  268. fi
  269. check_tool_versions
  270. check_qt_runtime
  271. echo
  272. ok "All required dependencies are present (or have been installed)."
  273. echo "- cmake >= $MIN_CMAKE"
  274. echo "- g++ >= $MIN_GXX"
  275. echo "- Qt 6 base + declarative + tools + QML runtime modules (Qt5 QML fallback if available)"
  276. }
  277. main "$@"