Cache the output of codeql resolve languages

Repeated calls to `resolveLanguages()` will only pay the performance penalty of executing `codeql resolve languages` once.
This commit is contained in:
Mario Campos
2026-06-03 16:39:55 -05:00
parent ee70cd5210
commit 311292c28f
3 changed files with 99 additions and 19 deletions
+6 -1
View File
@@ -731,6 +731,8 @@ async function getCodeQLForCmd(
filterToLanguagesWithQueries: boolean;
} = { filterToLanguagesWithQueries: false },
) {
let result = util.getCachedCodeQlResolveLanguages(cmd);
if (result === undefined) {
const codeqlArgs = [
"resolve",
"languages",
@@ -745,12 +747,15 @@ async function getCodeQLForCmd(
const output = await runCli(cmd, codeqlArgs);
try {
return JSON.parse(output) as ResolveLanguagesOutput;
result = JSON.parse(output) as ResolveLanguagesOutput;
} catch (e) {
throw new Error(
`Unexpected output from codeql resolve languages with --format=betterjson: ${e}`,
);
}
util.cacheCodeQlResolveLanguages(cmd, result);
}
return result;
},
async resolveBuildEnvironment(
workingDir: string | undefined,
+6
View File
@@ -23,6 +23,12 @@ export enum EnvVar {
*/
CODEQL_VERSION_INFO = "CODEQL_ACTION_CLI_VERSION_INFO",
/**
* `ResolveLanguagesOutput` for the CodeQL CLI, so later Actions steps can reuse it instead of
* invoking `codeql resolve languages` again.
*/
CODEQL_RESOLVE_LANGUAGES = "CODEQL_ACTION_CLI_RESOLVE_LANGUAGES",
/** Whether the CodeQL Action has invoked the Go autobuilder. */
DID_AUTOBUILD_GOLANG = "CODEQL_ACTION_DID_AUTOBUILD_GOLANG",
+70 -1
View File
@@ -10,7 +10,7 @@ import * as yaml from "js-yaml";
import * as semver from "semver";
import * as apiCompatibility from "./api-compatibility.json";
import type { CodeQL, VersionInfo } from "./codeql";
import type { CodeQL, VersionInfo, ResolveLanguagesOutput } from "./codeql";
import type { Pack } from "./config/db-config";
import type { Config } from "./config-utils";
import { EnvVar } from "./environment";
@@ -701,6 +701,75 @@ export function getCachedCodeQlVersion(cmd?: string): undefined | VersionInfo {
return cachedCodeQlVersion;
}
let cachedCodeQlResolveLanguages: undefined | ResolveLanguagesOutput =
undefined;
interface PersistedResolveLanguagesOutput {
cmd: string;
output: ResolveLanguagesOutput;
}
export function cacheCodeQlResolveLanguages(
cmd: string,
output: ResolveLanguagesOutput,
): void {
if (cachedCodeQlResolveLanguages !== undefined) {
throw new Error("cacheCodeQlResolveLanguages() should be called only once");
}
cachedCodeQlResolveLanguages = output;
// Persist the output so that subsequent Actions steps, which run in separate
// processes, can reuse it rather than invoking `codeql resolve languages` again. We
// record the CLI path so that a different step using a different CodeQL bundle
// doesn't pick up a stale output.
core.exportVariable(
EnvVar.CODEQL_RESOLVE_LANGUAGES,
JSON.stringify({ cmd, output }),
);
}
function isPersistedResolveLanguagesOutput(
value: unknown,
): value is PersistedResolveLanguagesOutput {
return (
typeof value === "object" &&
value !== null &&
typeof (value as Record<string, unknown>).cmd === "string" &&
typeof (value as Record<string, unknown>).output === "object" &&
(value as Record<string, unknown>).output !== null
);
}
export function getCachedCodeQlResolveLanguages(
cmd?: string,
): undefined | ResolveLanguagesOutput {
if (cachedCodeQlResolveLanguages !== undefined) {
return cachedCodeQlResolveLanguages;
}
// Fall back to the value persisted by an earlier Actions step, if any. This is
// best-effort: any malformed or mismatched value is ignored so that the caller
// invokes `codeql resolve languages` instead.
const serialized = process.env[EnvVar.CODEQL_RESOLVE_LANGUAGES];
if (!serialized) {
return undefined;
}
let persisted: unknown;
try {
persisted = JSON.parse(serialized);
} catch {
return undefined;
}
if (
!isPersistedResolveLanguagesOutput(persisted) ||
(cmd !== undefined && persisted.cmd !== cmd)
) {
return undefined;
}
// Memoize the parsed value so that subsequent calls in this process don't
// re-parse the environment variable.
cachedCodeQlResolveLanguages = persisted.output;
return cachedCodeQlResolveLanguages;
}
export async function codeQlVersionAtLeast(
codeql: CodeQL,
requiredVersion: string,