Skip to content

Commit fef394b

Browse files
author
Dave Bartolomeo
authored
Merge pull request #3233 from github/dbartol/mrva-multi-bundle
Use CLI to bundle packs for MRVA
2 parents 477c6b3 + d7e5552 commit fef394b

File tree

12 files changed

+449
-125
lines changed

12 files changed

+449
-125
lines changed

extensions/ql-vscode/src/codeql-cli/cli-command.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,38 @@ import { promisify } from "util";
33

44
import type { BaseLogger } from "../common/logging";
55
import type { ProgressReporter } from "../common/logging/vscode";
6-
import { getChildProcessErrorMessage } from "../common/helpers-pure";
6+
import {
7+
getChildProcessErrorMessage,
8+
getErrorMessage,
9+
} from "../common/helpers-pure";
710

811
/**
912
* Flags to pass to all cli commands.
1013
*/
1114
export const LOGGING_FLAGS = ["-v", "--log-to-stderr"];
1215

1316
/**
14-
* Runs a CodeQL CLI command without invoking the CLI server, returning the output as a string.
17+
* Runs a CodeQL CLI command without invoking the CLI server, deserializing the output as JSON.
1518
* @param codeQlPath The path to the CLI.
1619
* @param command The `codeql` command to be run, provided as an array of command/subcommand names.
1720
* @param commandArgs The arguments to pass to the `codeql` command.
1821
* @param description Description of the action being run, to be shown in log and error messages.
1922
* @param logger Logger to write command log messages, e.g. to an output channel.
2023
* @param progressReporter Used to output progress messages, e.g. to the status bar.
21-
* @returns The contents of the command's stdout, if the command succeeded.
24+
* @returns A JSON object parsed from the contents of the command's stdout, if the command succeeded.
2225
*/
23-
export async function runCodeQlCliCommand(
26+
export async function runJsonCodeQlCliCommand<OutputType>(
2427
codeQlPath: string,
2528
command: string[],
2629
commandArgs: string[],
2730
description: string,
2831
logger: BaseLogger,
2932
progressReporter?: ProgressReporter,
30-
): Promise<string> {
33+
): Promise<OutputType> {
3134
// Add logging arguments first, in case commandArgs contains positional parameters.
3235
const args = command.concat(LOGGING_FLAGS).concat(commandArgs);
3336
const argsString = args.join(" ");
37+
let stdout: string;
3438
try {
3539
if (progressReporter !== undefined) {
3640
progressReporter.report({ message: description });
@@ -41,10 +45,18 @@ export async function runCodeQlCliCommand(
4145
const result = await promisify(execFile)(codeQlPath, args);
4246
void logger.log(result.stderr);
4347
void logger.log("CLI command succeeded.");
44-
return result.stdout;
48+
stdout = result.stdout;
4549
} catch (err) {
4650
throw new Error(
4751
`${description} failed: ${getChildProcessErrorMessage(err)}`,
4852
);
4953
}
54+
55+
try {
56+
return JSON.parse(stdout) as OutputType;
57+
} catch (err) {
58+
throw new Error(
59+
`Parsing output of ${description} failed: ${getErrorMessage(err)}`,
60+
);
61+
}
5062
}

extensions/ql-vscode/src/codeql-cli/cli-version.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,49 @@
11
import type { SemVer } from "semver";
22
import { parse } from "semver";
3-
import { runCodeQlCliCommand } from "./cli-command";
3+
import { runJsonCodeQlCliCommand } from "./cli-command";
44
import type { Logger } from "../common/logging";
55
import { getErrorMessage } from "../common/helpers-pure";
66

7+
interface VersionResult {
8+
version: string;
9+
features: CliFeatures | undefined;
10+
}
11+
12+
export interface CliFeatures {
13+
featuresInVersionResult?: boolean;
14+
mrvaPackCreate?: boolean;
15+
}
16+
17+
export interface VersionAndFeatures {
18+
version: SemVer;
19+
features: CliFeatures;
20+
}
21+
722
/**
823
* Get the version of a CodeQL CLI.
924
*/
1025
export async function getCodeQlCliVersion(
1126
codeQlPath: string,
1227
logger: Logger,
13-
): Promise<SemVer | undefined> {
28+
): Promise<VersionAndFeatures | undefined> {
1429
try {
15-
const output: string = await runCodeQlCliCommand(
30+
const output: VersionResult = await runJsonCodeQlCliCommand<VersionResult>(
1631
codeQlPath,
1732
["version"],
18-
["--format=terse"],
33+
["--format=json"],
1934
"Checking CodeQL version",
2035
logger,
2136
);
22-
return parse(output.trim()) || undefined;
37+
38+
const version = parse(output.version.trim()) || undefined;
39+
if (version === undefined) {
40+
return undefined;
41+
}
42+
43+
return {
44+
version,
45+
features: output.features ?? {},
46+
};
2347
} catch (e) {
2448
// Failed to run the version command. This might happen if the cli version is _really_ old, or it is corrupted.
2549
// Either way, we can't determine compatibility.

extensions/ql-vscode/src/codeql-cli/cli.ts

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { QueryLanguage } from "../common/query-language";
3434
import { LINE_ENDINGS, splitStreamAtSeparators } from "../common/split-stream";
3535
import type { Position } from "../query-server/messages";
3636
import { LOGGING_FLAGS } from "./cli-command";
37+
import type { CliFeatures, VersionAndFeatures } from "./cli-version";
3738

3839
/**
3940
* The version of the SARIF format that we are using.
@@ -203,7 +204,9 @@ type OnLineCallback = (
203204
line: string,
204205
) => Promise<string | undefined> | string | undefined;
205206

206-
type VersionChangedListener = (newVersion: SemVer | undefined) => void;
207+
type VersionChangedListener = (
208+
newVersionAndFeatures: VersionAndFeatures | undefined,
209+
) => void;
207210

208211
/**
209212
* This class manages a cli server started by `codeql execute cli-server` to
@@ -221,8 +224,8 @@ export class CodeQLCliServer implements Disposable {
221224
/** A buffer with a single null byte. */
222225
nullBuffer: Buffer;
223226

224-
/** Version of current cli, lazily computed by the `getVersion()` method */
225-
private _version: SemVer | undefined;
227+
/** Version of current cli and its supported features, lazily computed by the `getVersion()` method */
228+
private _versionAndFeatures: VersionAndFeatures | undefined;
226229

227230
private _versionChangedListeners: VersionChangedListener[] = [];
228231

@@ -298,7 +301,7 @@ export class CodeQLCliServer implements Disposable {
298301
const callback = (): void => {
299302
try {
300303
this.killProcessIfRunning();
301-
this._version = undefined;
304+
this._versionAndFeatures = undefined;
302305
this._supportedLanguages = undefined;
303306
} finally {
304307
this.runNext();
@@ -1427,16 +1430,28 @@ export class CodeQLCliServer implements Disposable {
14271430
);
14281431
}
14291432

1433+
/**
1434+
* Compile a CodeQL pack and bundle it into a single file.
1435+
*
1436+
* @param sourcePackDir The directory of the input CodeQL pack.
1437+
* @param workspaceFolders The workspace folders to search for additional packs.
1438+
* @param outputBundleFile The path to the output bundle file.
1439+
* @param outputPackDir The directory to contain the unbundled output pack.
1440+
* @param moreOptions Additional options to be passed to `codeql pack bundle`.
1441+
*/
14301442
async packBundle(
1431-
dir: string,
1443+
sourcePackDir: string,
14321444
workspaceFolders: string[],
1433-
outputPath: string,
1445+
outputBundleFile: string,
1446+
outputPackDir: string,
14341447
moreOptions: string[],
14351448
): Promise<void> {
14361449
const args = [
14371450
"-o",
1438-
outputPath,
1439-
dir,
1451+
outputBundleFile,
1452+
sourcePackDir,
1453+
"--pack-path",
1454+
outputPackDir,
14401455
...moreOptions,
14411456
...this.getAdditionalPacksArg(workspaceFolders),
14421457
];
@@ -1491,27 +1506,35 @@ export class CodeQLCliServer implements Disposable {
14911506
);
14921507
}
14931508

1494-
public async getVersion() {
1495-
if (!this._version) {
1509+
public async getVersion(): Promise<SemVer> {
1510+
return (await this.getVersionAndFeatures()).version;
1511+
}
1512+
1513+
public async getFeatures(): Promise<CliFeatures> {
1514+
return (await this.getVersionAndFeatures()).features;
1515+
}
1516+
1517+
private async getVersionAndFeatures(): Promise<VersionAndFeatures> {
1518+
if (!this._versionAndFeatures) {
14961519
try {
1497-
const newVersion = await this.refreshVersion();
1498-
this._version = newVersion;
1520+
const newVersionAndFeatures = await this.refreshVersion();
1521+
this._versionAndFeatures = newVersionAndFeatures;
14991522
this._versionChangedListeners.forEach((listener) =>
1500-
listener(newVersion),
1523+
listener(newVersionAndFeatures),
15011524
);
15021525

15031526
// this._version is only undefined upon config change, so we reset CLI-based context key only when necessary.
15041527
await this.app.commands.execute(
15051528
"setContext",
15061529
"codeql.supportsQuickEvalCount",
1507-
newVersion.compare(
1530+
newVersionAndFeatures.version.compare(
15081531
CliVersionConstraint.CLI_VERSION_WITH_QUICK_EVAL_COUNT,
15091532
) >= 0,
15101533
);
15111534
await this.app.commands.execute(
15121535
"setContext",
15131536
"codeql.supportsTrimCache",
1514-
newVersion.compare(
1537+
newVersionAndFeatures.version.compare(
15151538
CliVersionConstraint.CLI_VERSION_WITH_TRIM_CACHE,
15161539
) >= 0,
15171540
);
@@ -1522,23 +1545,23 @@ export class CodeQLCliServer implements Disposable {
15221545
throw e;
15231546
}
15241547
}
1525-
return this._version;
1548+
return this._versionAndFeatures;
15261549
}
15271550

15281551
public addVersionChangedListener(listener: VersionChangedListener) {
1529-
if (this._version) {
1530-
listener(this._version);
1552+
if (this._versionAndFeatures) {
1553+
listener(this._versionAndFeatures);
15311554
}
15321555
this._versionChangedListeners.push(listener);
15331556
}
15341557

1535-
private async refreshVersion() {
1558+
private async refreshVersion(): Promise<VersionAndFeatures> {
15361559
const distribution = await this.distributionProvider.getDistribution();
15371560
switch (distribution.kind) {
15381561
case FindDistributionResultKind.CompatibleDistribution:
15391562
// eslint-disable-next-line no-fallthrough -- Intentional fallthrough
15401563
case FindDistributionResultKind.IncompatibleDistribution:
1541-
return distribution.version;
1564+
return distribution.versionAndFeatures;
15421565

15431566
default:
15441567
// We should not get here because if no distributions are available, then
@@ -1755,4 +1778,8 @@ export class CliVersionConstraint {
17551778
CliVersionConstraint.CLI_VERSION_WITH_EXTENSIBLE_PREDICATE_METADATA,
17561779
);
17571780
}
1781+
1782+
async supportsMrvaPackCreate(): Promise<boolean> {
1783+
return (await this.cli.getFeatures()).mrvaPackCreate === true;
1784+
}
17581785
}

extensions/ql-vscode/src/codeql-cli/distribution.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { createWriteStream, mkdtemp, pathExists, remove } from "fs-extra";
22
import { tmpdir } from "os";
33
import { delimiter, dirname, join } from "path";
4-
import type { SemVer } from "semver";
54
import { Range, satisfies } from "semver";
65
import type { Event, ExtensionContext } from "vscode";
76
import type { DistributionConfig } from "../config";
87
import { extLogger } from "../common/logging/vscode";
8+
import type { VersionAndFeatures } from "./cli-version";
99
import { getCodeQlCliVersion } from "./cli-version";
1010
import type { ProgressCallback } from "../common/vscode/progress";
1111
import { reportStreamProgress } from "../common/vscode/progress";
@@ -88,11 +88,11 @@ export class DistributionManager implements DistributionProvider {
8888
kind: FindDistributionResultKind.NoDistribution,
8989
};
9090
}
91-
const version = await getCodeQlCliVersion(
91+
const versionAndFeatures = await getCodeQlCliVersion(
9292
distribution.codeQlPath,
9393
extLogger,
9494
);
95-
if (version === undefined) {
95+
if (versionAndFeatures === undefined) {
9696
return {
9797
distribution,
9898
kind: FindDistributionResultKind.UnknownCompatibilityDistribution,
@@ -119,17 +119,21 @@ export class DistributionManager implements DistributionProvider {
119119
distribution.kind !== DistributionKind.ExtensionManaged ||
120120
this.config.includePrerelease;
121121

122-
if (!satisfies(version, this.versionRange, { includePrerelease })) {
122+
if (
123+
!satisfies(versionAndFeatures.version, this.versionRange, {
124+
includePrerelease,
125+
})
126+
) {
123127
return {
124128
distribution,
125129
kind: FindDistributionResultKind.IncompatibleDistribution,
126-
version,
130+
versionAndFeatures,
127131
};
128132
}
129133
return {
130134
distribution,
131135
kind: FindDistributionResultKind.CompatibleDistribution,
132-
version,
136+
versionAndFeatures,
133137
};
134138
}
135139

@@ -599,7 +603,7 @@ interface DistributionResult {
599603

600604
interface CompatibleDistributionResult extends DistributionResult {
601605
kind: FindDistributionResultKind.CompatibleDistribution;
602-
version: SemVer;
606+
versionAndFeatures: VersionAndFeatures;
603607
}
604608

605609
interface UnknownCompatibilityDistributionResult extends DistributionResult {
@@ -608,7 +612,7 @@ interface UnknownCompatibilityDistributionResult extends DistributionResult {
608612

609613
interface IncompatibleDistributionResult extends DistributionResult {
610614
kind: FindDistributionResultKind.IncompatibleDistribution;
611-
version: SemVer;
615+
versionAndFeatures: VersionAndFeatures;
612616
}
613617

614618
interface NoDistributionResult {

0 commit comments

Comments
 (0)