Skip to content

Commit c702abb

Browse files
committed
Support overlay database creation
1 parent c261e0e commit c702abb

File tree

8 files changed

+286
-7
lines changed

8 files changed

+286
-7
lines changed

src/analyze.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ test("status report fields", async (t) => {
101101
memoryFlag,
102102
addSnippetsFlag,
103103
threadsFlag,
104+
"brutal",
104105
undefined,
105106
undefined,
106107
config,

src/codeql.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { DocUrl } from "./doc-url";
2020
import { FeatureEnablement } from "./feature-flags";
2121
import { Language } from "./languages";
2222
import { getRunnerLogger } from "./logging";
23+
import { OverlayDatabaseMode } from "./overlay-database-utils";
2324
import { ToolsSource } from "./setup-codeql";
2425
import {
2526
setupTests,
@@ -510,6 +511,7 @@ const injectedConfigMacro = test.macro({
510511
"",
511512
undefined,
512513
undefined,
514+
OverlayDatabaseMode.None,
513515
getRunnerLogger(true),
514516
);
515517

@@ -723,6 +725,7 @@ test("passes a code scanning config AND qlconfig to the CLI", async (t: Executio
723725
"",
724726
undefined,
725727
"/path/to/qlconfig.yml",
728+
OverlayDatabaseMode.None,
726729
getRunnerLogger(true),
727730
);
728731

@@ -752,6 +755,7 @@ test("does not pass a qlconfig to the CLI when it is undefined", async (t: Execu
752755
"",
753756
undefined,
754757
undefined, // undefined qlconfigFile
758+
OverlayDatabaseMode.None,
755759
getRunnerLogger(true),
756760
);
757761

@@ -1005,6 +1009,7 @@ test("Avoids duplicating --overwrite flag if specified in CODEQL_ACTION_EXTRA_OP
10051009
"sourceRoot",
10061010
undefined,
10071011
undefined,
1012+
OverlayDatabaseMode.None,
10081013
getRunnerLogger(false),
10091014
);
10101015

src/codeql.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import {
2424
import { isAnalyzingDefaultBranch } from "./git-utils";
2525
import { Language } from "./languages";
2626
import { Logger } from "./logging";
27+
import {
28+
OverlayDatabaseMode,
29+
writeBaseDatabaseOidsFile,
30+
writeOverlayChangedFilesFile,
31+
} from "./overlay-database-utils";
2732
import * as setupCodeql from "./setup-codeql";
2833
import { ZstdAvailability } from "./tar";
2934
import { ToolsDownloadStatusReport } from "./tools-download";
@@ -82,6 +87,7 @@ export interface CodeQL {
8287
sourceRoot: string,
8388
processName: string | undefined,
8489
qlconfigFile: string | undefined,
90+
overlayDatabaseMode: OverlayDatabaseMode,
8591
logger: Logger,
8692
): Promise<void>;
8793
/**
@@ -552,6 +558,7 @@ export async function getCodeQLForCmd(
552558
sourceRoot: string,
553559
processName: string | undefined,
554560
qlconfigFile: string | undefined,
561+
overlayDatabaseMode: OverlayDatabaseMode,
555562
logger: Logger,
556563
) {
557564
const extraArgs = config.languages.map(
@@ -606,12 +613,21 @@ export async function getCodeQLForCmd(
606613
? "--force-overwrite"
607614
: "--overwrite";
608615

616+
if (overlayDatabaseMode === OverlayDatabaseMode.Overlay) {
617+
await writeOverlayChangedFilesFile(config, sourceRoot, logger);
618+
extraArgs.push("--overlay");
619+
} else if (overlayDatabaseMode === OverlayDatabaseMode.OverlayBase) {
620+
extraArgs.push("--overlay-base");
621+
}
622+
609623
await runCli(
610624
cmd,
611625
[
612626
"database",
613627
"init",
614-
overwriteFlag,
628+
...(overlayDatabaseMode === OverlayDatabaseMode.Overlay
629+
? []
630+
: [overwriteFlag]),
615631
"--db-cluster",
616632
config.dbLocation,
617633
`--source-root=${sourceRoot}`,
@@ -625,6 +641,10 @@ export async function getCodeQLForCmd(
625641
],
626642
{ stdin: externalRepositoryToken },
627643
);
644+
645+
if (overlayDatabaseMode === OverlayDatabaseMode.OverlayBase) {
646+
await writeBaseDatabaseOidsFile(config, sourceRoot);
647+
}
628648
},
629649
async runAutobuild(config: Config, language: Language) {
630650
applyAutobuildAzurePipelinesTimeoutFix();

src/git-utils.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,61 @@ export const decodeGitFilePath = function (filePath: string): string {
300300
return filePath;
301301
};
302302

303+
/**
304+
* Get the oids of all files in HEAD.
305+
*
306+
* @param checkoutPath A path into the Git repository.
307+
* @returns a map from file paths to the corresponding oid.
308+
* @throws {Error} if "git ls-tree" produces unexpected output.
309+
*/
310+
export const getAllFileOids = async function (
311+
checkoutPath: string,
312+
): Promise<{ [key: string]: string }> {
313+
const stdout = await runGitCommand(
314+
checkoutPath,
315+
["ls-tree", "--format=%(objectname)_%(path)", "-r", "HEAD"],
316+
"Cannot list file OIDs in HEAD.",
317+
);
318+
319+
const fileOidMap: { [key: string]: string } = {};
320+
const regex = /^([0-9a-f]{40})_(.+)$/;
321+
for (const line of stdout.split("\n")) {
322+
if (line) {
323+
const match = line.match(regex);
324+
if (match) {
325+
const oid = match[1];
326+
const path = decodeGitFilePath(match[2]);
327+
fileOidMap[path] = oid;
328+
} else {
329+
throw new Error(`Unexpected "git ls-tree" output: ${line}`);
330+
}
331+
}
332+
}
333+
return fileOidMap;
334+
};
335+
336+
/**
337+
* Get the root of the Git repository.
338+
*
339+
* @param sourceRoot The source root of the code being analyzed.
340+
* @returns The root of the Git repository.
341+
*/
342+
export const getGitRoot = async function (
343+
sourceRoot: string,
344+
): Promise<string | undefined> {
345+
try {
346+
const stdout = await runGitCommand(
347+
sourceRoot,
348+
["rev-parse", "--show-toplevel"],
349+
`Cannot find Git repository root from the source root ${sourceRoot}.`,
350+
);
351+
return stdout.trim();
352+
} catch {
353+
// Errors are already logged by runGitCommand()
354+
return undefined;
355+
}
356+
};
357+
303358
function getRefFromEnv(): string {
304359
// To workaround a limitation of Actions dynamic workflows not setting
305360
// the GITHUB_REF in some cases, we accept also the ref within the

src/init-action.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,14 @@ import { Feature, featureConfig, Features } from "./feature-flags";
3636
import {
3737
checkInstallPython311,
3838
cleanupDatabaseClusterDirectory,
39+
getOverlayDatabaseMode,
3940
initCodeQL,
4041
initConfig,
4142
runInit,
4243
} from "./init";
4344
import { Language } from "./languages";
4445
import { getActionsLogger, Logger } from "./logging";
46+
import { OverlayDatabaseMode } from "./overlay-database-utils";
4547
import { parseRepositoryNwo } from "./repository";
4648
import { ToolsSource } from "./setup-codeql";
4749
import {
@@ -395,7 +397,22 @@ async function run() {
395397
}
396398

397399
try {
398-
cleanupDatabaseClusterDirectory(config, logger);
400+
const sourceRoot = path.resolve(
401+
getRequiredEnvParam("GITHUB_WORKSPACE"),
402+
getOptionalInput("source-root") || "",
403+
);
404+
405+
const overlayDatabaseMode = await getOverlayDatabaseMode(
406+
(await codeql.getVersion()).version,
407+
config,
408+
sourceRoot,
409+
logger,
410+
);
411+
logger.info(`Using overlay database mode: ${overlayDatabaseMode}`);
412+
413+
if (overlayDatabaseMode !== OverlayDatabaseMode.Overlay) {
414+
cleanupDatabaseClusterDirectory(config, logger);
415+
}
399416

400417
if (zstdAvailability) {
401418
await recordZstdAvailability(config, zstdAvailability);
@@ -675,18 +692,14 @@ async function run() {
675692
}
676693
}
677694

678-
const sourceRoot = path.resolve(
679-
getRequiredEnvParam("GITHUB_WORKSPACE"),
680-
getOptionalInput("source-root") || "",
681-
);
682-
683695
const tracerConfig = await runInit(
684696
codeql,
685697
config,
686698
sourceRoot,
687699
"Runner.Worker.exe",
688700
getOptionalInput("registries"),
689701
apiDetails,
702+
overlayDatabaseMode,
690703
logger,
691704
);
692705
if (tracerConfig !== undefined) {

src/init.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@ import * as path from "path";
33

44
import * as toolrunner from "@actions/exec/lib/toolrunner";
55
import * as io from "@actions/io";
6+
import * as semver from "semver";
67

78
import { getOptionalInput, isSelfHostedRunner } from "./actions-util";
89
import { GitHubApiCombinedDetails, GitHubApiDetails } from "./api-client";
910
import { CodeQL, setupCodeQL } from "./codeql";
1011
import * as configUtils from "./config-utils";
1112
import { CodeQLDefaultVersionInfo, FeatureEnablement } from "./feature-flags";
13+
import { getGitRoot } from "./git-utils";
1214
import { Language, isScannedLanguage } from "./languages";
1315
import { Logger } from "./logging";
16+
import {
17+
CODEQL_OVERLAY_MINIMUM_VERSION,
18+
OverlayDatabaseMode,
19+
} from "./overlay-database-utils";
1420
import { ToolsSource } from "./setup-codeql";
1521
import { ZstdAvailability } from "./tar";
1622
import { ToolsDownloadStatusReport } from "./tools-download";
@@ -79,13 +85,55 @@ export async function initConfig(
7985
return config;
8086
}
8187

88+
export async function getOverlayDatabaseMode(
89+
codeqlVersion: string,
90+
config: configUtils.Config,
91+
sourceRoot: string,
92+
logger: Logger,
93+
): Promise<OverlayDatabaseMode> {
94+
const overlayDatabaseMode = process.env.CODEQL_OVERLAY_DATABASE_MODE;
95+
96+
if (
97+
overlayDatabaseMode === OverlayDatabaseMode.Overlay ||
98+
overlayDatabaseMode === OverlayDatabaseMode.OverlayBase
99+
) {
100+
if (config.buildMode !== util.BuildMode.None) {
101+
logger.warning(
102+
`Cannot build an ${overlayDatabaseMode} database because ` +
103+
`build-mode is set to "${config.buildMode}" instead of "none". ` +
104+
"Falling back to creating a normal full database instead.",
105+
);
106+
return OverlayDatabaseMode.None;
107+
}
108+
if (semver.lt(codeqlVersion, CODEQL_OVERLAY_MINIMUM_VERSION)) {
109+
logger.warning(
110+
`Cannot build an ${overlayDatabaseMode} database because ` +
111+
`the CodeQL CLI is older than ${CODEQL_OVERLAY_MINIMUM_VERSION}. ` +
112+
"Falling back to creating a normal full database instead.",
113+
);
114+
return OverlayDatabaseMode.None;
115+
}
116+
if ((await getGitRoot(sourceRoot)) === undefined) {
117+
logger.warning(
118+
`Cannot build an ${overlayDatabaseMode} database because ` +
119+
`the source root "${sourceRoot}" is not inside a git repository. ` +
120+
"Falling back to creating a normal full database instead.",
121+
);
122+
return OverlayDatabaseMode.None;
123+
}
124+
return overlayDatabaseMode as OverlayDatabaseMode;
125+
}
126+
return OverlayDatabaseMode.None;
127+
}
128+
82129
export async function runInit(
83130
codeql: CodeQL,
84131
config: configUtils.Config,
85132
sourceRoot: string,
86133
processName: string | undefined,
87134
registriesInput: string | undefined,
88135
apiDetails: GitHubApiCombinedDetails,
136+
overlayDatabaseMode: OverlayDatabaseMode,
89137
logger: Logger,
90138
): Promise<TracerConfig | undefined> {
91139
fs.mkdirSync(config.dbLocation, { recursive: true });
@@ -109,6 +157,7 @@ export async function runInit(
109157
sourceRoot,
110158
processName,
111159
qlconfigFile,
160+
overlayDatabaseMode,
112161
logger,
113162
),
114163
);

0 commit comments

Comments
 (0)