env-bootstrap.sh 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. #!/usr/bin/env sh
  2. # Copyright 2025 Nick Brassel (@tzarc)
  3. # SPDX-License-Identifier: GPL-2.0-or-later
  4. ################################################################################
  5. # This script will install the QMK CLI, toolchains, and flashing utilities.
  6. ################################################################################
  7. # Environment variables:
  8. # CONFIRM: Skip the pre-install delay. (or: --confirm)
  9. # QMK_DISTRIB_DIR: The directory to install the QMK distribution to. (or: --qmk-distrib-dir=...)
  10. # UV_INSTALL_DIR: The directory to install `uv` to. (or: --uv-install-dir=...)
  11. # UV_TOOL_DIR: The directory to install `uv` tools to. (or: --uv-tool-dir=...)
  12. # SKIP_CLEAN: Skip cleaning the distribution directory. (or: --skip-clean)
  13. # SKIP_PACKAGE_MANAGER: Skip installing the necessary packages for the package manager. (or: --skip-package-manager)
  14. # SKIP_UV: Skip installing `uv`. (or: --skip-uv)
  15. # SKIP_QMK_CLI: Skip installing the QMK CLI. (or: --skip-qmk-cli)
  16. # SKIP_QMK_TOOLCHAINS: Skip installing the QMK toolchains. (or: --skip-qmk-toolchains)
  17. # SKIP_QMK_FLASHUTILS: Skip installing the QMK flashing utilities. (or: --skip-qmk-flashutils)
  18. # SKIP_UDEV_RULES: Skip installing the udev rules for Linux. (or: --skip-udev-rules)
  19. # SKIP_WINDOWS_DRIVERS: Skip installing the Windows drivers for the flashing utilities. (or: --skip-windows-drivers)
  20. #
  21. # Arguments above may be negated by prefixing with `--no-` instead (e.g. `--no-skip-clean`).
  22. ################################################################################
  23. # Usage:
  24. # curl -fsSL https://raw.githubusercontent.com/qmk/qmk_firmware/master/util/env-bootstrap.sh | sh
  25. #
  26. # Help:
  27. # curl -fsSL https://raw.githubusercontent.com/qmk/qmk_firmware/master/util/env-bootstrap.sh | sh -s -- --help
  28. #
  29. # An example which skips installing `uv` using environment variables:
  30. # curl -fsSL https://raw.githubusercontent.com/qmk/qmk_firmware/master/util/env-bootstrap.sh | SKIP_UV=1 sh
  31. #
  32. # ...or by using command line arguments:
  33. # curl -fsSL https://raw.githubusercontent.com/qmk/qmk_firmware/master/util/env-bootstrap.sh | sh -s -- --skip-uv
  34. #
  35. # Any other configurable items listed above may be specified in the same way.
  36. ################################################################################
  37. { # this ensures the entire script is downloaded #
  38. set -eu
  39. BOOTSTRAP_TMPDIR="$(mktemp -d /tmp/qmk-bootstrap-failure.XXXXXX)"
  40. trap 'rm -rf "$BOOTSTRAP_TMPDIR" >/dev/null 2>&1 || true' EXIT
  41. FAILURE_FILE="${BOOTSTRAP_TMPDIR}/fail"
  42. # Work out which `sed` to use
  43. command -v gsed >/dev/null 2>&1 && SED=gsed || SED=sed
  44. script_args() {
  45. cat <<__EOT__
  46. --help -- Shows this help text
  47. --confirm -- Skips the delay before installation
  48. --uv-install-dir={path} -- The directory to install \`uv\` into
  49. --uv-tool-dir={path} -- The directory to install \`uv\` tools into
  50. --qmk-distrib-dir={path} -- The directory to install the QMK distribution into
  51. --skip-clean -- Skip cleaning the QMK distribution directory
  52. --skip-package-manager -- Skip installing the necessary packages for the package manager
  53. --skip-uv -- Skip installing \`uv\`
  54. --skip-qmk-cli -- Skip installing the QMK CLI
  55. --skip-qmk-toolchains -- Skip installing the QMK toolchains
  56. --skip-qmk-flashutils -- Skip installing the QMK flashing utilities
  57. --skip-udev-rules -- Skip installing the udev rules for Linux
  58. --skip-windows-drivers -- Skip installing the Windows drivers for the flashing utilities
  59. __EOT__
  60. # Hidden:
  61. # --wsl-install -- Installs the WSL variant of qmk_flashutils
  62. }
  63. signal_execution_failure() {
  64. touch "$FAILURE_FILE" >/dev/null 2>&1 || true
  65. }
  66. exit_if_execution_failed() {
  67. if [ -e "$FAILURE_FILE" ]; then
  68. exit 1
  69. fi
  70. }
  71. script_help() {
  72. echo "$(basename ${this_script:-qmk-install.sh}) $(script_args | sort | ${SED} -e 's@^\s*@@g' -e 's@\s\+--.*@@g' -e 's@^@[@' -e 's@$@]@' | tr '\n' ' ')"
  73. echo
  74. echo "Arguments:"
  75. script_args
  76. echo
  77. echo "Switch arguments may be negated by prefixing with '--no-' (e.g. '--no-skip-clean')."
  78. }
  79. script_parse_args() {
  80. local N
  81. local V
  82. while [ ! -z "${1:-}" ]; do
  83. case "$1" in
  84. --help)
  85. script_help
  86. exit 0
  87. ;;
  88. --*=*)
  89. N=${1%%=*}
  90. N=${N##--}
  91. N=$(echo $N | tr '-' '_' | tr 'a-z' 'A-Z')
  92. V=${1##*=}
  93. export $N="$V"
  94. ;;
  95. --no-*)
  96. N=${1##--no-}
  97. N=$(echo $N | tr '-' '_' | tr 'a-z' 'A-Z')
  98. unset $N
  99. ;;
  100. --*)
  101. N=${1##--}
  102. N=$(echo $N | tr '-' '_' | tr 'a-z' 'A-Z')
  103. export $N=true
  104. ;;
  105. *)
  106. echo "Unknown argument: '$1'" >&2
  107. echo
  108. script_help >&2
  109. exit 1
  110. ;;
  111. esac
  112. shift
  113. unset N
  114. unset V
  115. done
  116. }
  117. nsudo() {
  118. if [ "$(fn_os)" = "windows" ]; then
  119. # No need for sudo under QMK MSYS
  120. return
  121. elif [ $(id -u) -ne 0 ]; then
  122. if [ -n "$(command -v sudo 2>/dev/null || true)" ]; then
  123. echo "sudo"
  124. elif [ -n "$(command -v doas 2>/dev/null || true)" ]; then
  125. echo "doas"
  126. else
  127. echo "Please install 'sudo' or 'doas' to continue." >&2
  128. exit 1
  129. fi
  130. fi
  131. true
  132. }
  133. download_url() {
  134. local url=$1
  135. local filename=${2:-$(basename "$url")}
  136. local quiet=''
  137. if [ -n "$(command -v curl 2>/dev/null || true)" ]; then
  138. [ "$filename" = "-" ] && quiet='-s' || echo "Downloading '$url' => '$filename'" >&2
  139. curl -LSf $quiet -o "$filename" "$url"
  140. elif [ -n "$(command -v wget 2>/dev/null || true)" ]; then
  141. [ "$filename" = "-" ] && quiet='-q' || echo "Downloading '$url' => '$filename'" >&2
  142. wget $quiet "-O$filename" "$url"
  143. else
  144. echo "Please install 'curl' to continue." >&2
  145. exit 1
  146. fi
  147. }
  148. github_api_call() {
  149. local url="$1"
  150. local token="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
  151. if [ -n "${token:-}" ]; then
  152. if [ -n "$(command -v curl 2>/dev/null || true)" ]; then
  153. curl -fsSL -H "Authorization: token $token" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/$url"
  154. elif [ -n "$(command -v wget 2>/dev/null || true)" ]; then
  155. wget -q --header="Authorization: token $token" --header="Accept: application/vnd.github.v3+json" "https://api.github.com/$url" -O -
  156. fi
  157. else
  158. download_url "https://api.github.com/$url" -
  159. fi
  160. }
  161. fn_os() {
  162. local os_name=$(echo ${1:-} | tr 'A-Z' 'a-z')
  163. if [ -z "$os_name" ]; then
  164. os_name=$(uname -s | tr 'A-Z' 'a-z')
  165. fi
  166. case "$os_name" in
  167. *darwin* | *macos* | *apple*)
  168. echo macos
  169. ;;
  170. *windows* | *mingw* | *msys*)
  171. echo windows
  172. ;;
  173. *linux*)
  174. echo linux
  175. ;;
  176. *)
  177. echo unknown
  178. ;;
  179. esac
  180. }
  181. fn_arch() {
  182. local arch_name=$(echo ${1:-} | tr 'A-Z' 'a-z')
  183. if [ -z "$arch_name" ]; then
  184. arch_name=$(uname -m | tr 'A-Z' 'a-z')
  185. fi
  186. case "$arch_name" in
  187. *arm64* | *aarch64*)
  188. echo ARM64
  189. ;;
  190. *riscv64*)
  191. echo RV64
  192. ;;
  193. *x86_64* | *x64*)
  194. echo X64
  195. ;;
  196. *)
  197. echo unknown
  198. ;;
  199. esac
  200. }
  201. preinstall_delay() {
  202. [ -z "${CONFIRM:-}" ] || return 0
  203. echo >&2
  204. echo "Waiting 10 seconds before proceeding. Press Ctrl+C to cancel installation." >&2
  205. sleep 10
  206. }
  207. get_package_manager_deps() {
  208. case $(fn_os) in
  209. macos) echo "zstd clang-format make hidapi libusb dos2unix git" ;;
  210. windows) echo "base-devel: zstd:p toolchain:p clang:p hidapi:p dos2unix: git: unzip:" ;;
  211. linux)
  212. case $(grep ID /etc/os-release) in
  213. *arch* | *manjaro* | *cachyos*) echo "zstd base-devel clang diffutils wget unzip zip hidapi dos2unix git" ;;
  214. *debian* | *ubuntu*) echo "zstd build-essential clang-format diffutils wget unzip zip libhidapi-hidraw0 dos2unix git" ;;
  215. *fedora*) echo "zstd clang diffutils which gcc git wget unzip zip hidapi dos2unix libusb-devel libusb1-devel libusb-compat-0.1-devel libusb0-devel git epel-release" ;;
  216. *suse*) echo "zstd clang diffutils wget unzip zip libhidapi-hidraw0 dos2unix git libusb-1_0-devel gzip which" ;;
  217. *gentoo*) echo "zstd diffutils wget unzip zip dev-libs/hidapi dos2unix dev-vcs/git dev-libs/libusb app-arch/gzip which" ;;
  218. *)
  219. echo >&2
  220. echo "Sorry, we don't recognize your distribution." >&2
  221. echo >&2
  222. echo "Proceeding with the installation, however you will need to install at least the following tools manually:" >&2
  223. echo " - make, git, curl, zstd, unzip, [lib]hidapi" >&2
  224. echo "Other tools may be required depending on your distribution." >&2
  225. echo >&2
  226. echo "Alternatively, if you prefer Docker, try using the docker image instead:" >&2
  227. echo " - https://docs.qmk.fm/#/getting_started_docker" >&2
  228. ;;
  229. esac
  230. ;;
  231. *)
  232. # We can only really support macOS, Windows, and Linux at this time due to `uv` requirements.
  233. echo >&2
  234. echo "Sorry, we don't recognize your OS. Try using a compatible OS instead:" >&2
  235. echo " - https://docs.qmk.fm/newbs_getting_started#set-up-your-environment" >&2
  236. echo >&2
  237. echo "If you cannot use a compatible OS, you can try installing the \`qmk\` Python package manually using \`pip\`, most likely requiring a virtual environment:" >&2
  238. echo " % python3 -m pip install qmk" >&2
  239. echo >&2
  240. echo "All other dependencies will need to be installed manually, such as make, git, AVR and ARM toolchains, and associated flashing utilities." >&2
  241. echo >&2
  242. echo "**NOTE**: QMK does not provide official support for your environment. Here be dragons, you are on your own." >&2
  243. signal_execution_failure
  244. ;;
  245. esac
  246. }
  247. print_package_manager_deps_and_delay() {
  248. get_package_manager_deps | tr ' ' '\n' | sort | xargs -I'{}' echo " - {}" >&2
  249. exit_if_execution_failed
  250. preinstall_delay || exit 1
  251. }
  252. install_package_manager_deps() {
  253. # Install the necessary packages for the package manager
  254. case $(fn_os) in
  255. macos)
  256. if [ -n "$(command -v brew 2>/dev/null || true)" ]; then
  257. echo "It will also install the following system packages using 'brew':" >&2
  258. print_package_manager_deps_and_delay
  259. brew update
  260. local existing=""
  261. local new=""
  262. for dep in $(get_package_manager_deps); do
  263. if brew list --formula | grep -q "^${dep}\$"; then
  264. existing="${existing:-} $dep"
  265. else
  266. new="${new:-} $dep"
  267. fi
  268. done
  269. if [ -n "${existing:-}" ]; then
  270. brew upgrade $existing
  271. fi
  272. if [ -n "${new:-}" ]; then
  273. brew install $new
  274. fi
  275. else
  276. echo "Please install 'brew' to continue. See https://brew.sh/ for more information." >&2
  277. exit 1
  278. fi
  279. ;;
  280. windows)
  281. echo "It will also install the following packages using 'pacman'/'pacboy':" >&2
  282. print_package_manager_deps_and_delay
  283. $(nsudo) pacman --needed --noconfirm --disable-download-timeout -S pactoys
  284. $(nsudo) pacboy sync --needed --noconfirm --disable-download-timeout $(get_package_manager_deps)
  285. ;;
  286. linux)
  287. case $(grep ID /etc/os-release) in
  288. *arch* | *manjaro* | *cachyos*)
  289. echo "It will also install the following system packages using 'pacman':" >&2
  290. print_package_manager_deps_and_delay
  291. $(nsudo) pacman --needed --noconfirm -S $(get_package_manager_deps)
  292. ;;
  293. *debian* | *ubuntu*)
  294. echo "It will also install the following system packages using 'apt':" >&2
  295. print_package_manager_deps_and_delay
  296. $(nsudo) apt-get update
  297. DEBIAN_FRONTEND=noninteractive \
  298. $(nsudo) apt-get --quiet --yes install $(get_package_manager_deps)
  299. ;;
  300. *fedora*)
  301. echo "It will also install the following system packages using 'dnf':" >&2
  302. print_package_manager_deps_and_delay
  303. # Some RHEL-likes need EPEL for hidapi
  304. $(nsudo) dnf -y install epel-release 2>/dev/null || true
  305. # RHEL-likes have some naming differences in libusb packages, so manually handle those
  306. $(nsudo) dnf -y install $(get_package_manager_deps | tr ' ' '\n' | grep -v 'epel-release' | grep -v libusb | tr '\n' ' ')
  307. for pkg in $(get_package_manager_deps | tr ' ' '\n' | grep libusb); do
  308. $(nsudo) dnf -y install "$pkg" 2>/dev/null || true
  309. done
  310. ;;
  311. *opensuse* | *suse*)
  312. echo "It will also install development tools as well as the following system packages using 'zypper':" >&2
  313. print_package_manager_deps_and_delay
  314. $(nsudo) zypper --non-interactive refresh
  315. $(nsudo) zypper --non-interactive install -t pattern devel_basis devel_C_C++
  316. $(nsudo) zypper --non-interactive install $(get_package_manager_deps)
  317. ;;
  318. *gentoo*)
  319. echo "It will also install the following system packages using 'emerge':" >&2
  320. print_package_manager_deps_and_delay
  321. $(nsudo) emerge --sync
  322. $(nsudo) emerge --noreplace --ask=n $(get_package_manager_deps | tr ' ' '\n') || signal_execution_failure
  323. exit_if_execution_failed
  324. ;;
  325. *)
  326. print_package_manager_deps_and_delay
  327. echo "Proceeding with the installation, you will need to ensure prerequisites are installed." >&2
  328. ;;
  329. esac
  330. ;;
  331. *)
  332. print_package_manager_deps_and_delay
  333. ;;
  334. esac
  335. }
  336. install_uv() {
  337. # Install `uv` (or update as necessary)
  338. download_url https://astral.sh/uv/install.sh - | TMPDIR="$(windows_ish_path "${TMPDIR:-}")" UV_INSTALL_DIR="$(windows_ish_path "${UV_INSTALL_DIR:-}")" sh
  339. }
  340. setup_paths() {
  341. # Set up the paths for any of the locations `uv` expects
  342. if [ -n "${XDG_BIN_HOME:-}" ]; then
  343. export PATH="$XDG_BIN_HOME:$PATH"
  344. fi
  345. if [ -n "${XDG_DATA_HOME:-}" ]; then
  346. export PATH="$XDG_DATA_HOME/../bin:$PATH"
  347. fi
  348. [ ! -d "$HOME/.local/bin" ] || export PATH="$HOME/.local/bin:$PATH"
  349. if [ -n "${UV_INSTALL_DIR:-}" ]; then
  350. export PATH="$UV_INSTALL_DIR/bin:$UV_INSTALL_DIR:$PATH" # cater for both "flat" and "hierarchical" installs of `uv`
  351. fi
  352. if [ -n "${UV_TOOL_BIN_DIR:-}" ]; then
  353. export PATH="$UV_TOOL_BIN_DIR:$PATH"
  354. fi
  355. }
  356. uv_command() {
  357. if [ "$(fn_os)" = "windows" ]; then
  358. UV_TOOL_DIR="$(windows_ish_path "${UV_TOOL_DIR:-}")" \
  359. UV_TOOL_BIN_DIR="$(windows_ish_path "${UV_TOOL_BIN_DIR:-}")" \
  360. uv "$@"
  361. else
  362. uv "$@"
  363. fi
  364. }
  365. install_qmk_cli() {
  366. # Install the QMK CLI
  367. uv_command tool install --force --with pip --upgrade --python $PYTHON_TARGET_VERSION qmk
  368. # QMK is installed to...
  369. local qmk_tooldir="$(posix_ish_path "$(uv_command tool dir)/qmk")"
  370. # Activate the environment
  371. if [ -e "$qmk_tooldir/bin" ]; then
  372. . "$qmk_tooldir/bin/activate"
  373. elif [ -e "$qmk_tooldir/Scripts" ]; then
  374. . "$qmk_tooldir/Scripts/activate"
  375. else
  376. echo "Could not find the QMK environment to activate." >&2
  377. exit 1
  378. fi
  379. # Install the QMK dependencies
  380. uv_command pip install --upgrade -r https://raw.githubusercontent.com/qmk/qmk_firmware/refs/heads/master/requirements.txt
  381. uv_command pip install --upgrade -r https://raw.githubusercontent.com/qmk/qmk_firmware/refs/heads/master/requirements-dev.txt
  382. # Deactivate the environment
  383. deactivate
  384. }
  385. install_toolchains() {
  386. # Get the latest toolchain release from https://github.com/qmk/qmk_toolchains
  387. local latest_toolchains_release=$(github_api_call repos/qmk/qmk_toolchains/releases/latest - | grep -oE '"tag_name": "[^"]+' | grep -oE '[^"]+$')
  388. # Download the specific release asset with a matching keyword
  389. local toolchain_url=$(github_api_call repos/qmk/qmk_toolchains/releases/tags/$latest_toolchains_release - | grep -oE '"browser_download_url": "[^"]+"' | grep -oE 'https://[^"]+' | grep $(fn_os)$(fn_arch))
  390. if [ -z "$toolchain_url" ]; then
  391. echo "No toolchain found for this OS/Arch combination." >&2
  392. exit 1
  393. fi
  394. # Download the toolchain release to the toolchains location
  395. echo "Downloading compiler toolchains..." >&2
  396. local target_file="$QMK_DISTRIB_DIR/$(basename "$toolchain_url")"
  397. download_url "$toolchain_url" "$target_file"
  398. # Extract the toolchain
  399. echo "Extracting compiler toolchains to '$QMK_DISTRIB_DIR'..." >&2
  400. zstdcat "$target_file" | tar xf - -C "$QMK_DISTRIB_DIR" --strip-components=1
  401. }
  402. install_flashing_tools() {
  403. local osarchvariant="$(fn_os)$(fn_arch)"
  404. # Special case for WSL
  405. if [ -n "${WSL_INSTALL:-}" ] || [ -n "${WSL_DISTRO_NAME:-}" ] || [ -f /proc/sys/fs/binfmt_misc/WSLInterop ]; then
  406. osarchvariant="windowsWSL"
  407. fi
  408. # Get the latest flashing tools release from https://github.com/qmk/qmk_flashutils
  409. local latest_flashutils_release=$(github_api_call repos/qmk/qmk_flashutils/releases/latest - | grep -oE '"tag_name": "[^"]+' | grep -oE '[^"]+$')
  410. # Download the specific release asset with a matching keyword
  411. local flashutils_url=$(github_api_call repos/qmk/qmk_flashutils/releases/tags/$latest_flashutils_release - | grep -oE '"browser_download_url": "[^"]+"' | grep -oE 'https://[^"]+' | grep "$osarchvariant")
  412. if [ -z "$flashutils_url" ]; then
  413. echo "No flashing tools found for this OS/Arch combination." >&2
  414. exit 1
  415. fi
  416. # Download the flashing tools release to the toolchains location
  417. echo "Downloading flashing tools..." >&2
  418. local target_file="$QMK_DISTRIB_DIR/$(basename "$flashutils_url")"
  419. download_url "$flashutils_url" "$target_file"
  420. # Extract the flashing tools
  421. echo "Extracting flashing tools to '$QMK_DISTRIB_DIR'..." >&2
  422. zstdcat "$target_file" | tar xf - -C "$QMK_DISTRIB_DIR/bin"
  423. # Move the release file to etc
  424. mv "$QMK_DISTRIB_DIR/bin/flashutils_release"* "$QMK_DISTRIB_DIR/etc"
  425. }
  426. install_linux_udev_rules() {
  427. # Download the udev rules to the toolchains location
  428. echo "Downloading QMK udev rules file..." >&2
  429. local qmk_rules_target_file="$QMK_DISTRIB_DIR/50-qmk.rules"
  430. download_url "https://raw.githubusercontent.com/qmk/qmk_firmware/refs/heads/master/util/udev/50-qmk.rules" "$qmk_rules_target_file"
  431. # Install the udev rules -- path list is aligned with qmk doctor's linux.py
  432. local udev_rules_paths="
  433. /usr/lib/udev/rules.d
  434. /usr/local/lib/udev/rules.d
  435. /run/udev/rules.d
  436. /etc/udev/rules.d
  437. "
  438. for udev_rules_dir in $udev_rules_paths; do
  439. if [ -d "$udev_rules_dir" ]; then
  440. echo "Installing udev rules to $udev_rules_dir/50-qmk.rules ..." >&2
  441. $(nsudo) mv "$qmk_rules_target_file" "$udev_rules_dir"
  442. $(nsudo) chown 0:0 "$udev_rules_dir/50-qmk.rules"
  443. $(nsudo) chmod 644 "$udev_rules_dir/50-qmk.rules"
  444. break
  445. fi
  446. done
  447. # Reload udev rules
  448. if command -v udevadm >/dev/null 2>&1; then
  449. echo "Reloading udev rules..." >&2
  450. $(nsudo) udevadm control --reload-rules || true
  451. $(nsudo) udevadm trigger || true
  452. else
  453. echo "udevadm not found, skipping udev rules reload." >&2
  454. fi
  455. }
  456. install_windows_drivers() {
  457. # Get the latest driver installer release from https://github.com/qmk/qmk_driver_installer
  458. local latest_driver_installer_release=$(github_api_call repos/qmk/qmk_driver_installer/releases/latest - | grep -oE '"tag_name": "[^"]+' | grep -oE '[^"]+$')
  459. # Download the specific release asset
  460. local driver_installer_url=$(github_api_call repos/qmk/qmk_driver_installer/releases/tags/$latest_driver_installer_release - | grep -oE '"browser_download_url": "[^"]+"' | grep -oE 'https://[^"]+' | grep '\.exe')
  461. if [ -z "$driver_installer_url" ]; then
  462. echo "No driver installer found." >&2
  463. exit 1
  464. fi
  465. # Download the driver installer release to the toolchains location
  466. echo "Downloading driver installer..." >&2
  467. local target_file="$QMK_DISTRIB_DIR/$(basename "$driver_installer_url")"
  468. download_url "$driver_installer_url" "$target_file"
  469. # Download the drivers list
  470. download_url "https://raw.githubusercontent.com/qmk/qmk_firmware/refs/heads/master/util/drivers.txt" "$QMK_DISTRIB_DIR/drivers.txt"
  471. # Execute the driver installer
  472. cd "$QMK_DISTRIB_DIR"
  473. cmd.exe //c "qmk_driver_installer.exe --all --force drivers.txt"
  474. cd -
  475. # Remove the temporary files
  476. rm -f "$QMK_DISTRIB_DIR/qmk_driver_installer.exe" "$QMK_DISTRIB_DIR/drivers.txt" || true
  477. }
  478. clean_tarballs() {
  479. # Clean up the tarballs
  480. rm -f "$QMK_DISTRIB_DIR"/*.tar.zst || true
  481. }
  482. windows_ish_path() {
  483. [ -n "$1" ] || return 0
  484. [ "$(uname -o 2>/dev/null || true)" = "Msys" ] && cygpath -w "$1" || echo "$1"
  485. }
  486. posix_ish_path() {
  487. [ -n "$1" ] || return 0
  488. [ "$(uname -o 2>/dev/null || true)" = "Msys" ] && cygpath -u "$1" || echo "$1"
  489. }
  490. # Set the Python version we want to use with the QMK CLI
  491. export PYTHON_TARGET_VERSION=${PYTHON_TARGET_VERSION:-3.14}
  492. # Windows/MSYS doesn't like `/tmp` so we need to set a different temporary directory.
  493. # Also set the default `UV_INSTALL_DIR` and `QMK_DISTRIB_DIR` to locations which don't pollute the user's home directory, keeping the installation internal to MSYS.
  494. if [ "$(uname -o 2>/dev/null || true)" = "Msys" ]; then
  495. export TMPDIR="$(posix_ish_path "$TMP")"
  496. export UV_INSTALL_DIR="$(posix_ish_path "${UV_INSTALL_DIR:-/opt/uv}")"
  497. export QMK_DISTRIB_DIR="$(posix_ish_path "${QMK_DISTRIB_DIR:-/opt/qmk}")"
  498. export UV_TOOL_DIR="$(posix_ish_path "${UV_TOOL_DIR:-"$UV_INSTALL_DIR/tools"}")"
  499. export UV_TOOL_BIN_DIR="$(posix_ish_path "$UV_TOOL_DIR/bin")"
  500. fi
  501. script_parse_args "$@"
  502. echo "This QMK CLI installation script will install \`uv\`, the QMK CLI, as well as QMK-supplied toolchains and flashing utilities." >&2
  503. [ -z "${SKIP_PACKAGE_MANAGER:-}" ] || { preinstall_delay || exit 1; }
  504. [ -n "${SKIP_PACKAGE_MANAGER:-}" ] || install_package_manager_deps
  505. [ -n "${SKIP_UV:-}" ] || install_uv
  506. # Make sure the usual `uv` and other associated directories are on the $PATH
  507. setup_paths
  508. # Work out where we want to install the distribution and tools now that `uv` is installed
  509. export QMK_DISTRIB_DIR="$(posix_ish_path "${QMK_DISTRIB_DIR:-$(printf 'import platformdirs\nprint(platformdirs.user_data_dir("qmk"))' | uv_command run --quiet --python $PYTHON_TARGET_VERSION --with platformdirs -)}")"
  510. # Clear out the distrib directory if necessary
  511. if [ -z "${SKIP_CLEAN:-}" ] || [ -z "${SKIP_QMK_TOOLCHAINS:-}" -a -z "${SKIP_QMK_FLASHUTILS:-}" ]; then
  512. if [ -d "$QMK_DISTRIB_DIR" ]; then
  513. echo "Removing old QMK distribution..." >&2
  514. rm -rf "$QMK_DISTRIB_DIR"
  515. fi
  516. fi
  517. mkdir -p "$QMK_DISTRIB_DIR"
  518. [ -n "${SKIP_QMK_CLI:-}" ] || install_qmk_cli
  519. [ -n "${SKIP_QMK_TOOLCHAINS:-}" ] || install_toolchains
  520. [ -n "${SKIP_QMK_FLASHUTILS:-}" ] || install_flashing_tools
  521. if [ "$(uname -s 2>/dev/null || true)" = "Linux" ]; then
  522. [ -n "${SKIP_UDEV_RULES:-}" ] || install_linux_udev_rules
  523. fi
  524. if [ "$(uname -o 2>/dev/null || true)" = "Msys" ]; then
  525. [ -n "${SKIP_WINDOWS_DRIVERS:-}" ] || install_windows_drivers
  526. fi
  527. clean_tarballs
  528. # Notify the user that they may need to restart their shell to get the `qmk` command
  529. echo >&2
  530. echo "QMK CLI installation complete." >&2
  531. echo "The QMK CLI has been installed to '$(posix_ish_path "$(dirname "$(command -v qmk)")")'." >&2
  532. echo "The QMK CLI venv has been created at '$(posix_ish_path "$(uv_command tool dir)/qmk")'." >&2
  533. echo "Toolchains and flashing utilities have been installed to '$QMK_DISTRIB_DIR'." >&2
  534. echo >&2
  535. echo "You may need to restart your shell to gain access to the 'qmk' command." >&2
  536. echo "Alternatively, add "$(posix_ish_path "$(dirname "$(command -v qmk)")")" to your \$PATH:" >&2
  537. echo " export PATH=\"$(posix_ish_path "$(dirname "$(command -v qmk)")"):\$PATH\"" >&2
  538. } # this ensures the entire script is downloaded #