Преглед изворни кода

Bootstrap: environmental sanitisation, package install robustness (#26433)

* Sanitise more.

* Let's brute-force Gentoo prep.

* Quiet, Gentoo.

* Intel macOS, `brew` unsupported.

* Intel macOS, `brew` unsupported.

* Throttle.

* Apparently there's a free `macos-26-intel` runner.

* More safety.

* More safety.
Nick Brassel пре 2 недеља
родитељ
комит
3c73e928ab
2 измењених фајлова са 103 додато и 21 уклоњено
  1. 22 9
      .github/workflows/bootstrap_testing.yml
  2. 81 12
      util/env-bootstrap.sh

+ 22 - 9
.github/workflows/bootstrap_testing.yml

@@ -83,32 +83,34 @@ jobs:
       - name: Install base dependencies
         run: |
           # Attempt to run the package installation up to 10 times to mitigate transient network issues
+          ok=''
           for n in $(seq 1 10); do
             {
               echo "Attempt #$n of 10 to install base dependencies:"
               case "${{ matrix.distribution }}" in
                 *ubuntu*|*debian*)
-                  apt-get update
-                  apt-get install -y sudo git passwd
+                  apt-get update &&
+                    apt-get install -y sudo git passwd
                   ;;
                 *fedora*|*rockylinux*|*almalinux*)
                   dnf install -y sudo git passwd findutils  # findutils=xargs
                   ;;
                 *suse*)
-                  zypper --non-interactive refresh
-                  zypper --non-interactive install sudo git shadow findutils tar  # findutils=xargs
+                  zypper --non-interactive refresh &&
+                    zypper --non-interactive install sudo git shadow findutils tar  # findutils=xargs
                   ;;
                 *gentoo*)
-                  emaint sync
-                  emerge --noreplace --ask=n sudo dev-vcs/git shadow findutils  # findutils=xargs
+                  { emaint sync -a || emerge --sync --quiet || emerge-webrsync -q ; } &&
+                    emerge --noreplace --ask=n sudo dev-vcs/git shadow findutils  # findutils=xargs
                   ;;
                 *archlinux*|*cachyos*|*manjaro*)
-                  pacman -Syu --noconfirm
-                  pacman -S --noconfirm sudo git
+                  pacman -Syu --noconfirm &&
+                    pacman -S --noconfirm sudo git
                   ;;
               esac
-            } && break || sleep 10
+            } && { ok=1; break ; } || sleep 10
           done
+          [ -n "$ok" ] || exit 1
 
           # Fix PAM configuration for sudo in containers
           # Fix /etc/shadow permissions - common issue in container environments
@@ -196,10 +198,21 @@ jobs:
           - macos-15        # Apple Silicon ARM64
           - macos-15-intel  # Intel x64
           - macos-26        # Apple Silicon ARM64
+          - macos-26-intel  # Intel x64
 
     runs-on: ${{ matrix.os }}
 
     steps:
+      - name: Install base dependencies
+        run: |
+          # Attempt to run the brew update up to 10 times to mitigate transient issues
+          for n in $(seq 1 10); do
+            brew update && exit 0
+            brew update-reset || true
+            sleep 10
+          done
+          exit 1
+
       - name: Checkout repository
         uses: actions/checkout@v7
         with:

+ 81 - 12
util/env-bootstrap.sh

@@ -43,6 +43,49 @@
     # corrupt captured output and break pattern matching throughout this script.
     unset GREP_OPTIONS GREP_COLOR GREP_COLORS
 
