From 8a46b66353598349571f166090f58ec676f1c752 Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Fri, 28 Aug 2026 18:42:29 +0200 Subject: [PATCH 01/10] Extend install-qlt to optionally install CodeQL via qlt codeql run install Adds optional `language`/`codeql-cli-version`/`codeql-standard-library-version`/ `codeql-bundle-version`/`base`/`cache-dir` inputs and `cache-dir`/`cache-primary-key`/ `codeql-bin` outputs. All new steps are gated on `language != ''`, so existing callers that only install QLT itself (passing just `qlt-version`/`add-to-path`) are unaffected. --- .github/actions/install-qlt/action.yml | 112 ++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/.github/actions/install-qlt/action.yml b/.github/actions/install-qlt/action.yml index cb53a5c..8aa76b3 100644 --- a/.github/actions/install-qlt/action.yml +++ b/.github/actions/install-qlt/action.yml @@ -1,6 +1,12 @@ name: Fetch and Install QLT description: | - Fetches and installs QLT. + Fetches and installs QLT. + + Optionally also installs CodeQL and the repo's query packs via `qlt codeql run install`, + restoring a cache directory covering both the downloaded bundle and compiled query + artifacts, when `language` is supplied. Existing callers that only pass `qlt-version`/ + `add-to-path` are unaffected: the original "Install QLT" step and `qlt-home` output are + unchanged, and all the CodeQL-related steps below are skipped unless `language` is set. inputs: qlt-version: description: | @@ -14,11 +20,64 @@ inputs: required: false default: 'true' + language: + description: | + The language pack(s) to install and to scope the cache key to, e.g. `cpp` or `c`. + Space-separate multiple values (e.g. `cpp c`) to install packs for more than one + language in a single call. Leave unset to only install QLT itself (original behavior). + required: false + default: '' + + codeql-cli-version: + description: | + The version of the CodeQL CLI to use, e.g. `2.23.9`. Leave unset to use an already + committed `qlt.conf.json` as-is. Ignored unless `language` is set. + required: false + default: '' + + codeql-standard-library-version: + description: | + The tag or commit to use from the CodeQL Standard Library, e.g. `codeql-cli/v2.23.9`. + Ignored unless `language` is set. + required: false + default: '' + + codeql-bundle-version: + description: | + The CodeQL bundle version to use, e.g. `codeql-bundle-v2.23.9`. Ignored unless + `language` is set. + required: false + default: '' + + base: + description: The base path to find the query repository. Ignored unless `language` is set. + required: false + default: ${{ github.workspace }} + + cache-dir: + description: | + Directory used for the downloaded bundle and compiled query cache. Ignored unless + `language` is set. + required: false + default: ${{ github.workspace }}/.qlt-cache + outputs: qlt-home: description: 'The directory containing the QLT installation' value: ${{ steps.install-qlt.outputs.qlt-home }} + cache-dir: + description: The directory holding the downloaded bundle and compiled query cache. Empty unless `language` is set. + value: ${{ inputs.language != '' && inputs.cache-dir || '' }} + + cache-primary-key: + description: The primary cache key to pass to a cache-save step at the end of the job. Empty unless `language` is set. + value: ${{ steps.restore-qlt-cache.outputs.cache-primary-key }} + + codeql-bin: + description: Path to the `codeql` binary installed by QLT. Empty unless `language` is set. + value: ${{ steps.locate-codeql-bin.outputs.codeql-bin }} + runs: using: composite steps: @@ -80,3 +139,54 @@ runs: popd echo -e "\e[0;32m[QLT]\e[0m Done." + + # Everything below is opt-in: skipped entirely unless `language` is supplied, so existing + # callers that only install QLT itself see no behavior change. + - name: Configure QLT for this CodeQL version + if: inputs.language != '' && inputs.codeql-cli-version != '' + shell: bash + run: | + qlt codeql set version \ + --cli-version "${{ inputs.codeql-cli-version }}" \ + --standard-library-version "${{ inputs.codeql-standard-library-version }}" \ + --bundle-version "${{ inputs.codeql-bundle-version }}" \ + --base "${{ inputs.base }}" \ + --automation-type actions + + - name: Restore QLT cache + if: inputs.language != '' + id: restore-qlt-cache + uses: actions/cache/restore@v6 + with: + path: ${{ inputs.cache-dir }} + key: qlt-${{ runner.os }}-${{ inputs.language }}-${{ hashFiles('qlt.conf.json') }}-${{ github.run_id }} + restore-keys: qlt-${{ runner.os }}-${{ inputs.language }}-${{ hashFiles('qlt.conf.json') }} + + - name: Install CodeQL + packs via QLT + if: inputs.language != '' + shell: bash + run: | + # Installation lives under the `run` subgroup of `codeql` (`codeql install` alone is + # not a valid command). + qlt codeql run install \ + --custom-bundle \ + --packs ${{ inputs.language }} \ + --cache-dir "${{ inputs.cache-dir }}" \ + --base "${{ inputs.base }}" \ + --automation-type actions + + - name: Locate installed codeql binary + if: inputs.language != '' + id: locate-codeql-bin + shell: bash + env: + CACHE_DIR: ${{ inputs.cache-dir }} + run: | + # Unverified layout: QLT installs the CodeQL CLI it downloads/bundles somewhere + # under the cache directory. Fall back to PATH if it can't be found there. + CODEQL_BIN="$(find "$CACHE_DIR" -type f -name codeql -perm -u+x 2>/dev/null | head -n1)" + if [ -z "$CODEQL_BIN" ]; then + CODEQL_BIN="$(command -v codeql)" + fi + echo "::debug::Resolved codeql binary to $CODEQL_BIN" + echo "codeql-bin=$CODEQL_BIN" >> "$GITHUB_OUTPUT" From 2e094e95025801f2515696144f4e93711ba46df5 Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Fri, 28 Aug 2026 18:54:36 +0200 Subject: [PATCH 02/10] Add save-qlt-cache, bundle-codeql-packs, run-qlt-unit-tests actions Companion actions to the extended install-qlt: save-qlt-cache persists its cache-dir/cache-primary-key outputs, bundle-codeql-packs wraps `codeql pack bundle` for a data-driven list of packs, and run-qlt-unit-tests wraps `qlt test run execute-unit-tests`/`validate-unit-tests` for a single language. --- .../actions/bundle-codeql-packs/action.yml | 38 +++++++++++ .github/actions/run-qlt-unit-tests/action.yml | 64 +++++++++++++++++++ .github/actions/save-qlt-cache/action.yml | 22 +++++++ 3 files changed, 124 insertions(+) create mode 100644 .github/actions/bundle-codeql-packs/action.yml create mode 100644 .github/actions/run-qlt-unit-tests/action.yml create mode 100644 .github/actions/save-qlt-cache/action.yml diff --git a/.github/actions/bundle-codeql-packs/action.yml b/.github/actions/bundle-codeql-packs/action.yml new file mode 100644 index 0000000..6dc2c31 --- /dev/null +++ b/.github/actions/bundle-codeql-packs/action.yml @@ -0,0 +1,38 @@ +name: Bundle CodeQL packs +description: | + Bundles a set of CodeQL query packs via `codeql pack bundle`, reusing the cache + directory populated by the `install-qlt` action. This wraps the CLI directly rather than + a `qlt pack` subcommand, since QLT's own pack-publication CLI surface wasn't verified + when this action was written; it can be swapped for a native `qlt pack bundle` call + once that's confirmed. +inputs: + codeql-bin: + description: Path to the `codeql` binary, e.g. the `codeql-bin` output of `install-qlt`. + required: true + + cache-dir: + description: The cache directory to pass as `--common-caches`, e.g. the `cache-dir` output of `install-qlt`. + required: true + + packs: + description: | + One `.tgz ` pair per line, e.g.: + common-cpp-coding-standards.tgz cpp/common/src + common-c-coding-standards.tgz c/common/src + required: true + +runs: + using: composite + steps: + - name: Bundle packs + shell: bash + env: + CODEQL_BIN: ${{ inputs.codeql-bin }} + CACHE_DIR: ${{ inputs.cache-dir }} + PACKS: ${{ inputs.packs }} + run: | + while read -r output pack_path; do + [ -z "$output" ] && continue + echo "::debug::Bundling $pack_path -> $output" + "$CODEQL_BIN" pack bundle --common-caches="$CACHE_DIR" --output="$output" "$pack_path" + done <<< "$PACKS" diff --git a/.github/actions/run-qlt-unit-tests/action.yml b/.github/actions/run-qlt-unit-tests/action.yml new file mode 100644 index 0000000..24457e8 --- /dev/null +++ b/.github/actions/run-qlt-unit-tests/action.yml @@ -0,0 +1,64 @@ +name: Run and validate CodeQL unit tests via QLT +description: | + Runs the repository's unit tests for a given language via `qlt test run + execute-unit-tests`, then validates the results via `qlt test run + validate-unit-tests`, failing the step if any test failed. + + Replaces a hand-rolled `codeql test run` slicing script plus a separate + results-validation job with QLT's own test-running/validation subcommands. + Unverified: whether QLT's runner supports the same degree of parallelism/RAM + tuning as the `--slice`/`--ram` flags used by the script it replaces. +inputs: + language: + description: The language to run tests for, e.g. `cpp` or `c`. + required: true + + num-threads: + description: Number of threads to use for the test runner. + required: false + default: "8" + + work-dir: + description: Where to place intermediate execution output files. + required: false + default: ${{ runner.temp }} + + base: + description: The base path to find the query repository. + required: false + default: ${{ github.workspace }} + + codeql-args: + description: Extra arguments to pass through to CodeQL. + required: false + default: "" + +outputs: + results-directory: + description: The directory containing the raw test execution results. + value: ${{ inputs.work-dir }} + +runs: + using: composite + steps: + - name: Execute unit tests + shell: bash + run: | + qlt test run execute-unit-tests \ + --num-threads "${{ inputs.num-threads }}" \ + --language "${{ inputs.language }}" \ + --runner-os "${{ runner.os }}" \ + --work-dir "${{ inputs.work-dir }}" \ + --base "${{ inputs.base }}" \ + --automation-type actions \ + ${{ inputs.codeql-args != '' && format('--codeql-args "{0}"', inputs.codeql-args) || '' }} + + - name: Validate unit test results + shell: bash + run: | + # Deliberately omit --pretty-print: per QLT's own docs, that mode does not + # exit with a failure code on test failures, which we need for CI gating. + qlt test run validate-unit-tests \ + --results-directory "${{ inputs.work-dir }}" \ + --base "${{ inputs.base }}" \ + --automation-type actions diff --git a/.github/actions/save-qlt-cache/action.yml b/.github/actions/save-qlt-cache/action.yml new file mode 100644 index 0000000..146e1d5 --- /dev/null +++ b/.github/actions/save-qlt-cache/action.yml @@ -0,0 +1,22 @@ +name: Save QLT cache +description: | + Persists the cache directory populated by the `install-qlt` action (downloaded bundle and + compiled query artifacts) so subsequent runs can reuse it. Call this at the end of the job, + typically with `if: always()` so the cache is saved even if the test run failed. +inputs: + cache-dir: + description: The cache directory output by the `install-qlt` action. + required: true + + cache-primary-key: + description: The cache-primary-key output by the `install-qlt` action. + required: true + +runs: + using: composite + steps: + - name: Save QLT cache + uses: actions/cache/save@v6 + with: + path: ${{ inputs.cache-dir }} + key: ${{ inputs.cache-primary-key }} From 65819dfe37f812f83d6c618c25a44f7cd5ed82ca Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Fri, 28 Aug 2026 19:15:57 +0200 Subject: [PATCH 03/10] Add optional precompile step to install-qlt (compilation-cache-size, precompile) Lets callers opt into `codeql query compile --common-caches=\n--compilation-cache-size= [--precompile] ` right after install,\ninstead of hand-rolling that step in every workflow. Gated on both `language`\nand `compilation-cache-size` being set, so existing callers are unaffected. --- .github/actions/install-qlt/action.yml | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/actions/install-qlt/action.yml b/.github/actions/install-qlt/action.yml index 8aa76b3..54b778c 100644 --- a/.github/actions/install-qlt/action.yml +++ b/.github/actions/install-qlt/action.yml @@ -61,6 +61,24 @@ inputs: required: false default: ${{ github.workspace }}/.qlt-cache + compilation-cache-size: + description: | + If set, precompiles `language` right after install via `codeql query compile + --common-caches= --compilation-cache-size=`, so callers don't + each need to hand-roll that step. QLT itself has no equivalent setting (its own + `--cache-dir` is a fingerprinted download cache, not a size-bounded compile cache), so + this action runs the CLI directly. Ignored unless `language` is set. + required: false + default: '' + + precompile: + description: | + Pass `--precompile` to the `codeql query compile` step described by + `compilation-cache-size`. Ignored unless both `language` and `compilation-cache-size` + are set. + required: false + default: 'false' + outputs: qlt-home: description: 'The directory containing the QLT installation' @@ -190,3 +208,21 @@ runs: fi echo "::debug::Resolved codeql binary to $CODEQL_BIN" echo "codeql-bin=$CODEQL_BIN" >> "$GITHUB_OUTPUT" + + - name: Precompile queries + if: inputs.language != '' && inputs.compilation-cache-size != '' + shell: bash + env: + CODEQL_BIN: ${{ steps.locate-codeql-bin.outputs.codeql-bin }} + CACHE_DIR: ${{ inputs.cache-dir }} + CACHE_SIZE: ${{ inputs.compilation-cache-size }} + PRECOMPILE: ${{ inputs.precompile }} + run: | + # No QLT equivalent to --compilation-cache-size exists (its own --cache-dir is a + # fingerprinted download cache, not a size-bounded compile cache), so this shells + # out to the CLI directly rather than a qlt subcommand. + ARGS=(query compile --common-caches="$CACHE_DIR" --compilation-cache-size="$CACHE_SIZE" --threads 0) + if [ "$PRECOMPILE" == "true" ]; then + ARGS+=(--precompile) + fi + "$CODEQL_BIN" "${ARGS[@]}" ${{ inputs.language }} From 77f850b769466e911cc8407459f9955565d12666 Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Fri, 28 Aug 2026 19:22:46 +0200 Subject: [PATCH 04/10] Support plain (non-custom-bundle) CodeQL installs in install-qlt QLT's plain `qlt codeql run install` (no --custom-bundle/--quick-bundle) already\ndownloads the CLI and checks out the standard library at the configured version -\nexactly what workflows testing the standard library directly (rather than this\nrepo's query packs) need. Add a `custom-bundle` input (default 'true', preserving\ncurrent behavior) to opt out of building/installing a custom bundle. Plain installs\naren't affected by --cache-dir (only custom-bundle downloads/compilation are), so\ncache ~/.qlt instead in that case. Also prefer the QLT_CODEQL_PATH env var QLT\nitself exports via GITHUB_ENV (--automation-type actions) over the previous\nfind-based binary lookup, since it works for both install modes. --- .github/actions/install-qlt/action.yml | 61 ++++++++++++++++++-------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/.github/actions/install-qlt/action.yml b/.github/actions/install-qlt/action.yml index 54b778c..99320ec 100644 --- a/.github/actions/install-qlt/action.yml +++ b/.github/actions/install-qlt/action.yml @@ -25,9 +25,24 @@ inputs: The language pack(s) to install and to scope the cache key to, e.g. `cpp` or `c`. Space-separate multiple values (e.g. `cpp c`) to install packs for more than one language in a single call. Leave unset to only install QLT itself (original behavior). + When `custom-bundle` is `false` this only scopes the cache key/precompile target (no + packs are built), so pass whatever language the queries being tested/precompiled are in. required: false default: '' + custom-bundle: + description: | + Whether to build and install a custom bundle containing this repo's own query packs + (`qlt codeql run install --custom-bundle --packs `), or a plain CodeQL CLI + + standard library checkout (`qlt codeql run install`, using the `codeql-cli-version`/ + `codeql-standard-library-version` set above) for testing/using the standard library + directly rather than this repo's packs. Plain installs live under `~/.qlt` rather than + `cache-dir` (QLT's `--cache-dir` only applies to custom-bundle downloads/compilation), + so the cache step below caches `~/.qlt` instead in that case. Ignored unless `language` + is set. + required: false + default: 'true' + codeql-cli-version: description: | The version of the CodeQL CLI to use, e.g. `2.23.9`. Leave unset to use an already @@ -56,8 +71,9 @@ inputs: cache-dir: description: | - Directory used for the downloaded bundle and compiled query cache. Ignored unless - `language` is set. + Directory used for the downloaded bundle and compiled query cache when `custom-bundle` + is `true`. Ignored unless `language` is set (and, for caching purposes, unless + `custom-bundle` is also `true` — see `custom-bundle`). required: false default: ${{ github.workspace }}/.qlt-cache @@ -85,8 +101,10 @@ outputs: value: ${{ steps.install-qlt.outputs.qlt-home }} cache-dir: - description: The directory holding the downloaded bundle and compiled query cache. Empty unless `language` is set. - value: ${{ inputs.language != '' && inputs.cache-dir || '' }} + description: | + The directory holding the install/compile cache (`cache-dir` for custom bundles, `~/.qlt` + for plain installs). Empty unless `language` is set. + value: ${{ inputs.language != '' && (inputs.custom-bundle == 'true' && inputs.cache-dir || '~/.qlt') || '' }} cache-primary-key: description: The primary cache key to pass to a cache-save step at the end of the job. Empty unless `language` is set. @@ -176,22 +194,25 @@ runs: id: restore-qlt-cache uses: actions/cache/restore@v6 with: - path: ${{ inputs.cache-dir }} - key: qlt-${{ runner.os }}-${{ inputs.language }}-${{ hashFiles('qlt.conf.json') }}-${{ github.run_id }} - restore-keys: qlt-${{ runner.os }}-${{ inputs.language }}-${{ hashFiles('qlt.conf.json') }} + # Plain (non-custom-bundle) installs aren't affected by --cache-dir at all (QLT only + # uses it for custom-bundle downloads/compilation), so cache ~/.qlt instead, which is + # where `qlt codeql run install` puts a plain CLI + standard library checkout. + path: ${{ inputs.custom-bundle == 'true' && inputs.cache-dir || '~/.qlt' }} + key: qlt-${{ runner.os }}-${{ inputs.custom-bundle }}-${{ inputs.language }}-${{ hashFiles('qlt.conf.json') }}-${{ github.run_id }} + restore-keys: qlt-${{ runner.os }}-${{ inputs.custom-bundle }}-${{ inputs.language }}-${{ hashFiles('qlt.conf.json') }} - name: Install CodeQL + packs via QLT if: inputs.language != '' shell: bash run: | - # Installation lives under the `run` subgroup of `codeql` (`codeql install` alone is - # not a valid command). - qlt codeql run install \ - --custom-bundle \ - --packs ${{ inputs.language }} \ - --cache-dir "${{ inputs.cache-dir }}" \ - --base "${{ inputs.base }}" \ - --automation-type actions + ARGS=(--cache-dir "${{ inputs.cache-dir }}" --base "${{ inputs.base }}" --automation-type actions) + if [ "${{ inputs.custom-bundle }}" == "true" ]; then + # Build this repo's own query packs into the bundle. + ARGS+=(--custom-bundle --packs ${{ inputs.language }}) + fi + # Otherwise: plain CLI + standard library checkout, from qlt.conf.json's CodeQLCLI / + # CodeQLStandardLibrary (set by "Configure QLT for this CodeQL version" above). + qlt codeql run install "${ARGS[@]}" - name: Locate installed codeql binary if: inputs.language != '' @@ -200,9 +221,13 @@ runs: env: CACHE_DIR: ${{ inputs.cache-dir }} run: | - # Unverified layout: QLT installs the CodeQL CLI it downloads/bundles somewhere - # under the cache directory. Fall back to PATH if it can't be found there. - CODEQL_BIN="$(find "$CACHE_DIR" -type f -name codeql -perm -u+x 2>/dev/null | head -n1)" + # With --automation-type actions, the install step above already exported + # QLT_CODEQL_PATH (and QLT_CODEQL_HOME) to GITHUB_ENV, so it's available here + # directly. Fall back to searching the cache dir / PATH for older QLT versions. + CODEQL_BIN="$QLT_CODEQL_PATH" + if [ -z "$CODEQL_BIN" ]; then + CODEQL_BIN="$(find "$CACHE_DIR" -type f -name codeql -perm -u+x 2>/dev/null | head -n1)" + fi if [ -z "$CODEQL_BIN" ]; then CODEQL_BIN="$(command -v codeql)" fi From 28b9ae145e1f56d88764ed89da40da2cc8314ace Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Fri, 28 Aug 2026 19:31:16 +0200 Subject: [PATCH 05/10] Cache ~/.qlt too, not just cache-dir, and document config-file constraints QLT installs CodeQL to `~/.qlt/repo/{packages,custom-bundle}/` (confirmed via\nInstallationRepository/CodeQLInstallation) for every install mode, but the previous\ncache step only restored/saved `cache-dir`, which the source shows is a *separate*\ncache used only by custom-bundle builds (the codeql-bundle tool's own --cache-dir).\nThat meant the downloaded bundle tarball / CLI binaries were re-downloaded on every\nrun even on a cache hit. Add a step to compute the actual path list (~/.qlt always,\nplus cache-dir when custom-bundle is true) and restore/save that instead.\n\nAlso documented that QLT has no way to point at a different config file - it always\nreads qlt.conf.json from --base, and `qlt codeql set version` overwrites the whole\nfile (drops CodeQLPackConfiguration/CacheDir), which is why callers that need custom\npacks/cache-dir must keep passing --packs/--cache-dir explicitly on the install\ncommand rather than relying on the persisted file after set version runs. --- .github/actions/install-qlt/action.yml | 50 +++++++++++++++++++------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/.github/actions/install-qlt/action.yml b/.github/actions/install-qlt/action.yml index 99320ec..a80e16e 100644 --- a/.github/actions/install-qlt/action.yml +++ b/.github/actions/install-qlt/action.yml @@ -36,10 +36,7 @@ inputs: (`qlt codeql run install --custom-bundle --packs `), or a plain CodeQL CLI + standard library checkout (`qlt codeql run install`, using the `codeql-cli-version`/ `codeql-standard-library-version` set above) for testing/using the standard library - directly rather than this repo's packs. Plain installs live under `~/.qlt` rather than - `cache-dir` (QLT's `--cache-dir` only applies to custom-bundle downloads/compilation), - so the cache step below caches `~/.qlt` instead in that case. Ignored unless `language` - is set. + directly rather than this repo's packs. Ignored unless `language` is set. required: false default: 'true' @@ -47,6 +44,15 @@ inputs: description: | The version of the CodeQL CLI to use, e.g. `2.23.9`. Leave unset to use an already committed `qlt.conf.json` as-is. Ignored unless `language` is set. + + QLT has no option to point at a differently-named/located config file: it always reads + `qlt.conf.json` from `--base`. Setting this input runs `qlt codeql set version`, which + *overwrites* `qlt.conf.json` with only the CLI/standard-library/bundle versions (any + other committed settings, e.g. `ExportedCustomizationPacks`, are dropped from the file, + though `--packs`/`--cache-dir` passed explicitly to the install step below still apply + since those are passed on the command line, not read back from the file). Only needed + when a caller wants to override the committed `qlt.conf.json` per matrix entry, e.g. to + test multiple CLI/standard-library combinations from a single `qlt.conf.json`. required: false default: '' @@ -72,8 +78,10 @@ inputs: cache-dir: description: | Directory used for the downloaded bundle and compiled query cache when `custom-bundle` - is `true`. Ignored unless `language` is set (and, for caching purposes, unless - `custom-bundle` is also `true` — see `custom-bundle`). + is `true` (passed to the `codeql-bundle` tool's own `--cache-dir`). This is separate + from, and in addition to, `~/.qlt` (see the `cache-dir` output) which is where QLT + itself installs CodeQL for every install mode. Ignored unless `language` is set (and, + for caching purposes, unless `custom-bundle` is also `true`). required: false default: ${{ github.workspace }}/.qlt-cache @@ -102,9 +110,12 @@ outputs: cache-dir: description: | - The directory holding the install/compile cache (`cache-dir` for custom bundles, `~/.qlt` - for plain installs). Empty unless `language` is set. - value: ${{ inputs.language != '' && (inputs.custom-bundle == 'true' && inputs.cache-dir || '~/.qlt') || '' }} + Newline-separated list of paths to restore/save for the CodeQL install cache: always + `~/.qlt` (where QLT installs CodeQL for every install mode), plus `cache-dir` too when + `custom-bundle` is `true`. Pass straight through to `save-qlt-cache`'s `cache-dir` input + (which forwards it, unmodified, to `actions/cache/save`'s multiline-capable `path`). + Empty unless `language` is set. + value: ${{ steps.qlt-cache-paths.outputs.paths }} cache-primary-key: description: The primary cache key to pass to a cache-save step at the end of the job. Empty unless `language` is set. @@ -189,15 +200,28 @@ runs: --base "${{ inputs.base }}" \ --automation-type actions + - name: Determine QLT cache paths + if: inputs.language != '' + id: qlt-cache-paths + shell: bash + run: | + # QLT installs CodeQL under ~/.qlt regardless of install mode (packages or custom + # bundles); --cache-dir is a second, separate cache used only by custom-bundle builds. + { + echo "paths<> "$GITHUB_OUTPUT" + - name: Restore QLT cache if: inputs.language != '' id: restore-qlt-cache uses: actions/cache/restore@v6 with: - # Plain (non-custom-bundle) installs aren't affected by --cache-dir at all (QLT only - # uses it for custom-bundle downloads/compilation), so cache ~/.qlt instead, which is - # where `qlt codeql run install` puts a plain CLI + standard library checkout. - path: ${{ inputs.custom-bundle == 'true' && inputs.cache-dir || '~/.qlt' }} + path: ${{ steps.qlt-cache-paths.outputs.paths }} key: qlt-${{ runner.os }}-${{ inputs.custom-bundle }}-${{ inputs.language }}-${{ hashFiles('qlt.conf.json') }}-${{ github.run_id }} restore-keys: qlt-${{ runner.os }}-${{ inputs.custom-bundle }}-${{ inputs.language }}-${{ hashFiles('qlt.conf.json') }} From d147f01143196cfe96b1e93b8a57601d08ec0a9d Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Fri, 28 Aug 2026 19:43:23 +0200 Subject: [PATCH 06/10] Add --verbosity=progress++ to the precompile step so cache hits/misses are logged --- .github/actions/install-qlt/action.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/actions/install-qlt/action.yml b/.github/actions/install-qlt/action.yml index a80e16e..6116279 100644 --- a/.github/actions/install-qlt/action.yml +++ b/.github/actions/install-qlt/action.yml @@ -270,7 +270,8 @@ runs: # No QLT equivalent to --compilation-cache-size exists (its own --cache-dir is a # fingerprinted download cache, not a size-bounded compile cache), so this shells # out to the CLI directly rather than a qlt subcommand. - ARGS=(query compile --common-caches="$CACHE_DIR" --compilation-cache-size="$CACHE_SIZE" --threads 0) + # --verbosity=progress++ surfaces per-query compilation-cache hit/miss messages. + ARGS=(query compile --common-caches="$CACHE_DIR" --compilation-cache-size="$CACHE_SIZE" --threads 0 --verbosity=progress++) if [ "$PRECOMPILE" == "true" ]; then ARGS+=(--precompile) fi From ca97b26d42d21b6fe1c0a931ba3e32320f51cc0b Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Fri, 28 Aug 2026 19:44:42 +0200 Subject: [PATCH 07/10] Split install-qlt's cache-dir output back into a single-dir value plus a new install-cache-paths output The multi-line ~/.qlt + cache-dir list I put in `cache-dir`'s output broke every caller that uses it as a single --common-caches/--cache-dir CLI argument (bundle-codeql-packs, and the coding-standards repo's own --common-caches= in codeql-args). Restore `cache-dir` to a single directory again, and expose the multi-line actions/cache path list under a new `install-cache-paths` output instead, for save-qlt-cache callers to use. --- .github/actions/install-qlt/action.yml | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/actions/install-qlt/action.yml b/.github/actions/install-qlt/action.yml index 6116279..0a9ca5c 100644 --- a/.github/actions/install-qlt/action.yml +++ b/.github/actions/install-qlt/action.yml @@ -78,10 +78,11 @@ inputs: cache-dir: description: | Directory used for the downloaded bundle and compiled query cache when `custom-bundle` - is `true` (passed to the `codeql-bundle` tool's own `--cache-dir`). This is separate - from, and in addition to, `~/.qlt` (see the `cache-dir` output) which is where QLT - itself installs CodeQL for every install mode. Ignored unless `language` is set (and, - for caching purposes, unless `custom-bundle` is also `true`). + is `true` (passed to the `codeql-bundle` tool's own `--cache-dir`, and to `codeql`'s own + `--common-caches` in the precompile step below). This is separate from, and in addition + to, `~/.qlt` (see the `install-cache-paths` output) which is where QLT itself installs + CodeQL for every install mode. Ignored unless `language` is set (and, for caching + purposes, unless `custom-bundle` is also `true`). required: false default: ${{ github.workspace }}/.qlt-cache @@ -110,10 +111,19 @@ outputs: cache-dir: description: | - Newline-separated list of paths to restore/save for the CodeQL install cache: always - `~/.qlt` (where QLT installs CodeQL for every install mode), plus `cache-dir` too when - `custom-bundle` is `true`. Pass straight through to `save-qlt-cache`'s `cache-dir` input - (which forwards it, unmodified, to `actions/cache/save`'s multiline-capable `path`). + The single directory passed as `--cache-dir`/`--common-caches` to `codeql-bundle`/ + `codeql` (e.g. for a `bundle-codeql-packs` or further `codeql query compile` call). + Empty unless `language` is set. + value: ${{ inputs.language != '' && inputs.cache-dir || '' }} + + install-cache-paths: + description: | + Newline-separated list of paths to restore/save with `actions/cache` to cover the whole + CodeQL install: always `~/.qlt` (where QLT installs CodeQL for every install mode), plus + `cache-dir` too when `custom-bundle` is `true`. Pass straight through to + `save-qlt-cache`'s `cache-dir` input (which forwards it, unmodified, to + `actions/cache/save`'s multiline-capable `path`) — do not use this for `--common-caches`/ + `--cache-dir` CLI flags, which expect a single directory (see the `cache-dir` output). Empty unless `language` is set. value: ${{ steps.qlt-cache-paths.outputs.paths }} From b89defc2b824625ecf7a24f50b7231895175bc62 Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Fri, 28 Aug 2026 19:47:14 +0200 Subject: [PATCH 08/10] Default --verbosity=progress++ in run-qlt-unit-tests instead of requiring callers to pass it Adds a `verbosity` input (default 'progress++') so callers get per-test progress logging\nout of the box, matching install-qlt's own precompile step. Callers no longer need to\nremember to append --verbosity to codeql-args themselves. --- .github/actions/run-qlt-unit-tests/action.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/actions/run-qlt-unit-tests/action.yml b/.github/actions/run-qlt-unit-tests/action.yml index 24457e8..c4e2947 100644 --- a/.github/actions/run-qlt-unit-tests/action.yml +++ b/.github/actions/run-qlt-unit-tests/action.yml @@ -28,6 +28,13 @@ inputs: required: false default: ${{ github.workspace }} + verbosity: + description: | + CodeQL `--verbosity` level for the test run, e.g. `progress++` to show per-test + progress. Set to `''` to leave verbosity at CodeQL's own default. + required: false + default: "progress++" + codeql-args: description: Extra arguments to pass through to CodeQL. required: false @@ -44,6 +51,10 @@ runs: - name: Execute unit tests shell: bash run: | + CODEQL_ARGS="${{ inputs.codeql-args }}" + if [ -n "${{ inputs.verbosity }}" ]; then + CODEQL_ARGS="$CODEQL_ARGS --verbosity=${{ inputs.verbosity }}" + fi qlt test run execute-unit-tests \ --num-threads "${{ inputs.num-threads }}" \ --language "${{ inputs.language }}" \ @@ -51,7 +62,7 @@ runs: --work-dir "${{ inputs.work-dir }}" \ --base "${{ inputs.base }}" \ --automation-type actions \ - ${{ inputs.codeql-args != '' && format('--codeql-args "{0}"', inputs.codeql-args) || '' }} + ${{ format('--codeql-args "{0}"', '$CODEQL_ARGS') }} - name: Validate unit test results shell: bash From 1c84028bf89bc5d7e3583d89fa984be341141270 Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Fri, 28 Aug 2026 19:47:35 +0200 Subject: [PATCH 09/10] Simplify run-qlt-unit-tests verbosity/codeql-args composition to plain bash Replace the GH expression format() trick with straightforward bash string\nconcatenation and conditional flag inclusion - easier to read and reason about. --- .github/actions/run-qlt-unit-tests/action.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/actions/run-qlt-unit-tests/action.yml b/.github/actions/run-qlt-unit-tests/action.yml index c4e2947..4e09bf0 100644 --- a/.github/actions/run-qlt-unit-tests/action.yml +++ b/.github/actions/run-qlt-unit-tests/action.yml @@ -50,10 +50,12 @@ runs: steps: - name: Execute unit tests shell: bash + env: + CODEQL_ARGS: ${{ inputs.codeql-args }} + VERBOSITY: ${{ inputs.verbosity }} run: | - CODEQL_ARGS="${{ inputs.codeql-args }}" - if [ -n "${{ inputs.verbosity }}" ]; then - CODEQL_ARGS="$CODEQL_ARGS --verbosity=${{ inputs.verbosity }}" + if [ -n "$VERBOSITY" ]; then + CODEQL_ARGS="$CODEQL_ARGS --verbosity=$VERBOSITY" fi qlt test run execute-unit-tests \ --num-threads "${{ inputs.num-threads }}" \ @@ -62,7 +64,7 @@ runs: --work-dir "${{ inputs.work-dir }}" \ --base "${{ inputs.base }}" \ --automation-type actions \ - ${{ format('--codeql-args "{0}"', '$CODEQL_ARGS') }} + $([ -n "$CODEQL_ARGS" ] && echo --codeql-args "$CODEQL_ARGS") - name: Validate unit test results shell: bash From 1c459f7c48b7766ad28c038e50e9aaad46230b9a Mon Sep 17 00:00:00 2001 From: Mauro Baluda Date: Fri, 28 Aug 2026 19:47:58 +0200 Subject: [PATCH 10/10] Fix codeql-args passthrough: use a bash array instead of unquoted command substitution The previous $([ -n "$CODEQL_ARGS" ] && echo --codeql-args "$CODEQL_ARGS") form word-split the whole codeql-args blob on spaces before qlt ever saw it, instead of passing it as a single --codeql-args value. An array preserves it as one argument. --- .github/actions/run-qlt-unit-tests/action.yml | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/actions/run-qlt-unit-tests/action.yml b/.github/actions/run-qlt-unit-tests/action.yml index 4e09bf0..fa691ce 100644 --- a/.github/actions/run-qlt-unit-tests/action.yml +++ b/.github/actions/run-qlt-unit-tests/action.yml @@ -57,14 +57,18 @@ runs: if [ -n "$VERBOSITY" ]; then CODEQL_ARGS="$CODEQL_ARGS --verbosity=$VERBOSITY" fi - qlt test run execute-unit-tests \ - --num-threads "${{ inputs.num-threads }}" \ - --language "${{ inputs.language }}" \ - --runner-os "${{ runner.os }}" \ - --work-dir "${{ inputs.work-dir }}" \ - --base "${{ inputs.base }}" \ - --automation-type actions \ - $([ -n "$CODEQL_ARGS" ] && echo --codeql-args "$CODEQL_ARGS") + ARGS=( + --num-threads "${{ inputs.num-threads }}" + --language "${{ inputs.language }}" + --runner-os "${{ runner.os }}" + --work-dir "${{ inputs.work-dir }}" + --base "${{ inputs.base }}" + --automation-type actions + ) + if [ -n "$CODEQL_ARGS" ]; then + ARGS+=(--codeql-args "$CODEQL_ARGS") + fi + qlt test run execute-unit-tests "${ARGS[@]}" - name: Validate unit test results shell: bash