Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "pack install" and "pack download" commands #1076

Merged
merged 10 commits into from
Jan 19, 2022
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)
shati-patel marked this conversation as resolved.
Show resolved Hide resolved

## 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"
shati-patel marked this conversation as resolved.
Show resolved Hide resolved
},
{
"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
134 changes: 134 additions & 0 deletions extensions/ql-vscode/src/packaging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { CodeQLCliServer } from './cli';
import {
getOnDiskWorkspaceFolders,
showAndLogErrorMessage,
showAndLogInformationMessage,
} 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());
shati-patel marked this conversation as resolved.
Show resolved Hide 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',
shati-patel marked this conversation as resolved.
Show resolved Hide resolved
canPickMany: true,
ignoreFocusOut: true,
});
if (packsToInstall && packsToInstall.length > 0) {
progress({
Copy link
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
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.',
shati-patel marked this conversation as resolved.
Show resolved Hide resolved
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')}`);
throw new Error(
`Unable to install packs: ${failedPacks.join(', ')}. See logs for more details.`
shati-patel marked this conversation as resolved.
Show resolved Hide 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,109 @@
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/[email protected]');

await mod.handleDownloadPacks(cli, progress);

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

it('should attempt to 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],
},
]);

try {
await mod.handleInstallPacks(cli, progress);
expect(showAndLogInformationMessageSpy.firstCall.args[0]).to.contain(
'Finished installing packs.'
);
} catch (error) {
expect(error.message).to.contain('Unable to install packs:');
}
shati-patel marked this conversation as resolved.
Show resolved Hide resolved
});
});