Skip to content

Commit 6ca06f4

Browse files
committed
Upload overlay-base database to actions cache
1 parent d42ce71 commit 6ca06f4

File tree

2 files changed

+118
-2
lines changed

2 files changed

+118
-2
lines changed

src/analyze-action.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { EnvVar } from "./environment";
2727
import { Features } from "./feature-flags";
2828
import { Language } from "./languages";
2929
import { getActionsLogger, Logger } from "./logging";
30+
import { uploadOverlayBaseDatabaseToCache } from "./overlay-database-utils";
3031
import { getRepositoryNwo } from "./repository";
3132
import * as statusReport from "./status-report";
3233
import {
@@ -349,6 +350,9 @@ async function run() {
349350
// Possibly upload the database bundles for remote queries
350351
await uploadDatabases(repositoryNwo, config, apiDetails, logger);
351352

353+
// Possibly upload the overlay-base database to actions cache
354+
await uploadOverlayBaseDatabaseToCache(codeql, config, logger);
355+
352356
// Possibly upload the TRAP caches for later re-use
353357
const trapCacheUploadStartTime = performance.now();
354358
didUploadTrapCaches = await uploadTrapCaches(codeql, config, logger);

src/overlay-database-utils.ts

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import * as fs from "fs";
22
import * as path from "path";
33

4-
import { getTemporaryDirectory } from "./actions-util";
4+
import * as actionsCache from "@actions/cache";
5+
6+
import { getRequiredInput, getTemporaryDirectory } from "./actions-util";
7+
import { type CodeQL } from "./codeql";
58
import { type Config } from "./config-utils";
6-
import { getFileOidsUnderPath } from "./git-utils";
9+
import { getCommitOid, getFileOidsUnderPath } from "./git-utils";
710
import { Logger } from "./logging";
11+
import { isInTestMode, withTimeout } from "./util";
812

913
export enum OverlayDatabaseMode {
1014
Overlay = "overlay",
@@ -122,3 +126,111 @@ function computeChangedFiles(
122126
}
123127
return changes;
124128
}
129+
130+
// Constants for database caching
131+
const CACHE_VERSION = 1;
132+
const CACHE_PREFIX = "codeql-overlay-base-database";
133+
const MAX_CACHE_OPERATION_MS = 120_000; // Two minutes
134+
135+
/**
136+
* Uploads the overlay-base database to the GitHub Actions cache. If conditions
137+
* for uploading are not met, the function does nothing and returns false.
138+
*
139+
* This function uses the `checkout_path` input to determine the repository path
140+
* and works only when called from `analyze` or `upload-sarif`.
141+
*
142+
* @param codeql The CodeQL instance
143+
* @param config The configuration object
144+
* @param logger The logger instance
145+
* @returns A promise that resolves to true if the upload was performed and
146+
* successfully completed, or false otherwise
147+
*/
148+
export async function uploadOverlayBaseDatabaseToCache(
149+
codeql: CodeQL,
150+
config: Config,
151+
logger: Logger,
152+
): Promise<boolean> {
153+
const overlayDatabaseMode = config.augmentationProperties.overlayDatabaseMode;
154+
if (overlayDatabaseMode !== OverlayDatabaseMode.OverlayBase) {
155+
logger.debug(
156+
`Overlay database mode is ${overlayDatabaseMode}. ` +
157+
"Skip uploading overlay-base database to cache.",
158+
);
159+
return false;
160+
}
161+
if (!config.augmentationProperties.useOverlayDatabaseCaching) {
162+
logger.debug(
163+
"Overlay database caching is disabled. " +
164+
"Skip uploading overlay-base database to cache.",
165+
);
166+
return false;
167+
}
168+
if (isInTestMode()) {
169+
logger.debug(
170+
"In test mode. Skip uploading overlay-base database to cache.",
171+
);
172+
return false;
173+
}
174+
175+
// An overlay-base database should contain the base database OIDs file.
176+
// Verifying that the file exists serves as a sanity check.
177+
const baseDatabaseOidsFilePath = getBaseDatabaseOidsFilePath(config);
178+
if (!fs.existsSync(baseDatabaseOidsFilePath)) {
179+
logger.warning(
180+
"Cannot upload overlay-base database to cache: " +
181+
`${baseDatabaseOidsFilePath} does not exist`,
182+
);
183+
return false;
184+
}
185+
186+
const dbLocation = config.dbLocation;
187+
const codeQlVersion = (await codeql.getVersion()).version;
188+
const checkoutPath = getRequiredInput("checkout_path");
189+
const cacheKey = await generateCacheKey(config, codeQlVersion, checkoutPath);
190+
logger.info(
191+
`Uploading overlay-base database to Actions cache with key ${cacheKey}`,
192+
);
193+
194+
try {
195+
const cacheId = await withTimeout(
196+
MAX_CACHE_OPERATION_MS,
197+
actionsCache.saveCache([dbLocation], cacheKey),
198+
() => {},
199+
);
200+
if (cacheId === undefined) {
201+
logger.warning("Timed out while uploading overlay-base database");
202+
return false;
203+
}
204+
} catch (error) {
205+
logger.warning(
206+
"Failed to upload overlay-base database to cache: " +
207+
`${error instanceof Error ? error.message : String(error)}`,
208+
);
209+
return false;
210+
}
211+
logger.info(`Successfully uploaded overlay-base database from ${dbLocation}`);
212+
return true;
213+
}
214+
215+
async function generateCacheKey(
216+
config: Config,
217+
codeQlVersion: string,
218+
checkoutPath: string,
219+
): Promise<string> {
220+
const sha = await getCommitOid(checkoutPath);
221+
return `${getCacheRestoreKey(config, codeQlVersion)}${sha}`;
222+
}
223+
224+
function getCacheRestoreKey(config: Config, codeQlVersion: string): string {
225+
// The restore key (prefix) specifies which cached overlay-base databases are
226+
// compatible with the current analysis: the cached database must have the
227+
// same cache version and the same CodeQL bundle version.
228+
//
229+
// Actions cache supports using multiple restore keys to indicate preference.
230+
// Technically we prefer a cached overlay-base database with the same SHA as
231+
// we are analyzing. However, since overlay-base databases are built from the
232+
// default branch and used in PR analysis, it is exceedingly unlikely that
233+
// the commit SHA will ever be the same, so we can just leave it out.
234+
const languages = [...config.languages].sort().join("_");
235+
return `${CACHE_PREFIX}-${CACHE_VERSION}-${languages}-${codeQlVersion}-`;
236+
}

0 commit comments

Comments
 (0)