diff --git a/extensions/ql-vscode/src/cli.ts b/extensions/ql-vscode/src/cli.ts index a1f5c1247..adfa30177 100644 --- a/extensions/ql-vscode/src/cli.ts +++ b/extensions/ql-vscode/src/cli.ts @@ -4,15 +4,16 @@ import * as child_process from 'child_process'; import * as fs from 'fs-extra'; import * as path from 'path'; import * as sarif from 'sarif'; +import { SemVer } from 'semver'; import { Readable } from 'stream'; import { StringDecoder } from 'string_decoder'; import * as tk from 'tree-kill'; -import * as util from 'util'; +import { promisify } from 'util'; import { CancellationToken, Disposable } from 'vscode'; import { BQRSInfo, DecodedBqrsChunk } from './bqrs-cli-types'; import { CliConfig } from './config'; -import { DistributionProvider } from './distribution'; +import { DistributionProvider, FindDistributionResultKind } from './distribution'; import { assertNever } from './helpers-pure'; import { QueryMetadata, SortDirection } from './interface-types'; import { Logger, ProgressReporter } from './logging'; @@ -115,6 +116,11 @@ interface BqrsDecodeOptions { */ export class CodeQLCliServer implements Disposable { + /** + * CLI version where --kind=DIL was introduced + */ + private static CLI_VERSION_WITH_DECOMPILE_KIND_DIL = new SemVer('2.3.0'); + /** The process for the cli server, or undefined if one doesn't exist yet */ process?: child_process.ChildProcessWithoutNullStreams; /** Queue of future commands*/ @@ -124,6 +130,12 @@ export class CodeQLCliServer implements Disposable { /** A buffer with a single null byte. */ nullBuffer: Buffer; + /** Version of current cli, lazily computed by the `getVersion()` method */ + _version: SemVer | undefined; + + /** Path to current codeQL executable, or undefined if not running yet. */ + codeQlPath: string | undefined; + constructor( private distributionProvider: DistributionProvider, private cliConfig: CliConfig, @@ -140,11 +152,11 @@ export class CodeQLCliServer implements Disposable { if (this.cliConfig.onDidChangeConfiguration) { this.cliConfig.onDidChangeConfiguration(() => { this.restartCliServer(); + this._version = undefined; }); } } - dispose(): void { this.killProcessIfRunning(); } @@ -210,9 +222,9 @@ export class CodeQLCliServer implements Disposable { * Launch the cli server */ private async launchProcess(): Promise { - const config = await this.getCodeQlPath(); - return spawnServer( - config, + const codeQlPath = await this.getCodeQlPath(); + return await spawnServer( + codeQlPath, 'CodeQL CLI Server', ['execute', 'cli-server'], [], @@ -671,12 +683,37 @@ export class CodeQLCliServer implements Disposable { } async generateDil(qloFile: string, outFile: string): Promise { + const extraArgs = (await this.getVersion()).compare(CodeQLCliServer.CLI_VERSION_WITH_DECOMPILE_KIND_DIL) >= 0 + ? ['--kind', 'dil', '-o', outFile, qloFile] + : ['-o', outFile, qloFile]; await this.runCodeQlCliCommand( ['query', 'decompile'], - ['-o', outFile, qloFile], + extraArgs, 'Generating DIL', ); } + + private async getVersion() { + if (!this._version) { + this._version = await this.refreshVersion(); + } + return this._version; + } + + private async refreshVersion() { + const distribution = await this.distributionProvider.getDistribution(); + switch(distribution.kind) { + case FindDistributionResultKind.CompatibleDistribution: + // eslint-disable-next-line no-fallthrough + case FindDistributionResultKind.IncompatibleDistribution: + return distribution.version; + + default: + // We should not get here because if no distributions are available, then + // the cli class is never instantiated. + throw new Error('No distribution found'); + } + } } /** @@ -734,7 +771,7 @@ export function spawnServer( /** * Runs a CodeQL CLI command without invoking the CLI server, returning the output as a string. - * @param config The configuration containing the path to the CLI. + * @param codeQlPath The path to the CLI. * @param command The `codeql` command to be run, provided as an array of command/subcommand names. * @param commandArgs The arguments to pass to the `codeql` command. * @param description Description of the action being run, to be shown in log and error messages. @@ -742,7 +779,14 @@ export function spawnServer( * @param progressReporter Used to output progress messages, e.g. to the status bar. * @returns The contents of the command's stdout, if the command succeeded. */ -export async function runCodeQlCliCommand(codeQlPath: string, command: string[], commandArgs: string[], description: string, logger: Logger, progressReporter?: ProgressReporter): Promise { +export async function runCodeQlCliCommand( + codeQlPath: string, + command: string[], + commandArgs: string[], + description: string, + logger: Logger, + progressReporter?: ProgressReporter +): Promise { // Add logging arguments first, in case commandArgs contains positional parameters. const args = command.concat(LOGGING_FLAGS).concat(commandArgs); const argsString = args.join(' '); @@ -751,7 +795,7 @@ export async function runCodeQlCliCommand(codeQlPath: string, command: string[], progressReporter.report({ message: description }); } logger.log(`${description} using CodeQL CLI: ${codeQlPath} ${argsString}...`); - const result = await util.promisify(child_process.execFile)(codeQlPath, args); + const result = await promisify(child_process.execFile)(codeQlPath, args); logger.log(result.stderr); logger.log('CLI command succeeded.'); return result.stdout; diff --git a/extensions/ql-vscode/src/distribution.ts b/extensions/ql-vscode/src/distribution.ts index 40b5426c3..a9d2d6f3a 100644 --- a/extensions/ql-vscode/src/distribution.ts +++ b/extensions/ql-vscode/src/distribution.ts @@ -45,6 +45,7 @@ export const DEFAULT_DISTRIBUTION_VERSION_RANGE: semver.Range = new semver.Range export interface DistributionProvider { getCodeQlPathWithoutVersionCheck(): Promise; onDidChangeDistribution?: Event; + getDistribution(): Promise; } export class DistributionManager implements DistributionProvider { diff --git a/extensions/ql-vscode/test/pure-tests/query-test.ts b/extensions/ql-vscode/src/vscode-tests/no-workspace/query-test.ts similarity index 93% rename from extensions/ql-vscode/test/pure-tests/query-test.ts rename to extensions/ql-vscode/src/vscode-tests/no-workspace/query-test.ts index 52d9d4348..343178f77 100644 --- a/extensions/ql-vscode/test/pure-tests/query-test.ts +++ b/extensions/ql-vscode/src/vscode-tests/no-workspace/query-test.ts @@ -5,11 +5,12 @@ import * as path from 'path'; import * as tmp from 'tmp'; import * as url from 'url'; import { CancellationTokenSource } from 'vscode-jsonrpc'; -import * as messages from '../../src/messages'; -import * as qsClient from '../../src/queryserver-client'; -import * as cli from '../../src/cli'; -import { ProgressReporter, Logger } from '../../src/logging'; -import { ColumnValue } from '../../src/bqrs-cli-types'; +import * as messages from '../../messages'; +import * as qsClient from '../../queryserver-client'; +import * as cli from '../../cli'; +import { ProgressReporter, Logger } from '../../logging'; +import { ColumnValue } from '../../bqrs-cli-types'; +import { FindDistributionResultKind } from '../../distribution'; declare module 'url' { @@ -114,6 +115,11 @@ describe('using the query server', function() { async getCodeQlPathWithoutVersionCheck(): Promise { return codeQlPath; }, + getDistribution: async () => { + return { + kind: FindDistributionResultKind.NoDistribution + }; + } }, { numberTestThreads: 2