From f3f40ed43d7c2723a123f8ccc0920fc7080e6781 Mon Sep 17 00:00:00 2001 From: Rafal Chlodnicki Date: Thu, 15 Oct 2026 21:16:57 +0200 Subject: [PATCH] Install extra packages by name via Package Control A line in `extra-packages` without a `/` is now treated as a package name. It's added to `installed_packages` in Package Control's settings, so Package Control installs the latest compatible release on startup. The install helper waits for these packages before checking libraries and fails with the list of packages it could not install. Also fix a KeyError when reporting missing libraries. --- .github/workflows/test.yaml | 19 ++++++++ README.md | 8 ++++ actions/setup/action.yaml | 21 +++++++++ actions/setup/install_package_control.ps1 | 16 +++++++ actions/setup/install_package_control.sh | 23 ++++++++++ .../setup/install_package_control_helper.py | 46 ++++++++++++++++--- 6 files changed, 126 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f36ed334..44745029 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -80,3 +80,22 @@ jobs: SublimeText/UnitTesting - uses: "./UnitTesting/actions/run-tests" - uses: "./UnitTesting/actions/run-syntax-tests" + + test-package-control-extra-packages: + strategy: + fail-fast: false + matrix: + st-version: [3, 4] + os: ["ubuntu-latest", "macOS-latest", "windows-latest"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v6 + - uses: "./actions/setup" + with: + sublime-text-version: ${{ matrix.st-version }} + extra-packages: | + A File Icon + - name: Check package installed by Package Control + run: test -f "${SUBLIME_TEXT_PACKAGES%/}/../Installed Packages/A File Icon.sublime-package" + shell: bash + - uses: "./actions/run-tests" diff --git a/README.md b/README.md index b11109cc..b33a0610 100644 --- a/README.md +++ b/README.md @@ -336,6 +336,14 @@ jobs: - uses: codecov/codecov-action@v4 ``` +`extra-packages` takes one package per line, in one of these forms: + +- `user/repo@ref` or `Package Name:user/repo@ref` clones the package from GitHub. + `@ref` is a branch, a tag or a tag prefix (e.g. `st3-`). Without it, the latest `x.y.z` tag is used. +- `Package Name` (no `/`) lets Package Control install the package, + picking the latest release compatible with the Sublime Text build. + Requires `install-package-control: true` (the default). + Check [this](https://github.com/randy3k/UnitTesting-example) for further examples. diff --git a/actions/setup/action.yaml b/actions/setup/action.yaml index 08edd100..b41783f9 100644 --- a/actions/setup/action.yaml +++ b/actions/setup/action.yaml @@ -48,6 +48,9 @@ inputs: or just `user/repo@ref` if the repo name is the same as the package name. `@ref` could be either a branch name, a tag name , or a prefix of tags e.g. `st3-` or simply left empty. If an exact match is not found, the latest tag is determined by semver x.y.z. + A line with just a package name (no `/`), e.g. `A File Icon`, installs the package via + Package Control, which picks the latest release compatible with the Sublime Text build. + This requires `install-package-control` to be enabled. required: false display: description: internal and experimental only @@ -214,6 +217,7 @@ runs: fi # Install extra packages to to Packages/ + PC_PACKAGES="" while read -r x; do if [[ "$x" =~ ^(.*):(.*)/([^@]*)@?(.*)$ ]]; then PKG="${BASH_REMATCH[1]}" @@ -225,12 +229,29 @@ runs: USER="${BASH_REMATCH[1]}" REPO="${BASH_REMATCH[2]}" SHA="${BASH_REMATCH[3]}" + elif [ -n "$x" ]; then + # Plain package name, installed by Package Control + PC_PACKAGES+="$x"$'\n' + continue else continue fi InstallPackage "$PKG" "https://github.com/$USER/$REPO" "$SHA" done <<< "${{ inputs.extra-packages }}" + # Pass packages to be installed by Package Control to the next step + if [ -n "$PC_PACKAGES" ]; then + if [ "${{ inputs.install-package-control }}" != "true" ]; then + echo "::error::extra-packages lists packages to install via Package Control, but install-package-control is disabled." + exit 1 + fi + { + echo "UNITTESTING_PC_PACKAGES<> "$GITHUB_ENV" + fi + # Install UnitTesting to Packages/UnitTesting if not yet exists if [ ! -d "$SUBLIME_TEXT_PACKAGES/UnitTesting" ]; then echo "Copy action's 'UnitTesting' to $SUBLIME_TEXT_PACKAGES/UnitTesting" diff --git a/actions/setup/install_package_control.ps1 b/actions/setup/install_package_control.ps1 index affed857..e7d8ef23 100644 --- a/actions/setup/install_package_control.ps1 +++ b/actions/setup/install_package_control.ps1 @@ -26,6 +26,22 @@ if (-not (Test-Path $PC_SETTINGS)) { "{`"auto_upgrade`": false, `"ignore_vcs_packages`": true, `"remove_orphaned`": false, `"submit_usage`": false }" | Out-File -filepath $PC_SETTINGS -encoding utf8 } +# let Package Control install packages listed in `installed_packages` on startup +if ($env:UNITTESTING_PC_PACKAGES) { + Write-Verbose "Adding packages to be installed by Package Control:" + $settings = Get-Content -Raw $PC_SETTINGS | ConvertFrom-Json + $names = @($settings.installed_packages | Where-Object { $_ }) + foreach ($name in $env:UNITTESTING_PC_PACKAGES -split "`r?`n") { + $name = $name.Trim() + if ($name -and $names -notcontains $name) { + Write-Verbose " $name" + $names += $name + } + } + $settings | Add-Member -NotePropertyName installed_packages -NotePropertyValue $names -Force + $settings | ConvertTo-Json | Out-File -filepath $PC_SETTINGS -encoding utf8 +} + $PCH_PATH = "$STP\0_install_package_control_helper" New-Item -itemtype directory $PCH_PATH -force >$null diff --git a/actions/setup/install_package_control.sh b/actions/setup/install_package_control.sh index 7144a2ab..d3965451 100644 --- a/actions/setup/install_package_control.sh +++ b/actions/setup/install_package_control.sh @@ -55,6 +55,29 @@ if [ ! -f "$STP/User/Package Control.sublime-settings" ]; then echo '{"auto_upgrade": false, "ignore_vcs_packages": true, "remove_orphaned": false, "submit_usage": false }' > "$STP/User/Package Control.sublime-settings" fi +# let Package Control install packages listed in `installed_packages` on startup +if [ -n "$UNITTESTING_PC_PACKAGES" ]; then + echo Adding packages to be installed by Package Control: + printf '%s' "$UNITTESTING_PC_PACKAGES" | sed 's/^/ /' + python3 -B - "$STP/User/Package Control.sublime-settings" <<'EOF' +import json +import os +import sys + +path = sys.argv[1] +with open(path, encoding="utf-8") as f: + settings = json.load(f) +names = settings.get("installed_packages", []) +for name in os.environ["UNITTESTING_PC_PACKAGES"].splitlines(): + name = name.strip() + if name and name not in names: + names.append(name) +settings["installed_packages"] = names +with open(path, "w", encoding="utf-8") as f: + json.dump(settings, f, indent=4) +EOF +fi + PCH_PATH="$STP/0_install_package_control_helper" if [ ! -d "$PCH_PATH" ]; then diff --git a/actions/setup/install_package_control_helper.py b/actions/setup/install_package_control_helper.py index b22b714e..f996409e 100644 --- a/actions/setup/install_package_control_helper.py +++ b/actions/setup/install_package_control_helper.py @@ -44,12 +44,48 @@ def check_package_control(): Wait for Package Control to be loaded. """ if "Package Control" in sys.modules: - sublime.set_timeout(check_libraries, 2000) + sublime.set_timeout(check_packages, 2000) else: sublime.set_timeout(check_package_control, 2000) num_retries = 0 + def retry(callback): + """ + Schedule another check, return False if out of retries. + """ + nonlocal num_retries + + if num_retries < 20: + num_retries += 1 + sublime.set_timeout(callback, 2000) + return True + + return False + + def check_packages(): + """ + Wait for Package Control to install missing packages. + + PC4 automatically installs packages listed in `installed_packages` + at startup, before installing missing libraries. + """ + package_control = sys.modules["Package Control"].package_control + manager = package_control.package_manager.PackageManager() + settings = sublime.load_settings("Package Control.sublime-settings") + missing_packages = set(settings.get("installed_packages", [])) - manager.list_packages() + if not missing_packages: + check_libraries() + return + + if retry(check_packages): + return + + log.write("missing packages:" + "\n") + log.write("\n".join("- " + name for name in sorted(missing_packages)) + "\n") + touch("failed") + kill_subl(restart=False) + def check_libraries(): """ Wait for Package Control to finish bootstrapping. @@ -61,17 +97,13 @@ def check_libraries(): manager = package_control.package_manager.PackageManager() missing_libraries = manager.find_missing_libraries() if missing_libraries: - nonlocal num_retries - - if num_retries < 10: - num_retries += 1 - sublime.set_timeout(check_libraries, 2000) + if retry(check_libraries): return log.write("missing libraries:" + "\n") log.write( "\n".join( - "- {lib.name} for Python {lib.python_version}".format(lib) + "- {lib.name} for Python {lib.python_version}".format(lib=lib) for lib in sorted(missing_libraries) ) + "\n"