Add save and restore methods

This commit is contained in:
Henry Mercer
2026-02-17 13:38:00 +00:00
parent d28d9967fe
commit 69c2819972
+117 -1
View File
@@ -8,8 +8,124 @@
* We use the Actions cache as a lightweight way of providing this functionality.
*/
import * as fs from "fs";
import * as path from "path";
import * as actionsCache from "@actions/cache";
import { getTemporaryDirectory } from "../actions-util";
import { type CodeQL } from "../codeql";
import { DiskUsage } from "../util";
import { Logger } from "../logging";
import {
DiskUsage,
getErrorMessage,
waitForResultWithTimeLimit,
} from "../util";
/** The maximum time to wait for a cache operation to complete. */
const MAX_CACHE_OPERATION_MS = 30_000;
/** File name for the serialized overlay status. */
const STATUS_FILE_NAME = "overlay-status.json";
/** Status of an overlay analysis for a particular language. */
export interface OverlayStatus {
/** Whether the job successfully built an overlay base database. */
builtOverlayBaseDatabase: boolean;
}
/**
* Retrieve overlay status from the Actions cache, if available.
*
* @returns `undefined` if no status was found in the cache (e.g. first run with
* this cache key) or if the cache operation fails.
*/
export async function getOverlayStatus(
codeql: CodeQL,
language: string,
diskUsage: DiskUsage,
logger: Logger,
): Promise<OverlayStatus | undefined> {
const cacheKey = await getCacheKey(codeql, language, diskUsage);
const statusFile = path.join(
getTemporaryDirectory(),
"overlay-status",
language,
STATUS_FILE_NAME,
);
await fs.promises.mkdir(path.dirname(statusFile), { recursive: true });
try {
const foundKey = await waitForResultWithTimeLimit(
MAX_CACHE_OPERATION_MS,
actionsCache.restoreCache([statusFile], cacheKey),
() => {
logger.info("Timed out restoring overlay status from cache");
},
);
if (foundKey === undefined) {
logger.debug("No overlay status found in Actions cache");
return undefined;
}
if (!fs.existsSync(statusFile)) {
logger.debug(
"Overlay status cache entry found but status file is missing",
);
return undefined;
}
const contents = await fs.promises.readFile(statusFile, "utf-8");
return JSON.parse(contents) as OverlayStatus;
} catch (error) {
logger.warning(
`Failed to restore overlay status from cache: ${getErrorMessage(error)}`,
);
return undefined;
}
}
/**
* Save overlay status to the Actions cache.
*
* @returns `true` if the status was saved successfully, `false` otherwise.
*/
export async function saveOverlayStatus(
codeql: CodeQL,
language: string,
diskUsage: DiskUsage,
status: OverlayStatus,
logger: Logger,
): Promise<boolean> {
const cacheKey = await getCacheKey(codeql, language, diskUsage);
const statusFile = path.join(
getTemporaryDirectory(),
"overlay-status",
language,
STATUS_FILE_NAME,
);
await fs.promises.mkdir(path.dirname(statusFile), { recursive: true });
await fs.promises.writeFile(statusFile, JSON.stringify(status));
try {
const cacheId = await waitForResultWithTimeLimit(
MAX_CACHE_OPERATION_MS,
actionsCache.saveCache([statusFile], cacheKey),
() => {},
);
if (cacheId === undefined) {
logger.warning("Timed out saving overlay status to cache");
return false;
}
logger.info(`Saved overlay status to Actions cache with key ${cacheKey}`);
return true;
} catch (error) {
logger.warning(
`Failed to save overlay status to cache: ${getErrorMessage(error)}`,
);
return false;
}
}
export async function getCacheKey(
codeql: CodeQL,