+    # Force the C locale so `tr`/`grep`/`sed`/`sort` behave the same on every
+    # system, and clear other variables which alter tool behavior.
+    export LC_ALL=C
+    unset CDPATH POSIXLY_CORRECT TAR_OPTIONS
+
+    # Drop out of any inherited Python venv; `deactivate` doesn't exist in this
+    # process, so strip its $PATH entries and marker variables by hand.
+    if [ -n "${VIRTUAL_ENV:-}" ]; then
+        clean_path=''
+        old_ifs="${IFS}"
+        IFS=':'
+        for path_entry in $PATH; do
+            case "$path_entry" in
+                "$VIRTUAL_ENV"/bin | "$VIRTUAL_ENV"/Scripts) ;;
+                *) clean_path="${clean_path:+${clean_path}:}${path_entry}" ;;
+            esac
+        done
+        IFS="${old_ifs}"
+        PATH="${clean_path}"
+        export PATH
+        unset VIRTUAL_ENV VIRTUAL_ENV_PROMPT clean_path old_ifs path_entry
+    fi
+
+    # Wipe all PYTHON* variables (keeping PYTHON_TARGET_VERSION, which this
+    # script uses) so the user's Python settings can't leak into the
+    # interpreters and builds managed by `uv`.
+    saved_python_target_version="${PYTHON_TARGET_VERSION:-}"
+    for env_var_name in $(env | LC_ALL=C sed -n 's/^\(PYTHON[A-Za-z0-9_]*\)=.*/\1/p'); do
+        unset "$env_var_name" 2>/dev/null || true
+    done
+    [ -z "$saved_python_target_version" ] || export PYTHON_TARGET_VERSION="$saved_python_target_version"
+    unset saved_python_target_version env_var_name
+    export PYTHONNOUSERSITE=1
+
+    # Clear conda/pip/uv overrides which would affect dependency resolution or
+    # builds; proxy, TLS, and index/mirror variables stay for corporate networks.
+    unset CONDA_PREFIX CONDA_DEFAULT_ENV
+    unset PIP_REQUIRE_VIRTUALENV PIP_TARGET PIP_PREFIX PIP_USER
+    unset SETUPTOOLS_USE_DISTUTILS
+    unset UV_NO_BUILD_ISOLATION UV_OFFLINE UV_NO_INDEX \
+          UV_CONSTRAINT UV_BUILD_CONSTRAINT UV_OVERRIDE \
+          UV_SYSTEM_PYTHON UV_NO_MANAGED_PYTHON
+
     BOOTSTRAP_TMPDIR="$(mktemp -d /tmp/qmk-bootstrap-failure.XXXXXX)"
     trap 'rm -rf "$BOOTSTRAP_TMPDIR" >/dev/null 2>&1 || true' EXIT
     FAILURE_FILE="${BOOTSTRAP_TMPDIR}/fail"
@@ -175,6 +218,15 @@ __EOT__
         fi
     }
 
+    check_release_tag() {
+        # An empty tag means the GitHub API call failed, usually from rate limiting.
+        if [ -z "$2" ]; then
+            echo "Could not determine the latest $1 release." >&2
+            echo "If GitHub API rate limits are the cause, set GITHUB_TOKEN to raise them." >&2
+            exit 1
+        fi
+    }
+
     fn_os() {
         local os_name=$(echo ${1:-} | tr 'A-Z' 'a-z')
         if [ -z "$os_name" ]; then
@@ -285,6 +337,10 @@ __EOT__
     print_package_manager_deps_and_delay() {
         get_package_manager_deps | tr ' ' '\n' | sort | xargs -I'{}' echo "    - {}" >&2
         exit_if_execution_failed
+        if [ -n "${1:-}" ]; then
+            echo >&2
+            echo "$1" >&2
+        fi
         preinstall_delay || exit 1
     }
 
@@ -294,7 +350,11 @@ __EOT__
         macos)
             if [ -n "$(command -v brew 2>/dev/null || true)" ]; then
                 echo "It will also install the following system packages using 'brew':" >&2
-                print_package_manager_deps_and_delay
+                local intel_note=""
+                if [ "$(fn_arch)" = "X64" ]; then
+                    intel_note="NOTE: Homebrew no longer provides pre-built packages for Intel Macs, so some of the above may be built from source. This can take a long time."
+                fi
+                print_package_manager_deps_and_delay "$intel_note"
 
                 brew update
 
@@ -308,12 +368,14 @@ __EOT__
                 fi
                 done
 
