mirror of
https://github.com/github/codeql-action.git
synced 2026-10-03 17:41:28 +00:00
Clarify bundle resolution and latest-nightly selection
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -148,8 +148,8 @@ test("getPerLanguageBundleLanguage explains a disabled feature before checking e
|
||||
);
|
||||
});
|
||||
|
||||
test("getPerLanguageBundleLanguage skips only the release version check for nightlies", async (t) => {
|
||||
const nightly = { isNightly: true, cliVersion: undefined };
|
||||
test("getPerLanguageBundleLanguage skips only the release version check for the latest nightly", async (t) => {
|
||||
const nightly = { isLatestNightly: true, cliVersion: undefined };
|
||||
t.is(await checkEligibility(nightly), BuiltInLanguage.java);
|
||||
|
||||
for (const overrides of [
|
||||
|
||||
@@ -14,7 +14,7 @@ export const MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION = "2.27.1";
|
||||
const PER_LANGUAGE_BUNDLE_NAME =
|
||||
/^codeql-bundle-(.+)-(?:linux64|osx64|win64)\.tar\.(?:gz|zst)$/;
|
||||
|
||||
/** Identifies per-language tools URLs that must not populate the toolcache. */
|
||||
/** Returns the language in a per-language tools URL, or undefined for other URLs. */
|
||||
export function tryGetBundleLanguageFromUrl(
|
||||
url: string,
|
||||
): BuiltInLanguage | undefined {
|
||||
@@ -55,13 +55,14 @@ const PER_LANGUAGE_BUNDLE_LANGUAGES: Readonly<
|
||||
export interface PerLanguageBundleOptions {
|
||||
/** Explicit input only: autodetection needs a CLI instance. */
|
||||
rawLanguages: string[] | undefined;
|
||||
/** CLI version, if known. Ignored for nightly bundles. */
|
||||
/** Requested CLI version, if known. Ignored when requesting the latest nightly. */
|
||||
cliVersion: string | undefined;
|
||||
compressionMethod: tar.CompressionMethod;
|
||||
/** Platform for which the bundle is requested. */
|
||||
platform: BundlePlatform | undefined;
|
||||
variant: GitHubVariant;
|
||||
isNightly?: boolean;
|
||||
/** Whether the Action is selecting the latest nightly rather than a release version. */
|
||||
isLatestNightly?: boolean;
|
||||
}
|
||||
|
||||
/** Returns the eligible bundle language, or undefined for the combined bundle. */
|
||||
@@ -79,7 +80,7 @@ export async function getPerLanguageBundleLanguage(
|
||||
compressionMethod,
|
||||
platform,
|
||||
variant,
|
||||
isNightly,
|
||||
isLatestNightly,
|
||||
} = options;
|
||||
|
||||
const explain = (reason: string) => {
|
||||
@@ -106,7 +107,7 @@ export async function getPerLanguageBundleLanguage(
|
||||
|
||||
if (compressionMethod !== "zstd") {
|
||||
// Per-language bundles are only published as zstd archives.
|
||||
return explain(`the bundle would be downloaded as ${compressionMethod}`);
|
||||
return explain(`the bundle would be downloaded as '${compressionMethod}'`);
|
||||
}
|
||||
|
||||
if (variant !== GitHubVariant.DOTCOM) {
|
||||
@@ -121,16 +122,17 @@ export async function getPerLanguageBundleLanguage(
|
||||
return explain("the job is not running on a GitHub-hosted runner");
|
||||
}
|
||||
|
||||
// Nightly tags contain dates rather than comparable CLI versions.
|
||||
if (!isNightly) {
|
||||
// Check whether per-language bundles are published for the requested CLI version.
|
||||
// Skip this for the latest nightly, whose tag contains a date rather than a CLI version.
|
||||
if (!isLatestNightly) {
|
||||
if (cliVersion === undefined) {
|
||||
return explain("the CLI version of the bundle is unknown");
|
||||
return explain("the requested CLI version is unknown");
|
||||
}
|
||||
|
||||
if (!semver.gte(cliVersion, MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION)) {
|
||||
return explain(
|
||||
`CodeQL ${cliVersion} is older than ${MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION}, which is the ` +
|
||||
"first version that publishes per-language bundles",
|
||||
`the requested CodeQL version ${cliVersion} is older than ${MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION}, which is the ` +
|
||||
"first version for which per-language bundles are published",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ function stubDownloadAndExtract() {
|
||||
});
|
||||
}
|
||||
|
||||
/** Models a hosted Linux runner with zstd and the latest nightly release. */
|
||||
function stubHostedNightly(tagName: string) {
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(process, "arch").value("x64");
|
||||
@@ -66,15 +67,25 @@ function stubHostedNightly(tagName: string) {
|
||||
available: true,
|
||||
foundZstdBinary: true,
|
||||
});
|
||||
const client = github.getOctokit("123", {
|
||||
request: {
|
||||
fetch: async () =>
|
||||
const fetchRelease = sinon
|
||||
.stub<Parameters<typeof fetch>, ReturnType<typeof fetch>>()
|
||||
.rejects(new Error("Unexpected API request in nightly bundle test"));
|
||||
fetchRelease
|
||||
.withArgs(
|
||||
"https://api.github.com/repos/dsp-testing/codeql-cli-nightlies/releases?per_page=1&page=1&prerelease=true",
|
||||
sinon.match({ method: "GET" }),
|
||||
)
|
||||
.callsFake(
|
||||
async () =>
|
||||
new Response(JSON.stringify([{ tag_name: tagName }]), {
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
},
|
||||
);
|
||||
const client = github.getOctokit("123", {
|
||||
request: { fetch: fetchRelease },
|
||||
});
|
||||
sinon.stub(api, "getApiClient").value(() => client);
|
||||
return fetchRelease;
|
||||
}
|
||||
|
||||
test.serial("parse codeql bundle url version", (t) => {
|
||||
@@ -600,11 +611,11 @@ test.serial(
|
||||
|
||||
for (const toolsInput of ["nightly", "nightly-latest"]) {
|
||||
test.serial(
|
||||
`getCodeQLSource selects a per-language bundle for tools == ${toolsInput}`,
|
||||
`getCodeQLSource selects the latest per-language nightly for tools == ${toolsInput}`,
|
||||
async (t) => {
|
||||
const expectedTag = "codeql-bundle-30260213";
|
||||
const baseURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}`;
|
||||
stubHostedNightly(expectedTag);
|
||||
const latestNightlyRequest = stubHostedNightly(expectedTag);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -633,13 +644,14 @@ for (const toolsInput of ["nightly", "nightly-latest"]) {
|
||||
compressionMethod: "zstd",
|
||||
toolsVersion: "0.0.0-30260213",
|
||||
} satisfies setupCodeql.CodeQLDownloadSource);
|
||||
t.true(latestNightlyRequest.calledOnce);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource downloads the combined nightly bundle when not eligible",
|
||||
"getCodeQLSource downloads a combined nightly bundle when per-language selection is ineligible",
|
||||
async (t) => {
|
||||
const expectedTag = "codeql-bundle-30260213";
|
||||
stubHostedNightly(expectedTag);
|
||||
@@ -647,7 +659,9 @@ test.serial(
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
for (const { languages, features } of [
|
||||
// The per-language feature is disabled.
|
||||
{ languages: ["java"], features: createFeatures([]) },
|
||||
// More than one language requires a combined bundle.
|
||||
{
|
||||
languages: ["java", "python"],
|
||||
features: createFeatures([Feature.PerLanguageBundles]),
|
||||
@@ -679,11 +693,11 @@ test.serial(
|
||||
|
||||
for (const perLanguageBundles of [false, true]) {
|
||||
test.serial(
|
||||
`getCodeQLSource uses a ${perLanguageBundles ? "per-language" : "combined"} bundle for a forced nightly`,
|
||||
`getCodeQLSource uses the latest ${perLanguageBundles ? "per-language" : "combined"} bundle for a forced nightly`,
|
||||
async (t) => {
|
||||
const expectedTag = "codeql-bundle-30260213";
|
||||
const baseURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}`;
|
||||
stubHostedNightly(expectedTag);
|
||||
const latestNightlyRequest = stubHostedNightly(expectedTag);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic" });
|
||||
@@ -704,6 +718,7 @@ for (const perLanguageBundles of [false, true]) {
|
||||
);
|
||||
|
||||
t.is(source.sourceType, "download");
|
||||
t.true(latestNightlyRequest.calledOnce);
|
||||
if (source.sourceType === "download") {
|
||||
const combinedURL = `${baseURL}/codeql-bundle-linux64.tar.zst`;
|
||||
t.deepEqual(
|
||||
@@ -723,6 +738,105 @@ for (const perLanguageBundles of [false, true]) {
|
||||
);
|
||||
}
|
||||
|
||||
for (const date of ["20200101", "30260213"]) {
|
||||
for (const bundle of ["combined", "per-language"] as const) {
|
||||
test.serial(
|
||||
`getCodeQLSource preserves an explicit ${bundle} nightly URL for ${date}`,
|
||||
async (t) => {
|
||||
const latestNightlyRequest = stubHostedNightly(
|
||||
"codeql-bundle-30260213",
|
||||
);
|
||||
const asset =
|
||||
bundle === "combined"
|
||||
? "codeql-bundle-linux64.tar.zst"
|
||||
: "codeql-bundle-java-linux64.tar.zst";
|
||||
const url = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/codeql-bundle-${date}/${asset}`;
|
||||
const features = createFeatures([Feature.PerLanguageBundles]);
|
||||
const logger = getRecordingLogger([], { logToConsole: false });
|
||||
const error = new HTTPError("Not Found", 404);
|
||||
const extractStub = sinon
|
||||
.stub(toolsDownload, "downloadAndExtract")
|
||||
.rejects(error);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
url,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
["java"],
|
||||
false,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
true,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
t.deepEqual(source, {
|
||||
sourceType: "download",
|
||||
bundle:
|
||||
bundle === "combined"
|
||||
? { kind: "combined", url }
|
||||
: { kind: "per-language", url, language: BuiltInLanguage.java },
|
||||
bundleVersion: date,
|
||||
cliVersion: undefined,
|
||||
compressionMethod: "zstd",
|
||||
toolsVersion: `0.0.0-${date}`,
|
||||
} satisfies setupCodeql.CodeQLDownloadSource);
|
||||
|
||||
await t.throwsAsync(
|
||||
setupCodeql.setupCodeQLBundle(
|
||||
url,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
["java"],
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
),
|
||||
{ is: error },
|
||||
);
|
||||
t.true(extractStub.calledOnce);
|
||||
t.is(extractStub.firstCall.args[0], url);
|
||||
t.true(latestNightlyRequest.notCalled);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource reports a missing release tag when a toolcache entry disappears",
|
||||
async (t) => {
|
||||
sinon
|
||||
.stub(toolcache, "findAllVersions")
|
||||
.returns([MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION]);
|
||||
sinon.stub(toolcache, "find").returns("");
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic" });
|
||||
await t.throwsAsync(
|
||||
setupCodeql.getCodeQLSource(
|
||||
"toolcache",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
["java"],
|
||||
false,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
true,
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
),
|
||||
{
|
||||
message:
|
||||
"Could not determine a release tag for the requested CodeQL bundle.",
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource correctly returns latest version from toolcache when tools == toolcache",
|
||||
async (t) => {
|
||||
@@ -1016,18 +1130,21 @@ const PER_LANGUAGE_CLI_VERSION = {
|
||||
],
|
||||
};
|
||||
|
||||
test.serial("getCodeQLBundleName names the per-language bundle", (t) => {
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(process, "arch").value("x64");
|
||||
t.is(
|
||||
setupCodeql.getCodeQLBundleName("zstd", BuiltInLanguage.java),
|
||||
"codeql-bundle-java-linux64.tar.zst",
|
||||
);
|
||||
t.is(
|
||||
setupCodeql.getCodeQLBundleName("zstd"),
|
||||
"codeql-bundle-linux64.tar.zst",
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"getCodeQLBundleName returns a per-language bundle name only when a language is specified",
|
||||
(t) => {
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(process, "arch").value("x64");
|
||||
t.is(
|
||||
setupCodeql.getCodeQLBundleName("zstd", BuiltInLanguage.java),
|
||||
"codeql-bundle-java-linux64.tar.zst",
|
||||
);
|
||||
t.is(
|
||||
setupCodeql.getCodeQLBundleName("zstd"),
|
||||
"codeql-bundle-linux64.tar.zst",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial("getCodeQLBundleName names the Swift bundle for macOS", (t) => {
|
||||
sinon.stub(process, "platform").value("darwin");
|
||||
@@ -1313,7 +1430,7 @@ for (const bundle of ["per-language", "combined", "fallback"] as const) {
|
||||
bundle: { kind: "combined", url: combinedURL },
|
||||
});
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
`No javascript CodeQL bundle was found at ${perLanguageURL}`,
|
||||
`No per-language CodeQL bundle for 'javascript' was found at ${perLanguageURL}`,
|
||||
]);
|
||||
}
|
||||
if (bundle === "per-language") {
|
||||
|
||||
@@ -83,8 +83,7 @@ function getCodeQLBundleExtension(
|
||||
* Returns the name of the CodeQL bundle asset to download.
|
||||
*
|
||||
* @param compressionMethod The compression method of the bundle.
|
||||
* @param language If provided, the name of the bundle that contains only this language, rather than
|
||||
* the name of the combined bundle that contains every language.
|
||||
* @param language Optional language for a per-language bundle. If omitted, returns a combined bundle name.
|
||||
*/
|
||||
export function getCodeQLBundleName(
|
||||
compressionMethod: tar.CompressionMethod,
|
||||
@@ -247,7 +246,7 @@ export interface CodeQLDownloadSource {
|
||||
compressionMethod: tar.CompressionMethod;
|
||||
/** Bundle version of the tools, if known. */
|
||||
bundleVersion?: string;
|
||||
/** CLI version of the tools, if known. */
|
||||
/** Requested CLI version, if known. */
|
||||
cliVersion?: string;
|
||||
/** Resolved version for telemetry, independent of whether the bundle can be cached. */
|
||||
toolsVersion: string;
|
||||
@@ -476,7 +475,7 @@ export async function getCodeQLSource(
|
||||
};
|
||||
}
|
||||
|
||||
/** CLI version number, for example 2.12.6. */
|
||||
/** Requested CLI version number, for example 2.12.6. */
|
||||
let cliVersion: string | undefined;
|
||||
/** Tag name of the CodeQL bundle, for example `codeql-bundle-20230120`. */
|
||||
let tagName: string | undefined;
|
||||
@@ -530,7 +529,7 @@ export async function getCodeQLSource(
|
||||
`Using the latest CodeQL CLI nightly, as requested by 'tools: ${toolsInput}'.`,
|
||||
);
|
||||
}
|
||||
bundle = await getNightlyBundle(
|
||||
bundle = await getLatestNightlyBundle(
|
||||
{ env: getEnv(), features, logger },
|
||||
rawLanguages,
|
||||
variant,
|
||||
@@ -758,6 +757,13 @@ export async function getCodeQLSource(
|
||||
let compressionMethod: tar.CompressionMethod;
|
||||
|
||||
if (!url) {
|
||||
const bundleTagName = tagName;
|
||||
if (bundleTagName === undefined) {
|
||||
throw new Error(
|
||||
"Could not determine a release tag for the requested CodeQL bundle.",
|
||||
);
|
||||
}
|
||||
|
||||
compressionMethod =
|
||||
cliVersion !== undefined &&
|
||||
(await useZstdBundle(cliVersion, tarSupportsZstd))
|
||||
@@ -775,28 +781,29 @@ export async function getCodeQLSource(
|
||||
},
|
||||
);
|
||||
|
||||
// Resolve both bundle variants against the same release and repository lookup order.
|
||||
const resolveBundleURL = (language?: BuiltInLanguage) =>
|
||||
getCodeQLBundleDownloadURL(
|
||||
tagName!,
|
||||
bundleTagName,
|
||||
apiDetails,
|
||||
getCodeQLBundleName(compressionMethod, language),
|
||||
logger,
|
||||
);
|
||||
|
||||
const combinedBundleURL = await resolveBundleURL();
|
||||
if (perLanguageBundleLanguage !== undefined) {
|
||||
logger.info(
|
||||
`Downloading the ${perLanguageBundleLanguage} CodeQL bundle, since ${perLanguageBundleLanguage} ` +
|
||||
"is the only language being analyzed.",
|
||||
`Selected the per-language CodeQL bundle for '${perLanguageBundleLanguage}'.`,
|
||||
);
|
||||
url = await resolveBundleURL(perLanguageBundleLanguage);
|
||||
bundle = {
|
||||
kind: "per-language",
|
||||
url,
|
||||
language: perLanguageBundleLanguage,
|
||||
combinedBundleURL: await resolveBundleURL(),
|
||||
combinedBundleURL,
|
||||
};
|
||||
} else {
|
||||
url = await resolveBundleURL();
|
||||
url = combinedBundleURL;
|
||||
bundle = { kind: "combined", url };
|
||||
}
|
||||
} else {
|
||||
@@ -928,8 +935,8 @@ export const downloadCodeQL = async function (
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the canonical toolcache directory for a resolved download, or `undefined` if its bundle
|
||||
* version is unknown.
|
||||
* Returns the canonical toolcache directory for a combined bundle with a known version.
|
||||
* Returns undefined for per-language bundles or unknown versions.
|
||||
*/
|
||||
function getToolcacheDestination(
|
||||
source: CodeQLDownloadSource,
|
||||
@@ -1122,8 +1129,8 @@ export async function setupCodeQLBundle(
|
||||
/**
|
||||
* Performs eligible toolcache cleanup once, then downloads and extracts the resolved bundle.
|
||||
*
|
||||
* If `source` refers to a bundle for a single language and that bundle turns out not to exist, this
|
||||
* falls back to downloading the combined bundle.
|
||||
* If an automatically selected per-language bundle is missing, downloads the combined bundle
|
||||
* from the same release instead. Explicit bundle URLs are not substituted.
|
||||
*
|
||||
* @returns The extraction directory and download timings.
|
||||
*/
|
||||
@@ -1160,7 +1167,7 @@ export async function downloadCodeQLBundle(
|
||||
throw e;
|
||||
}
|
||||
logger.warning(
|
||||
`No ${bundle.language} CodeQL bundle was found at ${bundle.url}, so ` +
|
||||
`No per-language CodeQL bundle for '${bundle.language}' was found at ${bundle.url}, so ` +
|
||||
"falling back to the bundle that contains all languages. This analysis will still " +
|
||||
"produce correct results, but will take longer to set up.",
|
||||
);
|
||||
@@ -1202,8 +1209,11 @@ function getTempExtractionDir(tempDir: string) {
|
||||
return path.join(tempDir, uuidV4());
|
||||
}
|
||||
|
||||
/** Selects a bundle from the latest nightly, with a same-release fallback when applicable. */
|
||||
async function getNightlyBundle(
|
||||
/**
|
||||
* Selects a bundle from the latest nightly release, preferring a per-language bundle when eligible.
|
||||
* Records the combined bundle URL from that release for use if the selected asset is missing.
|
||||
*/
|
||||
async function getLatestNightlyBundle(
|
||||
action: ActionState<["Logger", "ReadOnlyEnv", "FeatureFlags"]>,
|
||||
rawLanguages: string[] | undefined,
|
||||
variant: util.GitHubVariant,
|
||||
@@ -1224,7 +1234,7 @@ async function getNightlyBundle(
|
||||
compressionMethod,
|
||||
platform: getBundlePlatform(),
|
||||
variant,
|
||||
isNightly: true,
|
||||
isLatestNightly: true,
|
||||
});
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user