Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions extensions/ql-vscode/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [UNRELEASED]

- Fix a bug where the results view moved column even when it was already visible. [#1070](https://github.com/github/vscode-codeql/pull/1070)
- Add packaging-related commands. _CodeQL: Download Packs_ downloads packs from the package registry, and _CodeQL: Install Packs_ installs dependencies for packs in your workspace. [#1076](https://github.com/github/vscode-codeql/pull/1076)
Comment thread
shati-patel marked this conversation as resolved.
Outdated

## 1.5.9 - 17 December 2021

Expand Down
8 changes: 8 additions & 0 deletions extensions/ql-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,14 @@
"command": "codeQL.clearCache",
"title": "CodeQL: Clear Cache"
},
{
"command": "codeQL.installPacks",
"title": "CodeQL: Install Packs"
Comment thread
shati-patel marked this conversation as resolved.
Outdated
},
{
"command": "codeQL.downloadPacks",
"title": "CodeQL: Download Packs"
},
{
"command": "codeQLDatabases.setCurrentDatabase",
"title": "Set Current Database"
Expand Down
8 changes: 8 additions & 0 deletions extensions/ql-vscode/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,14 @@ export class CodeQLCliServer implements Disposable {
);
}

/**
* Downloads a specified pack.
* @param packs The `<package-scope/name[@version]>` of the packs to download.
*/
async packDownload(packs: string[]) {
return this.runJsonCodeQlCliCommand(['pack', 'download'], packs, 'Downloading packs');
}

async packInstall(dir: string) {
return this.runJsonCodeQlCliCommand(['pack', 'install'], [dir], 'Installing pack dependencies');
}
Expand Down
21 changes: 21 additions & 0 deletions extensions/ql-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import { RemoteQuery } from './remote-queries/remote-query';
import { URLSearchParams } from 'url';
import { RemoteQueriesInterfaceManager } from './remote-queries/remote-queries-interface';
import { sampleRemoteQuery, sampleRemoteQueryResult } from './remote-queries/sample-data';
import { handleDownloadPacks, handleInstallPacks } from './packaging';

/**
* extension.ts
Expand Down Expand Up @@ -922,6 +923,26 @@ async function activateWithInstalledDistribution(
}
}));

ctx.subscriptions.push(
commandRunnerWithProgress('codeQL.installPacks', async (
progress: ProgressCallback
) =>
await handleInstallPacks(cliServer, progress),
{
title: 'Installing packs',
}
));

ctx.subscriptions.push(
commandRunnerWithProgress('codeQL.downloadPacks', async (
progress: ProgressCallback
) =>
await handleDownloadPacks(cliServer, progress),
{
title: 'Downloading packs',
}
));

commands.registerCommand('codeQL.showLogs', () => {
logger.show();
});
Expand Down
135 changes: 135 additions & 0 deletions extensions/ql-vscode/src/packaging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { CodeQLCliServer } from './cli';
import {
getOnDiskWorkspaceFolders,
showAndLogErrorMessage,
showAndLogInformationMessage,
showAndLogWarningMessage,
} from './helpers';
import { QuickPickItem, window } from 'vscode';
import { ProgressCallback, UserCancellationException } from './commandRunner';
import { logger } from './logging';

const QUERY_PACKS = [
'codeql/cpp-queries',
'codeql/csharp-queries',
'codeql/go-queries',
'codeql/java-queries',
'codeql/javascript-queries',
'codeql/python-queries',
'codeql/ruby-queries',
'codeql/csharp-solorigate-queries',
'codeql/javascript-experimental-atm-queries',
];

/**
* Prompts user to choose packs to download, and downloads them.
*
* @param cliServer The CLI server.
* @param progress A progress callback.
*/
export async function handleDownloadPacks(
cliServer: CodeQLCliServer,
progress: ProgressCallback,
): Promise<void> {
progress({
message: 'Choose packs to download',
step: 1,
maxStep: 2,
});
let packsToDownload: string[] = [];
const queryPackOption = 'Download all core query packs';
const customPackOption = 'Download custom specified pack';
const quickpick = await window.showQuickPick(
[queryPackOption, customPackOption],
{ ignoreFocusOut: true }
);
if (quickpick === queryPackOption) {
packsToDownload = QUERY_PACKS;
} else if (quickpick === customPackOption) {
const customPack = await window.showInputBox({
prompt:
'Enter the <package-scope/name[@version]> of the pack to download',
ignoreFocusOut: true,
});
if (customPack) {
packsToDownload.push(customPack);
} else {
throw new UserCancellationException('No pack specified.');
}
}
if (packsToDownload?.length > 0) {
progress({
message: 'Downloading packs. This may take a few minutes.',
step: 2,
maxStep: 2,
});
try {
await cliServer.packDownload(packsToDownload);
void showAndLogInformationMessage('Finished downloading packs.');
} catch (error) {
void showAndLogErrorMessage(
'Unable to download all packs. See logs for more details.'
);
}
}
}

interface QLPackQuickPickItem extends QuickPickItem {
packRootDir: string[];
}

/**
* Prompts user to choose packs to install, and installs them.
*
* @param cliServer The CLI server.
* @param progress A progress callback.
*/
export async function handleInstallPacks(
cliServer: CodeQLCliServer,
progress: ProgressCallback,
): Promise<void> {
progress({
message: 'Choose packs to install',
step: 1,
maxStep: 2,
});
const workspacePacks = await cliServer.resolveQlpacks(getOnDiskWorkspaceFolders());
Comment thread
shati-patel marked this conversation as resolved.
const quickPickItems = Object.entries(workspacePacks).map<QLPackQuickPickItem>(([key, value]) => ({
label: key,
packRootDir: value,
}));
const packsToInstall = await window.showQuickPick(quickPickItems, {
placeHolder: 'Select packs to install',
Comment thread
shati-patel marked this conversation as resolved.
Outdated
canPickMany: true,
ignoreFocusOut: true,
});
if (packsToInstall && packsToInstall.length > 0) {
progress({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: You know the total number of root packs, so you could make the progress monitor actually reflect the progress through the root packs. Save that for a future PR though :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! I will try that in a follow-up PR 🔄

message: 'Installing packs. This may take a few minutes.',
Comment thread
shati-patel marked this conversation as resolved.
Outdated
step: 2,
maxStep: 2,
});
const failedPacks = [];
const errors = [];
for (const pack of packsToInstall) {
try {
for (const dir of pack.packRootDir) {
await cliServer.packInstall(dir);
}
} catch (error) {
failedPacks.push(pack.label);
errors.push(error);
}
}
if (failedPacks.length > 0) {
void logger.log(`Errors:\n${errors.join('\n')}`);
void showAndLogWarningMessage(
`Unable to install some packs: ${failedPacks.join(', ')}. See logs for more details.`
);
Comment thread
shati-patel marked this conversation as resolved.
} else {
void showAndLogInformationMessage('Finished installing packs.');
}
} else {
throw new UserCancellationException('No packs selected.');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import * as sinon from 'sinon';
import { extensions, window } from 'vscode';
import 'mocha';
import * as path from 'path';

import * as pq from 'proxyquire';

import { CodeQLCliServer } from '../../cli';
import { CodeQLExtensionInterface } from '../../extension';
import { expect } from 'chai';

const proxyquire = pq.noPreserveCache();

describe('Packaging commands', function() {
let sandbox: sinon.SinonSandbox;

// up to 3 minutes per test
this.timeout(3 * 60 * 1000);

let cli: CodeQLCliServer;
let progress: sinon.SinonSpy;
let quickPickSpy: sinon.SinonStub;
let inputBoxSpy: sinon.SinonStub;
let showAndLogErrorMessageSpy: sinon.SinonStub;
let showAndLogInformationMessageSpy: sinon.SinonStub;
let mod: any;

beforeEach(async function() {
sandbox = sinon.createSandbox();

const extension = await extensions
.getExtension<CodeQLExtensionInterface | Record<string, never>>(
'GitHub.vscode-codeql'
)!
.activate();
if ('cliServer' in extension) {
cli = extension.cliServer;
} else {
throw new Error(
'Extension not initialized. Make sure cli is downloaded and installed properly.'
);
}

progress = sandbox.spy();
quickPickSpy = sandbox.stub(window, 'showQuickPick');
inputBoxSpy = sandbox.stub(window, 'showInputBox');
showAndLogErrorMessageSpy = sandbox.stub();
showAndLogInformationMessageSpy = sandbox.stub();
mod = proxyquire('../../packaging', {
'../helpers': {
showAndLogErrorMessage: showAndLogErrorMessageSpy,
showAndLogInformationMessage: showAndLogInformationMessageSpy,
},
});
});

afterEach(() => {
sandbox.restore();
});

it('should download all core query packs', async () => {
quickPickSpy.resolves('Download all core query packs');

await mod.handleDownloadPacks(cli, progress);
expect(showAndLogInformationMessageSpy.firstCall.args[0]).to.contain(
'Finished downloading packs.'
);
});

it('should download valid user-specified pack', async () => {
quickPickSpy.resolves('Download custom specified pack');
inputBoxSpy.resolves('codeql/csharp-solorigate-queries');

await mod.handleDownloadPacks(cli, progress);
expect(showAndLogInformationMessageSpy.firstCall.args[0]).to.contain(
'Finished downloading packs.'
);
});

it('should show error for invalid user-specified pack', async () => {
quickPickSpy.resolves('Download custom specified pack');
inputBoxSpy.resolves('foo/not-a-real-pack@0.0.1');

await mod.handleDownloadPacks(cli, progress);

expect(showAndLogErrorMessageSpy.firstCall.args[0]).to.contain(
'Unable to download all packs.'
);
});

it('should install selected workspace packs', async () => {
const rootDir = path.join(__dirname, '../../../src/vscode-tests/cli-integration/data');
quickPickSpy.resolves(
[
{
label: 'integration-test-queries-javascript',
packRootDir: [rootDir],
},
]
);

await mod.handleInstallPacks(cli, progress);

expect(showAndLogInformationMessageSpy.firstCall.args[0]).to.contain(
'Finished installing packs.'
);
});
});