-                if [ -n "${existing:-}" ]; then
-                    brew upgrade $existing
-                fi
-                if [ -n "${new:-}" ]; then
-                    brew install $new
-                fi
+                # Homebrew no longer builds Intel macOS bottles (tier 3); when a
+                # bottle is missing, retry the formula as a source build.
+                for dep in ${existing:-}; do
+                    brew upgrade "$dep" || brew upgrade --build-from-source "$dep"
+                done
+                for dep in ${new:-}; do
+                    brew install "$dep" || brew install --build-from-source "$dep"
+                done
             else
                 echo "Please install 'brew' to continue. See https://brew.sh/ for more information." >&2
                 exit 1
@@ -437,6 +499,7 @@ __EOT__
     install_toolchains() {
         # Get the latest toolchain release from https://github.com/qmk/qmk_toolchains
         local latest_toolchains_release=$(github_api_call repos/qmk/qmk_toolchains/releases/latest - | grep -oE '"tag_name": "[^"]+' | grep -oE '[^"]+$')
+        check_release_tag qmk_toolchains "$latest_toolchains_release"
         # Download the specific release asset with a matching keyword
         local toolchain_url=$(github_api_call repos/qmk/qmk_toolchains/releases/tags/$latest_toolchains_release - | grep -oE '"browser_download_url": "[^"]+"' | grep -oE 'https://[^"]+' | grep -E "qmk_toolchains-.*$(fn_os)$(fn_arch)")
         if [ -z "$toolchain_url" ]; then
@@ -464,6 +527,7 @@ __EOT__
 
         # Get the latest flashing tools release from https://github.com/qmk/qmk_flashutils
         local latest_flashutils_release=$(github_api_call repos/qmk/qmk_flashutils/releases/latest - | grep -oE '"tag_name": "[^"]+' | grep -oE '[^"]+$')
+        check_release_tag qmk_flashutils "$latest_flashutils_release"
         # Download the specific release asset with a matching keyword
         local flashutils_url=$(github_api_call repos/qmk/qmk_flashutils/releases/tags/$latest_flashutils_release - | grep -oE '"browser_download_url": "[^"]+"' | grep -oE 'https://[^"]+' | grep -E "qmk_flashutils-.*$osarchvariant")
         if [ -z "$flashutils_url" ]; then
@@ -486,10 +550,7 @@ __EOT__
     install_linux_udev_rules() {
         # Get the latest qmk_udev release
         local latest_udev_release=$(github_api_call repos/qmk/qmk_udev/releases/latest - | grep -oE '"tag_name": "[^"]+' | grep -oE '[^"]+$')
-        if [ -z "$latest_udev_release" ]; then
-            echo "Could not determine latest qmk_udev release." >&2
-            exit 1
-        fi
+        check_release_tag qmk_udev "$latest_udev_release"
         echo "Using qmk_udev release: $latest_udev_release" >&2
 
         # Download the udev rules file
@@ -541,6 +602,7 @@ __EOT__
     install_windows_drivers() {
         # Get the latest driver installer release from https://github.com/qmk/qmk_driver_installer
         local latest_driver_installer_release=$(github_api_call repos/qmk/qmk_driver_installer/releases/latest - | grep -oE '"tag_name": "[^"]+' | grep -oE '[^"]+$')
+        check_release_tag qmk_driver_installer "$latest_driver_installer_release"
         # Download the specific release asset
         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')
         if [ -z "$driver_installer_url" ]; then
@@ -600,7 +662,14 @@ __EOT__
     setup_paths
 
     # Work out where we want to install the distribution and tools now that `uv` is installed
-    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 -)}")"
+    export QMK_DISTRIB_DIR="$(posix_ish_path "${QMK_DISTRIB_DIR:-$(printf 'import platformdirs\nprint(platformdirs.user_data_dir("qmk"))' | uv_command run --quiet --no-project --python $PYTHON_TARGET_VERSION --with platformdirs -)}")"
+
+    # `export` masks any failure of the `uv` invocation above, so bail out here
+    # rather than continue with an empty directory.
+    if [ -z "$QMK_DISTRIB_DIR" ]; then
+        echo "Could not determine the QMK distribution directory." >&2
+        exit 1
+    fi
 
     # Clear out the distrib directory if necessary
     if [ -z "${SKIP_CLEAN:-}" ] || [ -z "${SKIP_QMK_TOOLCHAINS:-}" -a -z "${SKIP_QMK_FLASHUTILS:-}" ]; then