From 5f8c44ba623af0a22379411c415fafdc3937b616 Mon Sep 17 00:00:00 2001 From: Mario Campos Date: Fri, 7 Aug 2026 15:11:20 -0500 Subject: [PATCH] Persist CodeQL version output to file rather than environment --- src/environment.ts | 6 --- src/util.test.ts | 96 ++++++++++++++++++++++++++++++---------------- src/util.ts | 47 ++++++++++++++++++----- 3 files changed, 100 insertions(+), 49 deletions(-) diff --git a/src/environment.ts b/src/environment.ts index d6ff20391a..29665512c2 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -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", diff --git a/src/util.test.ts b/src/util.test.ts index 3d27e952af..039ee8cce1 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -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, + ); + } + }); }, ); diff --git a/src/util.ts b/src/util.ts index b7d27afae3..315e9ae4e2 100644 --- a/src/util.ts +++ b/src/util.ts @@ -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);