Compare commits

..

1 Commits

Author SHA1 Message Date
Mario Campos 5f8c44ba62 Persist CodeQL version output to file rather than environment 2026-08-07 15:11:20 -05:00
3 changed files with 100 additions and 49 deletions
-6
View File
@@ -39,12 +39,6 @@ export enum EnvVar {
*/
CODE_SCANNING_REF = "CODE_SCANNING_REF",
/**
* `PersistedVersionInfo` for the CodeQL CLI, so later Actions steps can reuse it instead of
* invoking `codeql version` again.
*/
CODEQL_VERSION_INFO = "CODEQL_ACTION_CLI_VERSION_INFO",
/** Whether the CodeQL Action has invoked the Go autobuilder. */
DID_AUTOBUILD_GOLANG = "CODEQL_ACTION_DID_AUTOBUILD_GOLANG",
+62 -34
View File
@@ -10,7 +10,7 @@ import * as sinon from "sinon";
import * as api from "./api-client";
import { EnvVar } from "./environment";
import { getRunnerLogger } from "./logging";
import { setupTests } from "./testing-utils";
import { getTestEnv, setupTests } from "./testing-utils";
import * as util from "./util";
setupTests(test);
@@ -535,55 +535,83 @@ test("Failure.orElse returns the default value for a failure result", (t) => {
test.serial(
"getCachedCodeQlVersion reuses a version persisted by an earlier step",
(t) => {
process.env[EnvVar.CODEQL_VERSION_INFO] = JSON.stringify({
cmd: "/path/to/codeql",
version: { version: "2.20.0" },
});
t.deepEqual(util.getCachedCodeQlVersion("/path/to/codeql"), {
version: "2.20.0",
async (t) => {
await util.withTmpDir(async (tmpDir: string) => {
const cacheFile = path.join(tmpDir, "version.json");
fs.writeFileSync(
cacheFile,
JSON.stringify({
cmd: "/path/to/codeql",
version: { version: "2.20.0" },
}),
"utf8",
);
const env = getTestEnv({ [EnvVar.TEMP]: tmpDir });
t.deepEqual(util.getCachedCodeQlVersion("/path/to/codeql", env), {
version: "2.20.0",
});
});
},
);
test.serial(
"getCachedCodeQlVersion ignores a persisted version from a different CLI",
(t) => {
process.env[EnvVar.CODEQL_VERSION_INFO] = JSON.stringify({
cmd: "/path/to/other-codeql",
version: { version: "2.20.0" },
async (t) => {
await util.withTmpDir(async (tmpDir: string) => {
const cacheFile = path.join(tmpDir, "version.json");
fs.writeFileSync(
cacheFile,
JSON.stringify({
cmd: "/path/to/other-codeql",
version: { version: "2.20.0" },
}),
"utf8",
);
const env = getTestEnv({ [EnvVar.TEMP]: tmpDir });
t.is(util.getCachedCodeQlVersion("/path/to/codeql", env), undefined);
});
t.is(util.getCachedCodeQlVersion("/path/to/codeql"), undefined);
},
);
test.serial(
"getCachedCodeQlVersion ignores a malformed persisted value",
(t) => {
process.env[EnvVar.CODEQL_VERSION_INFO] = "not valid json";
t.is(util.getCachedCodeQlVersion("/path/to/codeql"), undefined);
async (t) => {
await util.withTmpDir(async (tmpDir: string) => {
const cacheFile = path.join(tmpDir, "version.json");
fs.writeFileSync(cacheFile, "not valid json", "utf8");
const env = getTestEnv({ [EnvVar.TEMP]: tmpDir });
t.is(util.getCachedCodeQlVersion("/path/to/codeql", env), undefined);
});
},
);
test.serial(
"getCachedCodeQlVersion ignores a persisted value with the wrong structure",
(t) => {
for (const value of [
JSON.stringify({ cmd: "/path/to/codeql" }),
JSON.stringify({ cmd: "/path/to/codeql", version: {} }),
JSON.stringify({ cmd: "/path/to/codeql", version: { version: 2 } }),
JSON.stringify({ version: { version: "2.20.0" } }),
JSON.stringify({
cmd: "/path/to/codeql",
version: { version: "2.20.0", overlayVersion: "1" },
}),
JSON.stringify({
cmd: "/path/to/codeql",
version: { version: "2.20.0", features: "nope" },
}),
]) {
process.env[EnvVar.CODEQL_VERSION_INFO] = value;
t.is(util.getCachedCodeQlVersion("/path/to/codeql"), undefined, value);
}
async (t) => {
await util.withTmpDir(async (tmpDir: string) => {
const cacheFile = path.join(tmpDir, "version.json");
const env = getTestEnv({ [EnvVar.TEMP]: tmpDir });
for (const value of [
JSON.stringify({ cmd: "/path/to/codeql" }),
JSON.stringify({ cmd: "/path/to/codeql", version: {} }),
JSON.stringify({ cmd: "/path/to/codeql", version: { version: 2 } }),
JSON.stringify({ version: { version: "2.20.0" } }),
JSON.stringify({
cmd: "/path/to/codeql",
version: { version: "2.20.0", overlayVersion: "1" },
}),
JSON.stringify({
cmd: "/path/to/codeql",
version: { version: "2.20.0", features: "nope" },
}),
]) {
fs.writeFileSync(cacheFile, value, "utf8");
t.is(
util.getCachedCodeQlVersion("/path/to/codeql", env),
undefined,
value,
);
}
});
},
);
+38 -9
View File
@@ -9,11 +9,12 @@ import getFolderSize from "get-folder-size";
import * as yaml from "js-yaml";
import * as semver from "semver";
import { getTemporaryDirectory } from "./actions-util";
import * as apiCompatibility from "./api-compatibility.json";
import type { CodeQL, VersionInfo } from "./codeql";
import type { Pack } from "./config/db-config";
import type { Config } from "./config-utils";
import { EnvVar, getRequiredEnvParam } from "./environment";
import { Env, EnvVar, getEnv, getRequiredEnvParam } from "./environment";
import * as json from "./json";
import { Language } from "./languages";
import { Logger } from "./logging";
@@ -638,7 +639,25 @@ function isPersistedVersionInfo(x: unknown): x is PersistedVersionInfo {
);
}
export function cacheCodeQlVersion(cmd: string, version: VersionInfo): void {
/**
* Returns the file path to the `codeql version` output cache.
* @param env The environment variables to use—only necessary for testing.
*/
function getPathToCodeQLVersionCacheFile(env: Env): string {
return path.join(getTemporaryDirectory(env), "version.json");
}
/**
* Caches the CodeQL CLI version both in-memory and on disk.
* @param cmd The path to the CodeQL CLI.
* @param version The version information to cache.
* @param env The environment variables to use—only necessary for testing.
*/
export function cacheCodeQlVersion(
cmd: string,
version: VersionInfo,
env: Env = getEnv(),
): void {
if (cachedCodeQlVersion !== undefined) {
throw new Error("cacheCodeQlVersion() should be called only once");
}
@@ -647,23 +666,33 @@ export function cacheCodeQlVersion(cmd: string, version: VersionInfo): void {
// processes, can reuse it rather than invoking `codeql version` again. We
// record the CLI path so that a different step using a different CodeQL bundle
// doesn't pick up a stale version.
core.exportVariable(
EnvVar.CODEQL_VERSION_INFO,
fs.writeFileSync(
getPathToCodeQLVersionCacheFile(env),
JSON.stringify({ cmd, version }),
"utf8",
);
}
export function getCachedCodeQlVersion(cmd?: string): undefined | VersionInfo {
/**
* Returns the cached CodeQL CLI version, if any. If not cached,
* attempts to read and parse it from disk.
* @param cmd The path to the CodeQL CLI.
* @param env The environment variables to use—only necessary for testing.
*/
export function getCachedCodeQlVersion(
cmd?: string,
env: Env = getEnv(),
): undefined | VersionInfo {
if (cachedCodeQlVersion !== undefined) {
return cachedCodeQlVersion;
}
// 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 version` instead.
const serialized = process.env[EnvVar.CODEQL_VERSION_INFO];
if (!serialized) {
return undefined;
}
const serialized = fs.readFileSync(
getPathToCodeQLVersionCacheFile(env),
"utf8",
);
let persisted: unknown;
try {
persisted = JSON.parse(serialized);