@@ -36,6 +36,7 @@ import type { Position } from "../query-server/messages";
3636import { LOGGING_FLAGS } from "./cli-command" ;
3737import type { CliFeatures , VersionAndFeatures } from "./cli-version" ;
3838import { ExitCodeError , getCliError } from "./cli-errors" ;
39+ import { UserCancellationException } from "../common/vscode/progress" ;
3940
4041/**
4142 * The version of the SARIF format that we are using.
@@ -230,6 +231,15 @@ type RunOptions = {
230231 * If true, don't print logs to the CodeQL extension log.
231232 */
232233 silent ?: boolean ;
234+ /**
235+ * If true, run this command in a new process rather than in the CLI server.
236+ */
237+ runInNewProcess ?: boolean ;
238+ /**
239+ * If runInNewProcess is true, allows cancelling the command. If runInNewProcess
240+ * is false or not specified, this option is ignored.
241+ */
242+ token ?: CancellationToken ;
233243} ;
234244
235245type JsonRunOptions = RunOptions & {
@@ -438,6 +448,67 @@ export class CodeQLCliServer implements Disposable {
438448 }
439449 }
440450
451+ private async runCodeQlCliInNewProcess (
452+ command : string [ ] ,
453+ commandArgs : string [ ] ,
454+ description : string ,
455+ onLine ?: OnLineCallback ,
456+ silent ?: boolean ,
457+ token ?: CancellationToken ,
458+ ) : Promise < string > {
459+ const codeqlPath = await this . getCodeQlPath ( ) ;
460+
461+ const args = command . concat ( LOGGING_FLAGS ) . concat ( commandArgs ) ;
462+ const argsString = args . join ( " " ) ;
463+
464+ // If we are running silently, we don't want to print anything to the console.
465+ if ( ! silent ) {
466+ void this . logger . log ( `${ description } using CodeQL CLI: ${ argsString } ...` ) ;
467+ }
468+
469+ const process = spawnChildProcess ( codeqlPath , args ) ;
470+ if ( ! process || ! process . pid ) {
471+ throw new Error (
472+ `Failed to start ${ description } using command ${ codeqlPath } ${ argsString } .` ,
473+ ) ;
474+ }
475+
476+ let cancellationRegistration : Disposable | undefined = undefined ;
477+ try {
478+ cancellationRegistration = token ?. onCancellationRequested ( ( _e ) => {
479+ tk ( process . pid || 0 ) ;
480+ } ) ;
481+
482+ return await this . handleProcessOutput ( process , {
483+ handleNullTerminator : true ,
484+ onListenStart : ( process ) => {
485+ // Write the command followed by a null terminator.
486+ process . stdin . write ( JSON . stringify ( args ) , "utf8" ) ;
487+ process . stdin . write ( this . nullBuffer ) ;
488+ } ,
489+ description,
490+ args,
491+ silent,
492+ onLine,
493+ } ) ;
494+ } catch ( e ) {
495+ // If cancellation was requested, the error is probably just because the process was exited with SIGTERM.
496+ if ( token ?. isCancellationRequested ) {
497+ void this . logger . log (
498+ `The process was cancelled and exited with: ${ getErrorMessage ( e ) } ` ,
499+ ) ;
500+ throw new UserCancellationException (
501+ `Command ${ argsString } was cancelled.` ,
502+ true , // Don't show a warning message when the user manually cancelled the command.
503+ ) ;
504+ }
505+
506+ throw e ;
507+ } finally {
508+ cancellationRegistration ?. dispose ( ) ;
509+ }
510+ }
511+
441512 private async handleProcessOutput (
442513 process : ChildProcessWithoutNullStreams ,
443514 {
@@ -714,18 +785,38 @@ export class CodeQLCliServer implements Disposable {
714785 * @param progressReporter Used to output progress messages, e.g. to the status bar.
715786 * @param onLine Used for responding to interactive output on stdout/stdin.
716787 * @param silent If true, don't print logs to the CodeQL extension log.
788+ * @param runInNewProcess If true, run this command in a new process rather than in the CLI server.
789+ * @param token If runInNewProcess is true, allows cancelling the command. If runInNewProcess
790+ * is false or not specified, this option is ignored.
717791 * @returns The contents of the command's stdout, if the command succeeded.
718792 */
719793 runCodeQlCliCommand (
720794 command : string [ ] ,
721795 commandArgs : string [ ] ,
722796 description : string ,
723- { progressReporter, onLine, silent = false } : RunOptions = { } ,
797+ {
798+ progressReporter,
799+ onLine,
800+ silent = false ,
801+ runInNewProcess = false ,
802+ token,
803+ } : RunOptions = { } ,
724804 ) : Promise < string > {
725805 if ( progressReporter ) {
726806 progressReporter . report ( { message : description } ) ;
727807 }
728808
809+ if ( runInNewProcess ) {
810+ return this . runCodeQlCliInNewProcess (
811+ command ,
812+ commandArgs ,
813+ description ,
814+ onLine ,
815+ silent ,
816+ token ,
817+ ) ;
818+ }
819+
729820 return new Promise ( ( resolve , reject ) => {
730821 // Construct the command that actually does the work
731822 const callback = ( ) : void => {
@@ -1537,13 +1628,15 @@ export class CodeQLCliServer implements Disposable {
15371628 * @param outputBundleFile The path to the output bundle file.
15381629 * @param outputPackDir The directory to contain the unbundled output pack.
15391630 * @param moreOptions Additional options to be passed to `codeql pack bundle`.
1631+ * @param token Cancellation token for the operation.
15401632 */
15411633 async packBundle (
15421634 sourcePackDir : string ,
15431635 workspaceFolders : string [ ] ,
15441636 outputBundleFile : string ,
15451637 outputPackDir : string ,
15461638 moreOptions : string [ ] ,
1639+ token ?: CancellationToken ,
15471640 ) : Promise < void > {
15481641 const args = [
15491642 "-o" ,
@@ -1559,6 +1652,10 @@ export class CodeQLCliServer implements Disposable {
15591652 [ "pack" , "bundle" ] ,
15601653 args ,
15611654 "Bundling pack" ,
1655+ {
1656+ runInNewProcess : true ,
1657+ token,
1658+ } ,
15621659 ) ;
15631660 }
15641661
0 commit comments