Reuse the toolcache for stable releases in the CodeQL Action repositories

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Henry Mercer
2026-09-25 17:46:14 +01:00
parent 17f14786a5
commit c1e8d8d12c
3 changed files with 127 additions and 10 deletions

15
lib/entry-points.js generated
View File

@@ -152243,6 +152243,15 @@ function getDefaultBundleSources(apiDetails, logger) {
return !self2.slice(0, index2).some((other) => (0, import_fast_deep_equal.default)(source, other));
});
}
function isCacheableRelease(reference, cliVersion2, apiDetails, logger) {
if (cliVersion2 === void 0 || !/^\d+\.\d+\.\d+$/.test(cliVersion2) || reference.tagName !== `codeql-bundle-v${cliVersion2}`) {
return false;
}
const repository = `${reference.owner}/${reference.repo}`.toLowerCase();
return getDefaultBundleSources(apiDetails, logger).some(
([serverURL, sourceRepository]) => new URL(serverURL).origin === new URL(reference.serverURL).origin && sourceRepository.toLowerCase() === repository
);
}
async function selectDefaultBundle(action, tagName, apiDetails, options) {
const { logger } = action;
for (const [serverURL, repository] of getDefaultBundleSources(
@@ -152534,7 +152543,9 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO
release2.assetNames ?? [],
logger
);
customReleaseURL = release2.url;
if (!isCacheableRelease(requestedRelease, cliVersion2, apiDetails, logger)) {
customReleaseURL = release2.url;
}
} else if (toolsInput !== void 0) {
tagName = tryGetTagNameFromUrl(toolsInput, logger);
url2 = toolsInput;
@@ -152776,7 +152787,7 @@ var downloadCodeQL = async function(source, apiDetails, tarVersion, tempDir, log
function getToolcacheDestination({ logger }, source) {
if (source.customReleaseURL !== void 0) {
return new Failure(
`Not caching the CodeQL tools from ${source.customReleaseURL}, since we don't cache releases requested by URL.`
`Not caching the CodeQL tools from ${source.customReleaseURL}, since we only cache stable releases in the CodeQL Action repositories.`
);
}
if (source.bundle.kind !== "combined") {

View File

@@ -1519,15 +1519,88 @@ test.serial(
},
);
for (const [apiDetails, variant] of [
[SAMPLE_DOTCOM_API_DETAILS, GitHubVariant.DOTCOM],
[GHES_API_DETAILS, GitHubVariant.GHES],
] as const) {
test.serial(
`setupCodeQLBundle caches a stable release in the CodeQL Action repository on ${variant}`,
async (t) => {
const fixture = stubRequestedRelease({
apiDetails,
repository: "github/codeql-action",
});
const extract = stubDownloadAndExtract();
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const setup = () =>
setupCodeql.setupCodeQLBundle(
fixture.releaseURL,
apiDetails,
tmpDir,
variant,
PER_LANGUAGE_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
getRunnerLogger(true),
);
const downloaded = await setup();
t.deepEqual(fixture.requests, [
`${apiDetails.apiURL}/repos/github/codeql-action/releases/tags/codeql-bundle-v${MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION}`,
]);
t.is(downloaded.toolsSource, setupCodeql.ToolsSource.Download);
t.is(extract.firstCall.args[0], fixture.assets[0].url);
t.is(extract.firstCall.args[3], `token ${apiDetails.auth}`);
t.is(
downloaded.codeqlFolder,
toolcache.find("CodeQL", MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION),
);
const cached = await setup();
t.is(cached.toolsSource, setupCodeql.ToolsSource.Toolcache);
t.is(cached.codeqlFolder, downloaded.codeqlFolder);
t.true(extract.calledOnce);
});
},
);
}
for (const { repository, tagName, markers } of [
{
repository: "github/codeql-action",
tagName: "codeql-bundle-v2.27.1-rc.1",
markers: [],
},
{
repository: "github/codeql-action",
tagName: "codeql-bundle-v2.27.1",
markers: ["2.27.1+202609241200"],
},
{
repository: "github/codeql-action",
tagName: "codeql-bundle-20260924",
markers: ["2.27.1+202609241200"],
},
{
repository: "github/codeql-action",
tagName: "codeql-bundle-acme-2.27.1",
markers: ["2.27.1"],
},
{
repository: "octo/tools",
tagName: "codeql-bundle-feature_branch",
markers: [],
},
]) {
const description =
markers.length === 0
? tagName
: `${tagName} with marker ${markers.join(", ")}`;
test.serial(
`setupCodeQLBundle doesn't share the toolcache with ${tagName} in ${repository}`,
`setupCodeQLBundle doesn't share the toolcache with ${description} in ${repository}`,
async (t) => {
const fixture = stubRequestedRelease({ repository, tagName, markers });
const extract = stubDownloadAndExtract();
@@ -1590,7 +1663,10 @@ for (const repository of ["github/codeql-action", "octo/tools"]) {
`https://github.com/${repository}/releases/download/codeql-bundle-v${MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION}/codeql-bundle-linux64.tar.zst`,
);
t.is(extract.firstCall.args[3], undefined);
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
t.is(
fs.existsSync(`${result.codeqlFolder}.complete`),
repository === "github/codeql-action",
);
});
},
);

View File

@@ -26,6 +26,7 @@ import {
BundleSelection,
BundleSelectionOptions,
CodeQLRelease,
CodeQLReleaseReference,
getPublicRelease,
getRelease,
getReleaseCliVersion,
@@ -114,6 +115,32 @@ function getDefaultBundleSources(
});
}
/**
* Whether a release requested by URL contains the same build as the toolcache entry for its CLI
* version. The toolcache is keyed by version alone, so we only assume this for stable releases
* tagged `codeql-bundle-v<version>` in the repositories we download the default bundles from.
*/
function isCacheableRelease(
reference: CodeQLReleaseReference,
cliVersion: string | undefined,
apiDetails: api.GitHubApiDetails,
logger: Logger,
): boolean {
if (
cliVersion === undefined ||
!/^\d+\.\d+\.\d+$/.test(cliVersion) ||
reference.tagName !== `codeql-bundle-v${cliVersion}`
) {
return false;
}
const repository = `${reference.owner}/${reference.repo}`.toLowerCase();
return getDefaultBundleSources(apiDetails, logger).some(
([serverURL, sourceRepository]) =>
new URL(serverURL).origin === new URL(reference.serverURL).origin &&
sourceRepository.toLowerCase() === repository,
);
}
/**
* Selects a bundle from the first release tagged `tagName` that has a compatible bundle, trying the
* Action repositories on this GitHub instance before the canonical Action on GitHub.com. If we
@@ -405,9 +432,10 @@ async function resolveDefaultCliVersion(
*
* - A local path is extracted without using the toolcache.
* - A release URL selects a bundle from that release, and takes precedence over the `force_nightly`
* feature flag. We don't use the toolcache, since a release may contain a different build than
* the cached bundle for its version. Bundle URLs keep using the toolcache for the version in
* their tag, for compatibility.
* feature flag. Only stable releases in the repositories we download the default bundles from use
* the toolcache, since other releases may contain a different build than the cached bundle for
* their version. Bundle URLs keep using the toolcache for the version in their tag, for
* compatibility.
* - `nightly` or `nightly-latest`, or the `force_nightly` feature flag in a dynamic workflow,
* selects a bundle from the latest nightly release. We then continue with that bundle's URL.
* - `linked`, or its old name `latest`, selects the version shipped with the Action.
@@ -625,7 +653,9 @@ export async function getCodeQLSource(
release.assetNames ?? [],
logger,
);
customReleaseURL = release.url;
if (!isCacheableRelease(requestedRelease, cliVersion, apiDetails, logger)) {
customReleaseURL = release.url;
}
} else if (toolsInput !== undefined) {
// Any other value is a bundle URL, including one we selected from the latest nightly above.
// We use the version in its tag, if any, for the toolcache, so we assume that bundles with the
@@ -971,8 +1001,8 @@ function getToolcacheDestination(
): util.Result<string, string> {
if (source.customReleaseURL !== undefined) {
return new util.Failure(
`Not caching the CodeQL tools from ${source.customReleaseURL}, since we don't cache ` +
"releases requested by URL.",
`Not caching the CodeQL tools from ${source.customReleaseURL}, since we only cache stable ` +
"releases in the CodeQL Action repositories.",
);
}
if (source.bundle.kind !== "combined") {