qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

env-bootstrap.sh (26454B)


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