Merge pull request #4145 from github/henrymercer/bundle-resolution-pr

Separate CodeQL bundle resolution from installation
This commit is contained in:
Henry Mercer
2026-09-17 14:31:25 +00:00
committed by GitHub
4 changed files with 455 additions and 189 deletions

91
lib/entry-points.js generated
View File

@@ -151967,7 +151967,7 @@ async function getCodeQLBundleDownloadURL(tagName, apiDetails, compressionMethod
return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${codeQLBundleName}`; return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${codeQLBundleName}`;
} }
function tryGetBundleVersionFromTagName(tagName, logger) { function tryGetBundleVersionFromTagName(tagName, logger) {
const match2 = tagName.match(/^codeql-bundle-(.*)$/); const match2 = tagName.match(/^codeql-bundle-(.+)$/);
if (match2 === null || match2.length < 2) { if (match2 === null || match2.length < 2) {
logger.debug(`Could not determine bundle version from tag ${tagName}.`); logger.debug(`Could not determine bundle version from tag ${tagName}.`);
return void 0; return void 0;
@@ -152207,7 +152207,7 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
url2 = toolsInput; url2 = toolsInput;
if (tagName) { if (tagName) {
const bundleVersion3 = tryGetBundleVersionFromTagName(tagName, logger); const bundleVersion3 = tryGetBundleVersionFromTagName(tagName, logger);
if (bundleVersion3 && semver9.valid(bundleVersion3)) { if (bundleVersion3 !== void 0 && semver9.valid(bundleVersion3)) {
cliVersion2 = convertToSemVer(bundleVersion3, logger); cliVersion2 = convertToSemVer(bundleVersion3, logger);
} }
} }
@@ -152222,8 +152222,9 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
cliVersion2 = version.cliVersion; cliVersion2 = version.cliVersion;
tagName = version.tagName; tagName = version.tagName;
} }
const bundleVersion2 = tagName && tryGetBundleVersionFromTagName(tagName, logger); const bundleVersion2 = tagName !== void 0 ? tryGetBundleVersionFromTagName(tagName, logger) : void 0;
const humanReadableVersion = cliVersion2 ?? (bundleVersion2 && convertToSemVer(bundleVersion2, logger)) ?? tagName ?? url2 ?? "unknown"; const resolvedVersion = cliVersion2 ?? (bundleVersion2 !== void 0 ? convertToSemVer(bundleVersion2, logger) : void 0);
const humanReadableVersion = resolvedVersion ?? tagName ?? url2 ?? "unknown";
logger.debug( logger.debug(
`Attempting to obtain CodeQL tools. CLI version: ${cliVersion2 ?? "unknown"}, bundle tag name: ${tagName ?? "unknown"}, URL: ${url2 ?? "unspecified"}.` `Attempting to obtain CodeQL tools. CLI version: ${cliVersion2 ?? "unknown"}, bundle tag name: ${tagName ?? "unknown"}, URL: ${url2 ?? "unspecified"}.`
); );
@@ -152330,17 +152331,17 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
logger.info(`Using CodeQL CLI sourced from ${url2} .`); logger.info(`Using CodeQL CLI sourced from ${url2} .`);
} }
return { return {
bundleVersion: tagName && tryGetBundleVersionFromTagName(tagName, logger), bundle: { kind: "combined", url: url2 },
bundleVersion: bundleVersion2,
cliVersion: cliVersion2, cliVersion: cliVersion2,
codeqlURL: url2,
compressionMethod, compressionMethod,
sourceType: "download", sourceType: "download",
toolsVersion: cliVersion2 ?? humanReadableVersion toolsVersion: resolvedVersion ?? "unknown"
}; };
} }
async function tryGetFallbackToolcacheVersion(cliVersion2, tagName, logger) { async function tryGetFallbackToolcacheVersion(cliVersion2, tagName, logger) {
const bundleVersion2 = tryGetBundleVersionFromTagName(tagName, logger); const bundleVersion2 = tryGetBundleVersionFromTagName(tagName, logger);
if (!bundleVersion2) { if (bundleVersion2 === void 0) {
return void 0; return void 0;
} }
const fallbackVersion = convertToSemVer(bundleVersion2, logger); const fallbackVersion = convertToSemVer(bundleVersion2, logger);
@@ -152349,7 +152350,9 @@ async function tryGetFallbackToolcacheVersion(cliVersion2, tagName, logger) {
); );
return fallbackVersion; return fallbackVersion;
} }
var downloadCodeQL = async function(codeqlURL, compressionMethod, maybeBundleVersion, maybeCliVersion, apiDetails, tarVersion, tempDir, features, logger) { var downloadCodeQL = async function(source, apiDetails, tarVersion, tempDir, logger) {
const { bundle, compressionMethod } = source;
const codeqlURL = bundle.url;
const parsedCodeQLURL = new URL(codeqlURL); const parsedCodeQLURL = new URL(codeqlURL);
const searchParams = new URLSearchParams(parsedCodeQLURL.search); const searchParams = new URLSearchParams(parsedCodeQLURL.search);
const headers = { const headers = {
@@ -152365,13 +152368,8 @@ var downloadCodeQL = async function(codeqlURL, compressionMethod, maybeBundleVer
codeqlURL codeqlURL
); );
} }
const toolcacheInfo = getToolcacheDestinationInfo( const toolcacheDestination = getToolcacheDestination(source, logger);
maybeBundleVersion, const extractedBundlePath = toolcacheDestination ?? getTempExtractionDir(tempDir);
maybeCliVersion,
logger
);
const extractedBundlePath = toolcacheInfo?.path ?? getTempExtractionDir(tempDir);
await tryDeleteToolcacheBundles({ env: getEnv(), features, logger });
const statusReport = await downloadAndExtract( const statusReport = await downloadAndExtract(
codeqlURL, codeqlURL,
compressionMethod, compressionMethod,
@@ -152381,36 +152379,29 @@ var downloadCodeQL = async function(codeqlURL, compressionMethod, maybeBundleVer
tarVersion, tarVersion,
logger logger
); );
if (!toolcacheInfo) { if (toolcacheDestination) {
writeToolcacheMarkerFile(toolcacheDestination, logger);
} else {
logger.debug( logger.debug(
`Could not cache CodeQL tools because we could not determine the bundle version from the URL ${codeqlURL}.` `Could not cache CodeQL tools because we could not determine the bundle version from the URL ${codeqlURL}.`
); );
return {
codeqlFolder: extractedBundlePath,
statusReport,
toolsVersion: maybeCliVersion ?? "unknown"
};
} }
writeToolcacheMarkerFile(toolcacheInfo.path, logger);
return { return {
codeqlFolder: extractedBundlePath, codeqlFolder: extractedBundlePath,
statusReport, statusReport
toolsVersion: maybeCliVersion ?? toolcacheInfo.version
}; };
}; };
function getToolcacheDestinationInfo(maybeBundleVersion, maybeCliVersion, logger) { function getToolcacheDestination(source, logger) {
if (maybeBundleVersion) { if (!source.bundleVersion) {
const version = getCanonicalToolcacheVersion( return void 0;
maybeCliVersion,
maybeBundleVersion,
logger
);
return {
path: getToolcacheDirectory(version),
version
};
} }
return void 0; return getToolcacheDirectory(
getCanonicalToolcacheVersion(
source.cliVersion,
source.bundleVersion,
logger
)
);
} }
async function tryDeleteToolcacheBundles({ async function tryDeleteToolcacheBundles({
env, env,
@@ -152461,7 +152452,6 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau
logger logger
); );
let codeqlFolder; let codeqlFolder;
let toolsVersion = source.toolsVersion;
let toolsDownloadStatusReport; let toolsDownloadStatusReport;
let toolsSource; let toolsSource;
switch (source.sourceType) { switch (source.sourceType) {
@@ -152482,18 +152472,13 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau
toolsSource = "TOOLCACHE" /* Toolcache */; toolsSource = "TOOLCACHE" /* Toolcache */;
break; break;
case "download": { case "download": {
const result = await downloadCodeQL( const result = await downloadCodeQLBundle(
source.codeqlURL, { env: getEnv(), features, logger },
source.compressionMethod, source,
source.bundleVersion,
source.cliVersion,
apiDetails, apiDetails,
zstdAvailability.version, zstdAvailability.version,
tempDir, tempDir
features,
logger
); );
toolsVersion = result.toolsVersion;
codeqlFolder = result.codeqlFolder; codeqlFolder = result.codeqlFolder;
toolsDownloadStatusReport = result.statusReport; toolsDownloadStatusReport = result.statusReport;
toolsSource = "DOWNLOAD" /* Download */; toolsSource = "DOWNLOAD" /* Download */;
@@ -152507,9 +152492,19 @@ async function setupCodeQLBundle(toolsInput, apiDetails, tempDir, variant, defau
codeqlFolder, codeqlFolder,
toolsDownloadStatusReport, toolsDownloadStatusReport,
toolsSource, toolsSource,
toolsVersion toolsVersion: source.toolsVersion
}; };
} }
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) { async function useZstdBundle(cliVersion2, tarSupportsZstd) {
return ( return (
// In testing, gzip performs better than zstd on Windows. // In testing, gzip performs better than zstd on Windows.

View File

@@ -90,7 +90,7 @@ async function installIntoToolcache({
tmpDir: string; tmpDir: string;
}) { }) {
const url = mockBundleDownloadApi({ apiDetails, isPinned, tagName }); const url = mockBundleDownloadApi({ apiDetails, isPinned, tagName });
await codeql.setupCodeQL( return await codeql.setupCodeQL(
cliVersion !== undefined ? undefined : url, cliVersion !== undefined ? undefined : url,
apiDetails, apiDetails,
tmpDir, tmpDir,
@@ -259,6 +259,65 @@ test.serial(
}, },
); );
for (const { cliVersion, tagName, expectedToolcacheVersion } of [
{
cliVersion: "2.21.0",
tagName: "codeql-bundle-20240101",
expectedToolcacheVersion: "2.21.0",
},
{
cliVersion: "2.21.0-rc.1",
tagName: "codeql-bundle-20240101",
expectedToolcacheVersion: "0.0.0-20240101",
},
{
cliVersion: "2.21.0+20240101",
tagName: "codeql-bundle-20240101",
expectedToolcacheVersion: "0.0.0-20240101",
},
{
cliVersion: "2.21.0",
tagName: "custom-release",
expectedToolcacheVersion: undefined,
},
]) {
test.serial(
`preserves CLI version ${cliVersion} when installing ${tagName}`,
async (t) => {
await util.withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const result = await installIntoToolcache({
cliVersion,
isPinned: false,
tagName,
tmpDir,
});
t.is(result.toolsVersion, cliVersion);
t.is(result.toolsSource, ToolsSource.Download);
t.true(
Number.isInteger(result.toolsDownloadStatusReport?.totalDurationMs),
);
t.deepEqual(
toolcache.findAllVersions("CodeQL"),
expectedToolcacheVersion === undefined
? []
: [expectedToolcacheVersion],
);
if (expectedToolcacheVersion !== undefined) {
const cachedFolder = toolcache.find(
"CodeQL",
expectedToolcacheVersion,
);
t.truthy(cachedFolder);
t.true(fs.existsSync(`${cachedFolder}.complete`));
}
});
},
);
}
const EXPLICITLY_REQUESTED_BUNDLE_TEST_CASES = [ const EXPLICITLY_REQUESTED_BUNDLE_TEST_CASES = [
{ {
tagName: "codeql-bundle-2.17.6", tagName: "codeql-bundle-2.17.6",

View File

@@ -10,7 +10,7 @@ import * as sinon from "sinon";
import * as actionsUtil from "./actions-util"; import * as actionsUtil from "./actions-util";
import * as api from "./api-client"; import * as api from "./api-client";
import * as diagnostics from "./diagnostics"; import * as diagnostics from "./diagnostics";
import { ActionsEnvVars, EnvVar, ReadOnlyEnv } from "./environment"; import { ActionsEnvVars, Env, EnvVar, ReadOnlyEnv } from "./environment";
import { Feature } from "./feature-flags"; import { Feature } from "./feature-flags";
import { getRunnerLogger } from "./logging"; import { getRunnerLogger } from "./logging";
import { getCacheRestoreKeyPrefix } from "./overlay/caching"; import { getCacheRestoreKeyPrefix } from "./overlay/caching";
@@ -25,6 +25,8 @@ import {
createFeatures, createFeatures,
createTestConfig, createTestConfig,
getRecordingLogger, getRecordingLogger,
getTestEnv,
initAllState,
makeMacro, makeMacro,
mockBundleDownloadApi, mockBundleDownloadApi,
setupActionsVars, setupActionsVars,
@@ -34,6 +36,7 @@ import * as toolsDownload from "./tools-download";
import { import {
getErrorMessage, getErrorMessage,
GitHubVariant, GitHubVariant,
HTTPError,
initializeEnvironment, initializeEnvironment,
withTmpDir, withTmpDir,
} from "./util"; } from "./util";
@@ -44,6 +47,15 @@ test.beforeEach(() => {
initializeEnvironment("1.2.3"); initializeEnvironment("1.2.3");
}); });
function stubDownloadAndExtract() {
return sinon
.stub(toolsDownload, "downloadAndExtract")
.callsFake(async (_url, _compressionMethod, dest) => {
fs.mkdirSync(dest, { recursive: true });
return { downloadDurationMs: 200, totalDurationMs: 300 };
});
}
test.serial("parse codeql bundle url version", (t) => { test.serial("parse codeql bundle url version", (t) => {
t.deepEqual( t.deepEqual(
setupCodeql.getCodeQLURLVersion( setupCodeql.getCodeQLURLVersion(
@@ -102,9 +114,9 @@ test.serial(
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir); setupActionsVars(tmpDir, tmpDir);
const tagName = "codeql-bundle-v1.2.3"; const tagName = "codeql-bundle-v1.2.3";
mockBundleDownloadApi({ tagName }); const url = mockBundleDownloadApi({ tagName });
const source = await setupCodeql.getCodeQLSource( const source = await setupCodeql.getCodeQLSource(
`https://github.com/github/codeql-action/releases/download/${tagName}/codeql-bundle-linux64.tar.gz`, url,
SAMPLE_DEFAULT_CLI_VERSION, SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion false, // useOverlayAwareDefaultCliVersion
@@ -115,8 +127,14 @@ test.serial(
getRunnerLogger(true), getRunnerLogger(true),
); );
t.is(source.sourceType, "download"); t.deepEqual(source, {
t.is(source["cliVersion"], "1.2.3"); bundle: { kind: "combined", url },
bundleVersion: "v1.2.3",
cliVersion: "1.2.3",
compressionMethod: "gzip",
sourceType: "download",
toolsVersion: "1.2.3",
} satisfies setupCodeql.CodeQLDownloadSource);
}); });
}, },
); );
@@ -198,7 +216,8 @@ for (const {
t.is(source.sourceType, "download"); t.is(source.sourceType, "download");
if (source.sourceType === "download") { if (source.sourceType === "download") {
t.is(source.compressionMethod, expectedCompressionMethod); t.is(source.compressionMethod, expectedCompressionMethod);
t.true(source.codeqlURL.endsWith(`/${expectedBundleName}`)); t.is(source.bundle.kind, "combined");
t.true(source.bundle.url.endsWith(`/${expectedBundleName}`));
} }
}); });
}, },
@@ -252,23 +271,14 @@ test.serial(
const logger = getRecordingLogger(loggedMessages); const logger = getRecordingLogger(loggedMessages);
const features = createFeatures([]); const features = createFeatures([]);
// Stub the downloadCodeQL function to prevent downloading artefacts const extractStub = stubDownloadAndExtract();
// during testing from being called.
sinon.stub(setupCodeql, "downloadCodeQL").resolves({
codeqlFolder: "codeql",
statusReport: {
downloadDurationMs: 200,
totalDurationMs: 300,
},
toolsVersion: LINKED_CLI_VERSION.cliVersion,
});
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir); setupActionsVars(tmpDir, tmpDir);
const result = await setupCodeql.setupCodeQLBundle( const result = await setupCodeql.setupCodeQLBundle(
"linked", "linked",
SAMPLE_DOTCOM_API_DETAILS, SAMPLE_DOTCOM_API_DETAILS,
"tmp/codeql_action_test/", tmpDir,
GitHubVariant.DOTCOM, GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION, SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages undefined, // rawLanguages
@@ -280,6 +290,15 @@ test.serial(
// Basic sanity check that the version we got back is indeed // Basic sanity check that the version we got back is indeed
// the linked (default) CLI version. // the linked (default) CLI version.
t.is(result.toolsVersion, LINKED_CLI_VERSION.cliVersion); t.is(result.toolsVersion, LINKED_CLI_VERSION.cliVersion);
t.true(extractStub.calledOnce);
t.is(
result.codeqlFolder,
toolcache.find("CodeQL", LINKED_CLI_VERSION.cliVersion),
);
t.deepEqual(result.toolsDownloadStatusReport, {
downloadDurationMs: 200,
totalDurationMs: 300,
});
// Ensure message logging CodeQL CLI version was present in user logs. // Ensure message logging CodeQL CLI version was present in user logs.
const expected_message: string = `Using CodeQL CLI version ${LINKED_CLI_VERSION.cliVersion}`; const expected_message: string = `Using CodeQL CLI version ${LINKED_CLI_VERSION.cliVersion}`;
@@ -305,23 +324,14 @@ test.serial(
"https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.16.0/codeql-bundle-linux64.tar.gz"; "https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.16.0/codeql-bundle-linux64.tar.gz";
const expectedVersion = "2.16.0"; const expectedVersion = "2.16.0";
// Stub the downloadCodeQL function to prevent downloading artefacts const extractStub = stubDownloadAndExtract();
// during testing from being called.
sinon.stub(setupCodeql, "downloadCodeQL").resolves({
codeqlFolder: "codeql",
statusReport: {
downloadDurationMs: 200,
totalDurationMs: 300,
},
toolsVersion: expectedVersion,
});
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir); setupActionsVars(tmpDir, tmpDir);
const result = await setupCodeql.setupCodeQLBundle( const result = await setupCodeql.setupCodeQLBundle(
bundleUrl, bundleUrl,
SAMPLE_DOTCOM_API_DETAILS, SAMPLE_DOTCOM_API_DETAILS,
"tmp/codeql_action_test/", tmpDir,
GitHubVariant.DOTCOM, GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION, SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages undefined, // rawLanguages
@@ -333,6 +343,13 @@ test.serial(
// Basic sanity check that the version we got back is indeed the version that the // Basic sanity check that the version we got back is indeed the version that the
// bundle contains.. // bundle contains..
t.is(result.toolsVersion, expectedVersion); t.is(result.toolsVersion, expectedVersion);
t.true(extractStub.calledOnce);
t.is(extractStub.firstCall.args[0], bundleUrl);
t.is(result.codeqlFolder, toolcache.find("CodeQL", expectedVersion));
t.deepEqual(result.toolsDownloadStatusReport, {
downloadDurationMs: 200,
totalDurationMs: 300,
});
// Ensure message logging CodeQL CLI version was present in user logs. // Ensure message logging CodeQL CLI version was present in user logs.
const expected_message: string = `Using CodeQL CLI version 2.16.0 sourced from ${bundleUrl} .`; const expected_message: string = `Using CodeQL CLI version 2.16.0 sourced from ${bundleUrl} .`;
@@ -348,11 +365,12 @@ test.serial(
); );
test.serial( test.serial(
"getCodeQLSource correctly returns nightly CLI version when tools == nightly", "getCodeQLSource and setupCodeQLBundle preserve the nightly version when tools == nightly",
async (t) => { async (t) => {
const loggedMessages: LoggedMessage[] = []; const loggedMessages: LoggedMessage[] = [];
const logger = getRecordingLogger(loggedMessages); const logger = getRecordingLogger(loggedMessages);
const features = createFeatures([]); const features = createFeatures([]);
const extractStub = stubDownloadAndExtract();
const expectedDate = "30260213"; const expectedDate = "30260213";
const expectedTag = `codeql-bundle-${expectedDate}`; const expectedTag = `codeql-bundle-${expectedDate}`;
@@ -390,14 +408,34 @@ test.serial(
const expectedVersion = `0.0.0-${expectedDate}`; const expectedVersion = `0.0.0-${expectedDate}`;
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`; const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
t.deepEqual(source, { t.deepEqual(source, {
bundle: { kind: "combined", url: expectedURL },
bundleVersion: expectedDate, bundleVersion: expectedDate,
cliVersion: undefined, cliVersion: undefined,
codeqlURL: expectedURL,
compressionMethod: "zstd", compressionMethod: "zstd",
sourceType: "download", sourceType: "download",
toolsVersion: expectedVersion, toolsVersion: expectedVersion,
} satisfies setupCodeql.CodeQLToolsSource); } satisfies setupCodeql.CodeQLToolsSource);
const result = await setupCodeql.setupCodeQLBundle(
"nightly",
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
["javascript"],
false, // useOverlayAwareDefaultCliVersion
features,
logger,
);
t.true(extractStub.calledOnce);
t.is(extractStub.firstCall.args[0], expectedURL);
t.is(result.toolsVersion, source.toolsVersion);
t.is(result.toolsSource, setupCodeql.ToolsSource.Download);
t.is(result.codeqlFolder, toolcache.find("CodeQL", expectedVersion));
t.true(fs.existsSync(`${result.codeqlFolder}.complete`));
t.deepEqual(toolcache.findAllVersions("CodeQL"), [expectedVersion]);
// Afterwards, ensure that we see the expected messages in the log. // Afterwards, ensure that we see the expected messages in the log.
checkExpectedLogMessages(t, loggedMessages, [ checkExpectedLogMessages(t, loggedMessages, [
"Using the latest CodeQL CLI nightly, as requested by 'tools: nightly'.", "Using the latest CodeQL CLI nightly, as requested by 'tools: nightly'.",
@@ -453,9 +491,9 @@ test.serial(
const expectedVersion = `0.0.0-${expectedDate}`; const expectedVersion = `0.0.0-${expectedDate}`;
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`; const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
t.deepEqual(source, { t.deepEqual(source, {
bundle: { kind: "combined", url: expectedURL },
bundleVersion: expectedDate, bundleVersion: expectedDate,
cliVersion: undefined, cliVersion: undefined,
codeqlURL: expectedURL,
compressionMethod: "zstd", compressionMethod: "zstd",
sourceType: "download", sourceType: "download",
toolsVersion: expectedVersion, toolsVersion: expectedVersion,
@@ -472,6 +510,91 @@ test.serial(
}, },
); );
for (const bundlePath of [
"codeql-bundle.tar.gz",
"codeql-bundle.tar.zst",
"codeql-bundle-/codeql-bundle.tar.gz",
]) {
test.serial(
`setupCodeQLBundle reports an unknown version for ${bundlePath}`,
async (t) => {
const extractStub = stubDownloadAndExtract();
const downloadSpy = sinon.spy(setupCodeql, "downloadCodeQL");
const url = `https://example.com/${bundlePath}`;
const messages: LoggedMessage[] = [];
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const result = await setupCodeql.setupCodeQLBundle(
url,
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
getRecordingLogger(messages),
);
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);
t.is(path.dirname(result.codeqlFolder), tmpDir);
t.true(fs.existsSync(result.codeqlFolder));
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
t.deepEqual(toolcache.findAllVersions("CodeQL"), []);
checkExpectedLogMessages(t, messages, [
`Using CodeQL CLI sourced from ${url}`,
]);
});
},
);
}
test.serial(
"setupCodeQLBundle preserves local installation without cleaning the toolcache",
async (t) => {
const cleanupSpy = sinon.spy(toolsDownload, "deleteToolcacheBundles");
const downloadSpy = sinon.spy(setupCodeql, "downloadCodeQL");
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted";
const cachedDirectory = createToolcacheEntry(
tmpDir,
"CodeQL",
CLEANUP_STALE_VERSION,
);
const result = await setupCodeql.setupCodeQLBundle(
path.join(__dirname, "../src/testdata/codeql-bundle.tar.gz"),
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
createFeatures([Feature.CleanupToolcacheBundles]),
getRunnerLogger(true),
);
t.is(result.toolsVersion, "local");
t.is(result.toolsSource, setupCodeql.ToolsSource.Local);
t.is(result.toolsDownloadStatusReport, undefined);
t.is(path.dirname(result.codeqlFolder), tmpDir);
t.true(fs.existsSync(result.codeqlFolder));
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
t.true(fs.existsSync(cachedDirectory));
t.true(cleanupSpy.notCalled);
t.true(downloadSpy.notCalled);
t.is(process.env[EnvVar.HAS_SET_UP_CODEQL], "true");
});
},
);
test.serial( test.serial(
"getCodeQLSource correctly returns latest version from toolcache when tools == toolcache", "getCodeQLSource correctly returns latest version from toolcache when tools == toolcache",
async (t) => { async (t) => {
@@ -974,30 +1097,35 @@ async function runDownloadCodeQL(
toolcacheRoot: string, toolcacheRoot: string,
features: Feature[], features: Feature[],
bundleVersion: string | undefined, bundleVersion: string | undefined,
env = getTestEnv({
[ActionsEnvVars.RUNNER_TOOL_CACHE]: toolcacheRoot,
}),
): Promise<{ ): Promise<{
codeqlFolder: string; codeqlFolder: string;
cleanupDiagnostic: toolsDownload.ToolcacheCleanupResult | undefined; cleanupDiagnostic: toolsDownload.ToolcacheCleanupResult | undefined;
}> { }> {
sinon stubDownloadAndExtract();
.stub(toolsDownload, "downloadAndExtract")
.callsFake(async (_url, _compressionMethod, dest) => {
// The real implementation creates the destination directory, which matters here because the
// cleanup deletes it first and `writeToolcacheMarkerFile` writes into its parent afterwards.
fs.mkdirSync(dest, { recursive: true });
return { totalDurationMs: 1 };
});
const addDiagnostic = sinon.stub(diagnostics, "addNoLanguageDiagnostic"); const addDiagnostic = sinon.stub(diagnostics, "addNoLanguageDiagnostic");
const { codeqlFolder } = await setupCodeql.downloadCodeQL( const { codeqlFolder } = await setupCodeql.downloadCodeQLBundle(
"https://example.com/codeql-bundle.tar.gz", initAllState({
"gzip", env,
bundleVersion, features: createFeatures(features),
CLEANUP_CLI_VERSION, }),
{
bundle: {
kind: "combined",
url: "https://example.com/codeql-bundle.tar.gz",
},
compressionMethod: "gzip",
bundleVersion,
cliVersion: CLEANUP_CLI_VERSION,
sourceType: "download",
toolsVersion: CLEANUP_CLI_VERSION,
},
SAMPLE_DOTCOM_API_DETAILS, SAMPLE_DOTCOM_API_DETAILS,
undefined, // tarVersion undefined, // tarVersion
toolcacheRoot, // tempDir toolcacheRoot, // tempDir
createFeatures(features),
getRunnerLogger(true),
); );
const diagnostic = addDiagnostic const diagnostic = addDiagnostic
@@ -1026,7 +1154,7 @@ async function testToolcacheCleanup(
}: { }: {
features: Feature[]; features: Feature[];
runnerEnvironment: string | undefined; runnerEnvironment: string | undefined;
setUp?: () => void; setUp?: (env: Env) => void;
}, },
check: (context: { check: (context: {
cleanupDiagnostic: toolsDownload.ToolcacheCleanupResult | undefined; cleanupDiagnostic: toolsDownload.ToolcacheCleanupResult | undefined;
@@ -1041,7 +1169,10 @@ async function testToolcacheCleanup(
} else { } else {
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnvironment; process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnvironment;
} }
setUp?.(); const env = getTestEnv({
[ActionsEnvVars.RUNNER_TOOL_CACHE]: tmpDir,
});
setUp?.(env);
// The extraction of the bundle would normally create this directory. // The extraction of the bundle would normally create this directory.
const destinationDirectory = createToolcacheEntry( const destinationDirectory = createToolcacheEntry(
@@ -1060,6 +1191,7 @@ async function testToolcacheCleanup(
tmpDir, tmpDir,
features, features,
CLEANUP_BUNDLE_VERSION, CLEANUP_BUNDLE_VERSION,
env,
); );
t.true( t.true(
@@ -1072,7 +1204,7 @@ async function testToolcacheCleanup(
} }
test.serial( test.serial(
"downloadCodeQL does not clean up the toolcache when the feature flag is disabled", "downloadCodeQLBundle does not clean up the toolcache when the feature flag is disabled",
async (t) => { async (t) => {
await testToolcacheCleanup( await testToolcacheCleanup(
t, t,
@@ -1087,7 +1219,7 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL does not clean up the toolcache when the runner is not GitHub-hosted", "downloadCodeQLBundle does not clean up the toolcache when the runner is not GitHub-hosted",
async (t) => { async (t) => {
await testToolcacheCleanup( await testToolcacheCleanup(
t, t,
@@ -1105,7 +1237,7 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL does not clean up the toolcache when the runner environment is unknown", "downloadCodeQLBundle does not clean up the toolcache when the runner environment is unknown",
async (t) => { async (t) => {
// A runner that doesn't report its environment must be treated as not GitHub-hosted, since its // A runner that doesn't report its environment must be treated as not GitHub-hosted, since its
// toolcache may well outlive the job. // toolcache may well outlive the job.
@@ -1125,7 +1257,7 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL deletes other CodeQL bundles from the toolcache when enabled on a GitHub-hosted runner", "downloadCodeQLBundle deletes other CodeQL bundles from the toolcache when enabled on a GitHub-hosted runner",
async (t) => { async (t) => {
await testToolcacheCleanup( await testToolcacheCleanup(
t, t,
@@ -1153,7 +1285,7 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL reports no deleted versions when the toolcache has no CodeQL bundles", "downloadCodeQLBundle reports no deleted versions when the toolcache has no CodeQL bundles",
async (t) => { async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir); setupActionsVars(tmpDir, tmpDir);
@@ -1175,7 +1307,7 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL continues when deleting a CodeQL bundle from the toolcache fails", "downloadCodeQLBundle continues when deleting a CodeQL bundle from the toolcache fails",
async (t) => { async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir); setupActionsVars(tmpDir, tmpDir);
@@ -1306,7 +1438,7 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL does not follow a symlinked CodeQL toolcache directory", "downloadCodeQLBundle does not follow a symlinked CodeQL toolcache directory",
async (t) => { async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const toolcacheRoot = path.join(tmpDir, "toolcache"); const toolcacheRoot = path.join(tmpDir, "toolcache");
@@ -1342,7 +1474,7 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL does not clean up the toolcache once a step has already set up CodeQL", "downloadCodeQLBundle does not clean up the toolcache once a step has already set up CodeQL",
async (t) => { async (t) => {
// `.github/workflows/codeql.yml` sets up CodeQL twice and then runs both returned paths. If the // `.github/workflows/codeql.yml` sets up CodeQL twice and then runs both returned paths. If the
// second setup downloads, it must not delete the bundle the first one handed out. // second setup downloads, it must not delete the bundle the first one handed out.
@@ -1351,8 +1483,8 @@ test.serial(
{ {
features: [Feature.CleanupToolcacheBundles], features: [Feature.CleanupToolcacheBundles],
runnerEnvironment: "github-hosted", runnerEnvironment: "github-hosted",
setUp: () => { setUp: (env) => {
process.env[EnvVar.HAS_SET_UP_CODEQL] = "true"; env.set(EnvVar.HAS_SET_UP_CODEQL, "true");
}, },
}, },
({ cleanupDiagnostic, destinationDirectory, staleDirectory }) => { ({ cleanupDiagnostic, destinationDirectory, staleDirectory }) => {
@@ -1364,6 +1496,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( test.serial(
"setupCodeQLBundle records that this job has set up CodeQL", "setupCodeQLBundle records that this job has set up CodeQL",
async (t) => { async (t) => {
@@ -1371,11 +1530,7 @@ test.serial(
setupActionsVars(tmpDir, tmpDir); setupActionsVars(tmpDir, tmpDir);
delete process.env[EnvVar.HAS_SET_UP_CODEQL]; delete process.env[EnvVar.HAS_SET_UP_CODEQL];
sinon.stub(setupCodeql, "downloadCodeQL").resolves({ stubDownloadAndExtract();
codeqlFolder: "codeql",
statusReport: { totalDurationMs: 1 },
toolsVersion: LINKED_CLI_VERSION.cliVersion,
});
await setupCodeql.setupCodeQLBundle( await setupCodeql.setupCodeQLBundle(
"linked", "linked",
@@ -1399,7 +1554,50 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL cleans up the toolcache even when the download will not be cached", "setupCodeQLBundle cleans up once and propagates a failed combined download",
async (t) => {
const error = new HTTPError("Not Found", 404);
const extractStub = stubDownloadAndExtract().rejects(error);
const cleanupSpy = sinon.spy(toolsDownload, "deleteToolcacheBundles");
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,
);
await t.throwsAsync(
setupCodeql.setupCodeQLBundle(
"linked",
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
createFeatures([Feature.CleanupToolcacheBundles]),
getRunnerLogger(true),
),
{ is: error },
);
t.true(cleanupSpy.calledOnce);
t.true(cleanupSpy.calledBefore(extractStub));
t.true(extractStub.calledOnce);
t.false(fs.existsSync(staleDirectory));
t.false(fs.existsSync(`${extractStub.firstCall.args[2]}.complete`));
t.deepEqual(toolcache.findAllVersions("CodeQL"), []);
t.is(process.env[EnvVar.HAS_SET_UP_CODEQL], undefined);
});
},
);
test.serial(
"downloadCodeQLBundle cleans up the toolcache even when the download will not be cached",
async (t) => { async (t) => {
// A `tools` URL we can't derive a bundle version from is extracted to a temporary directory // A `tools` URL we can't derive a bundle version from is extracted to a temporary directory
// rather than the toolcache, but the toolcache is on the same filesystem, so emptying it still // rather than the toolcache, but the toolcache is on the same filesystem, so emptying it still
@@ -1434,7 +1632,7 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL reports a failure when the toolcache cannot be inspected", "downloadCodeQLBundle reports a failure when the toolcache cannot be inspected",
async (t) => { async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir); setupActionsVars(tmpDir, tmpDir);
@@ -1466,7 +1664,7 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL does not clean up a toolcache on a different filesystem to the workspace", "downloadCodeQLBundle does not clean up a toolcache on a different filesystem to the workspace",
async (t) => { async (t) => {
// Some runner images keep the toolcache on a different volume to the workspace, in which case // Some runner images keep the toolcache on a different volume to the workspace, in which case
// deleting the tools frees up disk space that the analysis cannot use. // deleting the tools frees up disk space that the analysis cannot use.
@@ -1516,7 +1714,7 @@ test.serial(
); );
test.serial( test.serial(
"downloadCodeQL does not delete through a symlinked version directory", "downloadCodeQLBundle does not delete through a symlinked version directory",
async (t) => { async (t) => {
await withTmpDir(async (tmpDir) => { await withTmpDir(async (tmpDir) => {
const toolcacheRoot = path.join(tmpDir, "toolcache"); const toolcacheRoot = path.join(tmpDir, "toolcache");

View File

@@ -164,7 +164,7 @@ function tryGetBundleVersionFromTagName(
tagName: string, tagName: string,
logger: Logger, logger: Logger,
): string | undefined { ): string | undefined {
const match = tagName.match(/^codeql-bundle-(.*)$/); const match = tagName.match(/^codeql-bundle-(.+)$/);
if (match === null || match.length < 2) { if (match === null || match.length < 2) {
logger.debug(`Could not determine bundle version from tag ${tagName}.`); logger.debug(`Could not determine bundle version from tag ${tagName}.`);
return undefined; return undefined;
@@ -215,6 +215,25 @@ export function convertToSemVer(version: string, logger: Logger): string {
return s; 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;
/** CLI version of the tools, if known. */
cliVersion?: string;
/** Resolved version for telemetry, independent of whether the bundle can be cached. */
toolsVersion: string;
}
export type CodeQLToolsSource = export type CodeQLToolsSource =
| { | {
codeqlTarPath: string; codeqlTarPath: string;
@@ -229,17 +248,7 @@ export type CodeQLToolsSource =
/** Human-readable description of the source of the tools for telemetry purposes. */ /** Human-readable description of the source of the tools for telemetry purposes. */
toolsVersion: string; toolsVersion: string;
} }
| { | CodeQLDownloadSource;
/** Bundle version of the tools, if known. */
bundleVersion?: string;
/** CLI version of the tools, if known. */
cliVersion?: string;
compressionMethod: tar.CompressionMethod;
codeqlURL: string;
sourceType: "download";
/** Human-readable description of the source of the tools for telemetry purposes. */
toolsVersion: string;
};
/** /**
* Look for a version of the CodeQL tools in the cache which could override the requested CLI version. * Look for a version of the CodeQL tools in the cache which could override the requested CLI version.
@@ -583,7 +592,7 @@ export async function getCodeQLSource(
if (tagName) { if (tagName) {
const bundleVersion = tryGetBundleVersionFromTagName(tagName, logger); const bundleVersion = tryGetBundleVersionFromTagName(tagName, logger);
// If the bundle version is a semantic version, it is a CLI version number. // 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); cliVersion = convertToSemVer(bundleVersion, logger);
} }
} }
@@ -600,13 +609,15 @@ export async function getCodeQLSource(
} }
const bundleVersion = const bundleVersion =
tagName && tryGetBundleVersionFromTagName(tagName, logger); tagName !== undefined
const humanReadableVersion = ? tryGetBundleVersionFromTagName(tagName, logger)
: undefined;
const resolvedVersion =
cliVersion ?? cliVersion ??
(bundleVersion && convertToSemVer(bundleVersion, logger)) ?? (bundleVersion !== undefined
tagName ?? ? convertToSemVer(bundleVersion, logger)
url ?? : undefined);
"unknown"; const humanReadableVersion = resolvedVersion ?? tagName ?? url ?? "unknown";
logger.debug( logger.debug(
"Attempting to obtain CodeQL tools. " + "Attempting to obtain CodeQL tools. " +
@@ -750,12 +761,12 @@ export async function getCodeQLSource(
logger.info(`Using CodeQL CLI sourced from ${url} .`); logger.info(`Using CodeQL CLI sourced from ${url} .`);
} }
return { return {
bundleVersion: tagName && tryGetBundleVersionFromTagName(tagName, logger), bundle: { kind: "combined", url },
bundleVersion,
cliVersion, cliVersion,
codeqlURL: url,
compressionMethod, compressionMethod,
sourceType: "download", sourceType: "download",
toolsVersion: cliVersion ?? humanReadableVersion, toolsVersion: resolvedVersion ?? "unknown",
}; };
} }
@@ -769,7 +780,7 @@ async function tryGetFallbackToolcacheVersion(
logger: Logger, logger: Logger,
): Promise<string | undefined> { ): Promise<string | undefined> {
const bundleVersion = tryGetBundleVersionFromTagName(tagName, logger); const bundleVersion = tryGetBundleVersionFromTagName(tagName, logger);
if (!bundleVersion) { if (bundleVersion === undefined) {
return undefined; return undefined;
} }
const fallbackVersion = convertToSemVer(bundleVersion, logger); const fallbackVersion = convertToSemVer(bundleVersion, logger);
@@ -783,20 +794,17 @@ async function tryGetFallbackToolcacheVersion(
// Exported using `export const` for testing purposes. Specifically, we want to // Exported using `export const` for testing purposes. Specifically, we want to
// be able to stub this function and have other functions in this file use that stub. // be able to stub this function and have other functions in this file use that stub.
export const downloadCodeQL = async function ( export const downloadCodeQL = async function (
codeqlURL: string, source: CodeQLDownloadSource,
compressionMethod: tar.CompressionMethod,
maybeBundleVersion: string | undefined,
maybeCliVersion: string | undefined,
apiDetails: api.GitHubApiDetails, apiDetails: api.GitHubApiDetails,
tarVersion: tar.TarVersion | undefined, tarVersion: tar.TarVersion | undefined,
tempDir: string, tempDir: string,
features: FeatureEnablement,
logger: Logger, logger: Logger,
): Promise<{ ): Promise<{
codeqlFolder: string; codeqlFolder: string;
statusReport: ToolsDownloadStatusReport; statusReport: ToolsDownloadStatusReport;
toolsVersion: string;
}> { }> {
const { bundle, compressionMethod } = source;
const codeqlURL = bundle.url;
const parsedCodeQLURL = new URL(codeqlURL); const parsedCodeQLURL = new URL(codeqlURL);
const searchParams = new URLSearchParams(parsedCodeQLURL.search); const searchParams = new URLSearchParams(parsedCodeQLURL.search);
const headers: OutgoingHttpHeaders = { const headers: OutgoingHttpHeaders = {
@@ -815,16 +823,9 @@ export const downloadCodeQL = async function (
); );
} }
const toolcacheInfo = getToolcacheDestinationInfo( const toolcacheDestination = getToolcacheDestination(source, logger);
maybeBundleVersion,
maybeCliVersion,
logger,
);
const extractedBundlePath = const extractedBundlePath =
toolcacheInfo?.path ?? getTempExtractionDir(tempDir); toolcacheDestination ?? getTempExtractionDir(tempDir);
await tryDeleteToolcacheBundles({ env: getEnv(), features, logger });
const statusReport = await downloadAndExtract( const statusReport = await downloadAndExtract(
codeqlURL, codeqlURL,
@@ -836,46 +837,40 @@ export const downloadCodeQL = async function (
logger, logger,
); );
if (!toolcacheInfo) { if (toolcacheDestination) {
writeToolcacheMarkerFile(toolcacheDestination, logger);
} else {
logger.debug( logger.debug(
"Could not cache CodeQL tools because we could not determine the bundle version from the " + "Could not cache CodeQL tools because we could not determine the bundle version from the " +
`URL ${codeqlURL}.`, `URL ${codeqlURL}.`,
); );
return {
codeqlFolder: extractedBundlePath,
statusReport,
toolsVersion: maybeCliVersion ?? "unknown",
};
} }
writeToolcacheMarkerFile(toolcacheInfo.path, logger);
return { return {
codeqlFolder: extractedBundlePath, codeqlFolder: extractedBundlePath,
statusReport, statusReport,
toolsVersion: maybeCliVersion ?? toolcacheInfo.version,
}; };
}; };
function getToolcacheDestinationInfo( /**
maybeBundleVersion: string | undefined, * Returns the canonical toolcache directory for a resolved download, or `undefined` if its bundle
maybeCliVersion: string | undefined, * version is unknown.
*/
function getToolcacheDestination(
source: CodeQLDownloadSource,
logger: Logger, logger: Logger,
): { path: string; version: string } | undefined { ): string | undefined {
if (maybeBundleVersion) { if (!source.bundleVersion) {
const version = getCanonicalToolcacheVersion( return undefined;
maybeCliVersion,
maybeBundleVersion,
logger,
);
return {
path: getToolcacheDirectory(version),
version,
};
} }
return undefined; return getToolcacheDirectory(
getCanonicalToolcacheVersion(
source.cliVersion,
source.bundleVersion,
logger,
),
);
} }
/** /**
@@ -1000,7 +995,6 @@ export async function setupCodeQLBundle(
); );
let codeqlFolder: string; let codeqlFolder: string;
let toolsVersion = source.toolsVersion;
let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined; let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined;
let toolsSource: ToolsSource; let toolsSource: ToolsSource;
switch (source.sourceType) { switch (source.sourceType) {
@@ -1021,18 +1015,13 @@ export async function setupCodeQLBundle(
toolsSource = ToolsSource.Toolcache; toolsSource = ToolsSource.Toolcache;
break; break;
case "download": { case "download": {
const result = await downloadCodeQL( const result = await downloadCodeQLBundle(
source.codeqlURL, { env: getEnv(), features, logger },
source.compressionMethod, source,
source.bundleVersion,
source.cliVersion,
apiDetails, apiDetails,
zstdAvailability.version, zstdAvailability.version,
tempDir, tempDir,
features,
logger,
); );
toolsVersion = result.toolsVersion;
codeqlFolder = result.codeqlFolder; codeqlFolder = result.codeqlFolder;
toolsDownloadStatusReport = result.statusReport; toolsDownloadStatusReport = result.statusReport;
toolsSource = ToolsSource.Download; toolsSource = ToolsSource.Download;
@@ -1050,10 +1039,35 @@ export async function setupCodeQLBundle(
codeqlFolder, codeqlFolder,
toolsDownloadStatusReport, toolsDownloadStatusReport,
toolsSource, toolsSource,
toolsVersion, toolsVersion: source.toolsVersion,
}; };
} }
/**
* 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,
): Promise<{
codeqlFolder: string;
statusReport: ToolsDownloadStatusReport;
}> {
await tryDeleteToolcacheBundles(action);
return await downloadCodeQL(
source,
apiDetails,
tarVersion,
tempDir,
action.logger,
);
}
async function useZstdBundle( async function useZstdBundle(
cliVersion: string, cliVersion: string,
tarSupportsZstd: boolean, tarSupportsZstd: boolean,