Run pack bundle in separate process and allow cancelling it
This commit is contained in:
@@ -36,6 +36,7 @@ import type { Position } from "../query-server/messages";
|
|||||||
import { LOGGING_FLAGS } from "./cli-command";
|
import { LOGGING_FLAGS } from "./cli-command";
|
||||||
import type { CliFeatures, VersionAndFeatures } from "./cli-version";
|
import type { CliFeatures, VersionAndFeatures } from "./cli-version";
|
||||||
import { ExitCodeError, getCliError } from "./cli-errors";
|
import { ExitCodeError, getCliError } from "./cli-errors";
|
||||||
|
import { UserCancellationException } from "../common/vscode/progress";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the SARIF format that we are using.
|
* The version of the SARIF format that we are using.
|
||||||
@@ -230,6 +231,15 @@ type RunOptions = {
|
|||||||
* If true, don't print logs to the CodeQL extension log.
|
* If true, don't print logs to the CodeQL extension log.
|
||||||
*/
|
*/
|
||||||
silent?: boolean;
|
silent?: boolean;
|
||||||
|
/**
|
||||||
|
* If true, run this command in a new process rather than in the CLI server.
|
||||||
|
*/
|
||||||
|
runInNewProcess?: boolean;
|
||||||
|
/**
|
||||||
|
* If runInNewProcess is true, allows cancelling the command. If runInNewProcess
|
||||||
|
* is false or not specified, this option is ignored.
|
||||||
|
*/
|
||||||
|
token?: CancellationToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
type JsonRunOptions = RunOptions & {
|
type JsonRunOptions = RunOptions & {
|
||||||
@@ -438,6 +448,67 @@ export class CodeQLCliServer implements Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async runCodeQlCliInNewProcess(
|
||||||
|
command: string[],
|
||||||
|
commandArgs: string[],
|
||||||
|
description: string,
|
||||||
|
onLine?: OnLineCallback,
|
||||||
|
silent?: boolean,
|
||||||
|
token?: CancellationToken,
|
||||||
|
): Promise<string> {
|
||||||
|
const codeqlPath = await this.getCodeQlPath();
|
||||||
|
|
||||||
|
const args = command.concat(LOGGING_FLAGS).concat(commandArgs);
|
||||||
|
const argsString = args.join(" ");
|
||||||
|
|
||||||
|
// If we are running silently, we don't want to print anything to the console.
|
||||||
|
if (!silent) {
|
||||||
|
void this.logger.log(`${description} using CodeQL CLI: ${argsString}...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const process = spawnChildProcess(codeqlPath, args);
|
||||||
|
if (!process || !process.pid) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to start ${description} using command ${codeqlPath} ${argsString}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancellationRegistration: Disposable | undefined = undefined;
|
||||||
|
try {
|
||||||
|
cancellationRegistration = token?.onCancellationRequested((_e) => {
|
||||||
|
tk(process.pid || 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
return await this.handleProcessOutput(process, {
|
||||||
|
handleNullTerminator: true,
|
||||||
|
onListenStart: (process) => {
|
||||||
|
// Write the command followed by a null terminator.
|
||||||
|
process.stdin.write(JSON.stringify(args), "utf8");
|
||||||
|
process.stdin.write(this.nullBuffer);
|
||||||
|
},
|
||||||
|
description,
|
||||||
|
args,
|
||||||
|
silent,
|
||||||
|
onLine,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// If cancellation was requested, the error is probably just because the process was exited with SIGTERM.
|
||||||
|
if (token?.isCancellationRequested) {
|
||||||
|
void this.logger.log(
|
||||||
|
`The process was cancelled and exited with: ${getErrorMessage(e)}`,
|
||||||
|
);
|
||||||
|
throw new UserCancellationException(
|
||||||
|
`Command ${argsString} was cancelled.`,
|
||||||
|
true, // Don't show a warning message when the user manually cancelled the command.
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
cancellationRegistration?.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async handleProcessOutput(
|
private async handleProcessOutput(
|
||||||
process: ChildProcessWithoutNullStreams,
|
process: ChildProcessWithoutNullStreams,
|
||||||
{
|
{
|
||||||
@@ -714,18 +785,38 @@ export class CodeQLCliServer implements Disposable {
|
|||||||
* @param progressReporter Used to output progress messages, e.g. to the status bar.
|
* @param progressReporter Used to output progress messages, e.g. to the status bar.
|
||||||
* @param onLine Used for responding to interactive output on stdout/stdin.
|
* @param onLine Used for responding to interactive output on stdout/stdin.
|
||||||
* @param silent If true, don't print logs to the CodeQL extension log.
|
* @param silent If true, don't print logs to the CodeQL extension log.
|
||||||
|
* @param runInNewProcess If true, run this command in a new process rather than in the CLI server.
|
||||||
|
* @param token If runInNewProcess is true, allows cancelling the command. If runInNewProcess
|
||||||
|
* is false or not specified, this option is ignored.
|
||||||
* @returns The contents of the command's stdout, if the command succeeded.
|
* @returns The contents of the command's stdout, if the command succeeded.
|
||||||
*/
|
*/
|
||||||
runCodeQlCliCommand(
|
runCodeQlCliCommand(
|
||||||
command: string[],
|
command: string[],
|
||||||
commandArgs: string[],
|
commandArgs: string[],
|
||||||
description: string,
|
description: string,
|
||||||
{ progressReporter, onLine, silent = false }: RunOptions = {},
|
{
|
||||||
|
progressReporter,
|
||||||
|
onLine,
|
||||||
|
silent = false,
|
||||||
|
runInNewProcess = false,
|
||||||
|
token,
|
||||||
|
}: RunOptions = {},
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (progressReporter) {
|
if (progressReporter) {
|
||||||
progressReporter.report({ message: description });
|
progressReporter.report({ message: description });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (runInNewProcess) {
|
||||||
|
return this.runCodeQlCliInNewProcess(
|
||||||
|
command,
|
||||||
|
commandArgs,
|
||||||
|
description,
|
||||||
|
onLine,
|
||||||
|
silent,
|
||||||
|
token,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// Construct the command that actually does the work
|
// Construct the command that actually does the work
|
||||||
const callback = (): void => {
|
const callback = (): void => {
|
||||||
@@ -1537,6 +1628,7 @@ export class CodeQLCliServer implements Disposable {
|
|||||||
* @param outputBundleFile The path to the output bundle file.
|
* @param outputBundleFile The path to the output bundle file.
|
||||||
* @param outputPackDir The directory to contain the unbundled output pack.
|
* @param outputPackDir The directory to contain the unbundled output pack.
|
||||||
* @param moreOptions Additional options to be passed to `codeql pack bundle`.
|
* @param moreOptions Additional options to be passed to `codeql pack bundle`.
|
||||||
|
* @param token Cancellation token for the operation.
|
||||||
*/
|
*/
|
||||||
async packBundle(
|
async packBundle(
|
||||||
sourcePackDir: string,
|
sourcePackDir: string,
|
||||||
@@ -1544,6 +1636,7 @@ export class CodeQLCliServer implements Disposable {
|
|||||||
outputBundleFile: string,
|
outputBundleFile: string,
|
||||||
outputPackDir: string,
|
outputPackDir: string,
|
||||||
moreOptions: string[],
|
moreOptions: string[],
|
||||||
|
token?: CancellationToken,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const args = [
|
const args = [
|
||||||
"-o",
|
"-o",
|
||||||
@@ -1559,6 +1652,10 @@ export class CodeQLCliServer implements Disposable {
|
|||||||
["pack", "bundle"],
|
["pack", "bundle"],
|
||||||
args,
|
args,
|
||||||
"Bundling pack",
|
"Bundling pack",
|
||||||
|
{
|
||||||
|
runInNewProcess: true,
|
||||||
|
token,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export class ModelEvaluator extends DisposableObject {
|
|||||||
),
|
),
|
||||||
{
|
{
|
||||||
title: "Run model evaluation",
|
title: "Run model evaluation",
|
||||||
cancellable: false,
|
cancellable: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ async function generateQueryPack(
|
|||||||
cliServer: CodeQLCliServer,
|
cliServer: CodeQLCliServer,
|
||||||
qlPackDetails: QlPackDetails,
|
qlPackDetails: QlPackDetails,
|
||||||
tmpDir: RemoteQueryTempDir,
|
tmpDir: RemoteQueryTempDir,
|
||||||
|
token: CancellationToken,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const workspaceFolders = getOnDiskWorkspaceFolders();
|
const workspaceFolders = getOnDiskWorkspaceFolders();
|
||||||
const extensionPacks = await getExtensionPacksToInject(
|
const extensionPacks = await getExtensionPacksToInject(
|
||||||
@@ -148,6 +149,7 @@ async function generateQueryPack(
|
|||||||
bundlePath,
|
bundlePath,
|
||||||
tmpDir.compiledPackDir,
|
tmpDir.compiledPackDir,
|
||||||
precompilationOpts,
|
precompilationOpts,
|
||||||
|
token,
|
||||||
);
|
);
|
||||||
const base64Pack = (await readFile(bundlePath)).toString("base64");
|
const base64Pack = (await readFile(bundlePath)).toString("base64");
|
||||||
return base64Pack;
|
return base64Pack;
|
||||||
@@ -331,7 +333,12 @@ export async function prepareRemoteQueryRun(
|
|||||||
let base64Pack: string;
|
let base64Pack: string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
base64Pack = await generateQueryPack(cliServer, qlPackDetails, tempDir);
|
base64Pack = await generateQueryPack(
|
||||||
|
cliServer,
|
||||||
|
qlPackDetails,
|
||||||
|
tempDir,
|
||||||
|
token,
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
await tempDir.remoteQueryDir.cleanup();
|
await tempDir.remoteQueryDir.cleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,42 +221,48 @@ export class VariantAnalysisManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async runVariantAnalysisFromPublishedPack(): Promise<void> {
|
public async runVariantAnalysisFromPublishedPack(): Promise<void> {
|
||||||
return withProgress(async (progress, token) => {
|
return withProgress(
|
||||||
progress({
|
async (progress, token) => {
|
||||||
maxStep: 7,
|
progress({
|
||||||
step: 0,
|
maxStep: 7,
|
||||||
message: "Determining query language",
|
step: 0,
|
||||||
});
|
message: "Determining query language",
|
||||||
|
});
|
||||||
|
|
||||||
const language = await askForLanguage(this.cliServer);
|
const language = await askForLanguage(this.cliServer);
|
||||||
if (!language) {
|
if (!language) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
progress({
|
progress({
|
||||||
maxStep: 7,
|
maxStep: 7,
|
||||||
step: 2,
|
step: 2,
|
||||||
message: "Downloading query pack and resolving queries",
|
message: "Downloading query pack and resolving queries",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build up details to pass to the functions that run the variant analysis.
|
// Build up details to pass to the functions that run the variant analysis.
|
||||||
const qlPackDetails = await resolveCodeScanningQueryPack(
|
const qlPackDetails = await resolveCodeScanningQueryPack(
|
||||||
this.app.logger,
|
this.app.logger,
|
||||||
this.cliServer,
|
this.cliServer,
|
||||||
language,
|
language,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.runVariantAnalysis(
|
await this.runVariantAnalysis(
|
||||||
qlPackDetails,
|
qlPackDetails,
|
||||||
(p) =>
|
(p) =>
|
||||||
progress({
|
progress({
|
||||||
...p,
|
...p,
|
||||||
maxStep: p.maxStep + 3,
|
maxStep: p.maxStep + 3,
|
||||||
step: p.step + 3,
|
step: p.step + 3,
|
||||||
}),
|
}),
|
||||||
token,
|
token,
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
title: "Run Variant Analysis",
|
||||||
|
cancellable: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async runVariantAnalysisCommand(queryFiles: Uri[]): Promise<void> {
|
private async runVariantAnalysisCommand(queryFiles: Uri[]): Promise<void> {
|
||||||
|
|||||||
Reference in New Issue
Block a user