From f3e0c870be80dbd6efb827e32fa3bec0e935b4d1 Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Wed, 16 Sep 2026 12:16:13 +0100 Subject: [PATCH] Clarify resolved bundle handling Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- lib/entry-points.js | 27 +++++++++++++++---------- src/setup-codeql.test.ts | 39 +++++++++++++++++++++++++++++++++--- src/setup-codeql.ts | 43 ++++++++++++++++++++++++++++++---------- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 4afc64403..35c18d8af 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -151967,7 +151967,7 @@ async function getCodeQLBundleDownloadURL(tagName, apiDetails, compressionMethod return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${codeQLBundleName}`; } function tryGetBundleVersionFromTagName(tagName, logger) { - const match2 = tagName.match(/^codeql-bundle-(.*)$/); + const match2 = tagName.match(/^codeql-bundle-(.+)$/); if (match2 === null || match2.length < 2) { logger.debug(`Could not determine bundle version from tag ${tagName}.`); return void 0; @@ -152207,7 +152207,7 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO url2 = toolsInput; if (tagName) { const bundleVersion3 = tryGetBundleVersionFromTagName(tagName, logger); - if (bundleVersion3 && semver9.valid(bundleVersion3)) { + if (bundleVersion3 !== void 0 && semver9.valid(bundleVersion3)) { cliVersion2 = convertToSemVer(bundleVersion3, logger); } } @@ -152222,8 +152222,8 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO cliVersion2 = version.cliVersion; tagName = version.tagName; } - const bundleVersion2 = tagName && tryGetBundleVersionFromTagName(tagName, logger); - const resolvedVersion = cliVersion2 ?? (bundleVersion2 ? convertToSemVer(bundleVersion2, logger) : void 0); + const bundleVersion2 = tagName !== void 0 ? tryGetBundleVersionFromTagName(tagName, logger) : void 0; + const resolvedVersion = cliVersion2 ?? (bundleVersion2 !== void 0 ? convertToSemVer(bundleVersion2, logger) : void 0); const humanReadableVersion = resolvedVersion ?? tagName ?? url2 ?? "unknown"; logger.debug( `Attempting to obtain CodeQL tools. CLI version: ${cliVersion2 ?? "unknown"}, bundle tag name: ${tagName ?? "unknown"}, URL: ${url2 ?? "unspecified"}.` @@ -152341,7 +152341,7 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO } async function tryGetFallbackToolcacheVersion(cliVersion2, tagName, logger) { const bundleVersion2 = tryGetBundleVersionFromTagName(tagName, logger); - if (!bundleVersion2) { + if (bundleVersion2 === void 0) { return void 0; } const fallbackVersion = convertToSemVer(bundleVersion2, logger); @@ -152473,12 +152473,11 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau break; case "download": { const result = await downloadCodeQLBundle( + { env: getEnv(), features, logger }, source, apiDetails, zstdAvailability.version, - tempDir, - features, - logger + tempDir ); codeqlFolder = result.codeqlFolder; toolsDownloadStatusReport = result.statusReport; @@ -152496,9 +152495,15 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau toolsVersion: source.toolsVersion }; } -async function downloadCodeQLBundle(source, apiDetails, tarVersion, tempDir, features, logger) { - await tryDeleteToolcacheBundles({ env: getEnv(), features, logger }); - return await downloadCodeQL(source, apiDetails, tarVersion, tempDir, logger); +async function downloadCodeQLBundle(action, source, apiDetails, tarVersion, tempDir) { + await tryDeleteToolcacheBundles(action); + return await downloadCodeQL( + source, + apiDetails, + tarVersion, + tempDir, + action.logger + ); } async function useZstdBundle(cliVersion2, tarSupportsZstd) { return ( diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index f4dbc9d80..973beef5e 100644 --- a/src/setup-codeql.test.ts +++ b/src/setup-codeql.test.ts @@ -10,7 +10,7 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import * as api from "./api-client"; import * as diagnostics from "./diagnostics"; -import { ActionsEnvVars, EnvVar, ReadOnlyEnv } from "./environment"; +import { ActionsEnvVars, EnvVar, getEnv, ReadOnlyEnv } from "./environment"; import { Feature } from "./feature-flags"; import { getRunnerLogger } from "./logging"; import { getCacheRestoreKeyPrefix } from "./overlay/caching"; @@ -25,6 +25,7 @@ import { createFeatures, createTestConfig, getRecordingLogger, + getTestEnv, makeMacro, mockBundleDownloadApi, setupActionsVars, @@ -537,6 +538,7 @@ for (const bundlePath of [ t.true(extractStub.calledOnce); t.is(extractStub.firstCall.args[0], url); + t.is(downloadSpy.firstCall.args[0].bundleVersion, undefined); t.is(downloadSpy.firstCall.args[0].toolsVersion, "unknown"); t.is(result.toolsVersion, "unknown"); t.is(result.toolsSource, setupCodeql.ToolsSource.Download); @@ -1094,6 +1096,7 @@ async function runDownloadCodeQL( toolcacheRoot: string, features: Feature[], bundleVersion: string | undefined, + env: ReadOnlyEnv = getEnv(), ): Promise<{ codeqlFolder: string; cleanupDiagnostic: toolsDownload.ToolcacheCleanupResult | undefined; @@ -1102,6 +1105,11 @@ async function runDownloadCodeQL( const addDiagnostic = sinon.stub(diagnostics, "addNoLanguageDiagnostic"); const { codeqlFolder } = await setupCodeql.downloadCodeQLBundle( + { + env, + features: createFeatures(features), + logger: getRunnerLogger(true), + }, { bundle: { kind: "combined", @@ -1116,8 +1124,6 @@ async function runDownloadCodeQL( SAMPLE_DOTCOM_API_DETAILS, undefined, // tarVersion toolcacheRoot, // tempDir - createFeatures(features), - getRunnerLogger(true), ); const diagnostic = addDiagnostic @@ -1484,6 +1490,33 @@ test.serial( }, ); +test.serial( + "downloadCodeQLBundle checks the supplied environment before cleaning the toolcache", + async (t) => { + await withTmpDir(async (tmpDir) => { + setupActionsVars(tmpDir, tmpDir); + process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted"; + delete process.env[EnvVar.HAS_SET_UP_CODEQL]; + + const staleDirectory = createToolcacheEntry( + tmpDir, + "CodeQL", + CLEANUP_STALE_VERSION, + ); + const { codeqlFolder, cleanupDiagnostic } = await runDownloadCodeQL( + tmpDir, + [Feature.CleanupToolcacheBundles], + CLEANUP_BUNDLE_VERSION, + getTestEnv({ [EnvVar.HAS_SET_UP_CODEQL]: "true" }), + ); + + t.true(fs.existsSync(staleDirectory)); + t.true(fs.existsSync(`${codeqlFolder}.complete`)); + t.is(cleanupDiagnostic, undefined); + }); + }, +); + test.serial( "setupCodeQLBundle records that this job has set up CodeQL", async (t) => { diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index 46b9baf94..e5d6a77a9 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -164,7 +164,7 @@ function tryGetBundleVersionFromTagName( tagName: string, logger: Logger, ): string | undefined { - const match = tagName.match(/^codeql-bundle-(.*)$/); + const match = tagName.match(/^codeql-bundle-(.+)$/); if (match === null || match.length < 2) { logger.debug(`Could not determine bundle version from tag ${tagName}.`); return undefined; @@ -215,12 +215,16 @@ export function convertToSemVer(version: string, logger: Logger): string { return s; } +/** Describes the contents and location of a downloadable CodeQL bundle. */ type CodeQLBundle = { kind: "combined"; url: string }; /** A resolved download, including its bundle identity and version. */ export interface CodeQLDownloadSource { + /** Distinguishes downloads from local archives and cached installations. */ sourceType: "download"; + /** The bundle to download. */ bundle: CodeQLBundle; + /** The compression format of the bundle archive. */ compressionMethod: tar.CompressionMethod; /** Bundle version of the tools, if known. */ bundleVersion?: string; @@ -588,7 +592,7 @@ export async function getCodeQLSource( if (tagName) { const bundleVersion = tryGetBundleVersionFromTagName(tagName, logger); // If the bundle version is a semantic version, it is a CLI version number. - if (bundleVersion && semver.valid(bundleVersion)) { + if (bundleVersion !== undefined && semver.valid(bundleVersion)) { cliVersion = convertToSemVer(bundleVersion, logger); } } @@ -605,10 +609,14 @@ export async function getCodeQLSource( } const bundleVersion = - tagName && tryGetBundleVersionFromTagName(tagName, logger); + tagName !== undefined + ? tryGetBundleVersionFromTagName(tagName, logger) + : undefined; const resolvedVersion = cliVersion ?? - (bundleVersion ? convertToSemVer(bundleVersion, logger) : undefined); + (bundleVersion !== undefined + ? convertToSemVer(bundleVersion, logger) + : undefined); const humanReadableVersion = resolvedVersion ?? tagName ?? url ?? "unknown"; logger.debug( @@ -772,7 +780,7 @@ async function tryGetFallbackToolcacheVersion( logger: Logger, ): Promise { const bundleVersion = tryGetBundleVersionFromTagName(tagName, logger); - if (!bundleVersion) { + if (bundleVersion === undefined) { return undefined; } const fallbackVersion = convertToSemVer(bundleVersion, logger); @@ -844,6 +852,10 @@ export const downloadCodeQL = async function ( }; }; +/** + * Returns the canonical toolcache directory for a resolved download, or `undefined` if its bundle + * version is unknown. + */ function getToolcacheDestination( source: CodeQLDownloadSource, logger: Logger, @@ -1004,12 +1016,11 @@ export async function setupCodeQLBundle( break; case "download": { const result = await downloadCodeQLBundle( + { env: getEnv(), features, logger }, source, apiDetails, zstdAvailability.version, tempDir, - features, - logger, ); codeqlFolder = result.codeqlFolder; toolsDownloadStatusReport = result.statusReport; @@ -1032,19 +1043,29 @@ export async function setupCodeQLBundle( }; } +/** + * Performs eligible toolcache cleanup once, then downloads and extracts the resolved bundle. + * + * @returns The extraction directory and download timings. + */ export async function downloadCodeQLBundle( + action: ActionState<["Logger", "ReadOnlyEnv", "FeatureFlags"]>, source: CodeQLDownloadSource, apiDetails: api.GitHubApiDetails, tarVersion: tar.TarVersion | undefined, tempDir: string, - features: FeatureEnablement, - logger: Logger, ): Promise<{ codeqlFolder: string; statusReport: ToolsDownloadStatusReport; }> { - await tryDeleteToolcacheBundles({ env: getEnv(), features, logger }); - return await downloadCodeQL(source, apiDetails, tarVersion, tempDir, logger); + await tryDeleteToolcacheBundles(action); + return await downloadCodeQL( + source, + apiDetails, + tarVersion, + tempDir, + action.logger, + ); } async function useZstdBundle(