Merge pull request #971 from marcnjaramillo/integrate-codeql-database-unbundle
Integrate codeql database unbundle
This commit is contained in:
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
## [UNRELEASED]
|
## [UNRELEASED]
|
||||||
|
|
||||||
- Replace certain control codes (`U+0000` - `U+001F`) with their corresponding control labels (`U+2400` - `U+241F`) in the results view. [#963](https://github.com/github/vscode-codeql/pull/963)
|
- Use the CodeQL CLI command `database unbundle` for the archive download option, which ensures even archives over 4GB in size can be downloaded. [#971](https://github.com/github/vscode-codeql/pull/971)
|
||||||
|
- Fix a bug with importing large databases. Databases over 4GB can now be imported directly from LGTM or from a zip file. This functionality is only available when using CodeQL CLI version 2.6.0 or later. [#963](https://github.com/github/vscode-codeql/pull/963)
|
||||||
|
|
||||||
## 1.5.6 - 07 October 2021
|
## 1.5.6 - 07 October 2021
|
||||||
|
|
||||||
|
|||||||
@@ -600,6 +600,15 @@ export class CodeQLCliServer implements Disposable {
|
|||||||
return await this.runJsonCodeQlCliCommand<BQRSInfo>(['bqrs', 'info'], subcommandArgs, 'Reading bqrs header');
|
return await this.runJsonCodeQlCliCommand<BQRSInfo>(['bqrs', 'info'], subcommandArgs, 'Reading bqrs header');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async databaseUnbundle(archivePath: string, target: string, name?: string): Promise<string> {
|
||||||
|
const subcommandArgs = [];
|
||||||
|
if (target) subcommandArgs.push('--target', target);
|
||||||
|
if (name) subcommandArgs.push('--name', name);
|
||||||
|
subcommandArgs.push(archivePath);
|
||||||
|
|
||||||
|
return await this.runCodeQlCliCommand(['database', 'unbundle'], subcommandArgs, `Extracting ${archivePath} to directory ${target}`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the results from a bqrs.
|
* Gets the results from a bqrs.
|
||||||
* @param bqrsPath The path to the bqrs.
|
* @param bqrsPath The path to the bqrs.
|
||||||
@@ -1074,6 +1083,11 @@ export class CliVersionConstraint {
|
|||||||
*/
|
*/
|
||||||
public static CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES = new SemVer('2.6.1');
|
public static CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES = new SemVer('2.6.1');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLI version where the `database unbundle` subcommand was introduced.
|
||||||
|
*/
|
||||||
|
public static CLI_VERSION_WITH_DATABASE_UNBUNDLE = new SemVer('2.6.0');
|
||||||
|
|
||||||
constructor(private readonly cli: CodeQLCliServer) {
|
constructor(private readonly cli: CodeQLCliServer) {
|
||||||
/**/
|
/**/
|
||||||
}
|
}
|
||||||
@@ -1105,4 +1119,9 @@ export class CliVersionConstraint {
|
|||||||
async supportsDatabaseRegistration() {
|
async supportsDatabaseRegistration() {
|
||||||
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DB_REGISTRATION);
|
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DB_REGISTRATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async supportsDatabaseUnbundle() {
|
||||||
|
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DATABASE_UNBUNDLE);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import fetch, { Response } from 'node-fetch';
|
import fetch, { Response } from 'node-fetch';
|
||||||
import * as unzipper from 'unzipper';
|
|
||||||
import { zip } from 'zip-a-folder';
|
import { zip } from 'zip-a-folder';
|
||||||
|
import * as unzipper from 'unzipper';
|
||||||
import {
|
import {
|
||||||
Uri,
|
Uri,
|
||||||
CancellationToken,
|
CancellationToken,
|
||||||
commands,
|
commands,
|
||||||
window,
|
window,
|
||||||
} from 'vscode';
|
} from 'vscode';
|
||||||
|
import { CodeQLCliServer } from './cli';
|
||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ export async function promptImportInternetDatabase(
|
|||||||
storagePath: string,
|
storagePath: string,
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
token: CancellationToken,
|
token: CancellationToken,
|
||||||
|
cli?: CodeQLCliServer
|
||||||
): Promise<DatabaseItem | undefined> {
|
): Promise<DatabaseItem | undefined> {
|
||||||
const databaseUrl = await window.showInputBox({
|
const databaseUrl = await window.showInputBox({
|
||||||
prompt: 'Enter URL of zipfile of database to download',
|
prompt: 'Enter URL of zipfile of database to download',
|
||||||
@@ -47,7 +49,8 @@ export async function promptImportInternetDatabase(
|
|||||||
databaseManager,
|
databaseManager,
|
||||||
storagePath,
|
storagePath,
|
||||||
progress,
|
progress,
|
||||||
token
|
token,
|
||||||
|
cli
|
||||||
);
|
);
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
@@ -70,7 +73,8 @@ export async function promptImportLgtmDatabase(
|
|||||||
databaseManager: DatabaseManager,
|
databaseManager: DatabaseManager,
|
||||||
storagePath: string,
|
storagePath: string,
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
token: CancellationToken
|
token: CancellationToken,
|
||||||
|
cli?: CodeQLCliServer
|
||||||
): Promise<DatabaseItem | undefined> {
|
): Promise<DatabaseItem | undefined> {
|
||||||
progress({
|
progress({
|
||||||
message: 'Choose project',
|
message: 'Choose project',
|
||||||
@@ -93,7 +97,8 @@ export async function promptImportLgtmDatabase(
|
|||||||
databaseManager,
|
databaseManager,
|
||||||
storagePath,
|
storagePath,
|
||||||
progress,
|
progress,
|
||||||
token
|
token,
|
||||||
|
cli
|
||||||
);
|
);
|
||||||
if (item) {
|
if (item) {
|
||||||
await commands.executeCommand('codeQLDatabases.focus');
|
await commands.executeCommand('codeQLDatabases.focus');
|
||||||
@@ -120,6 +125,7 @@ export async function importArchiveDatabase(
|
|||||||
storagePath: string,
|
storagePath: string,
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
token: CancellationToken,
|
token: CancellationToken,
|
||||||
|
cli?: CodeQLCliServer,
|
||||||
): Promise<DatabaseItem | undefined> {
|
): Promise<DatabaseItem | undefined> {
|
||||||
try {
|
try {
|
||||||
const item = await databaseArchiveFetcher(
|
const item = await databaseArchiveFetcher(
|
||||||
@@ -127,7 +133,8 @@ export async function importArchiveDatabase(
|
|||||||
databaseManager,
|
databaseManager,
|
||||||
storagePath,
|
storagePath,
|
||||||
progress,
|
progress,
|
||||||
token
|
token,
|
||||||
|
cli
|
||||||
);
|
);
|
||||||
if (item) {
|
if (item) {
|
||||||
await commands.executeCommand('codeQLDatabases.focus');
|
await commands.executeCommand('codeQLDatabases.focus');
|
||||||
@@ -159,7 +166,8 @@ async function databaseArchiveFetcher(
|
|||||||
databaseManager: DatabaseManager,
|
databaseManager: DatabaseManager,
|
||||||
storagePath: string,
|
storagePath: string,
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
token: CancellationToken
|
token: CancellationToken,
|
||||||
|
cli?: CodeQLCliServer,
|
||||||
): Promise<DatabaseItem> {
|
): Promise<DatabaseItem> {
|
||||||
progress({
|
progress({
|
||||||
message: 'Getting database',
|
message: 'Getting database',
|
||||||
@@ -173,9 +181,9 @@ async function databaseArchiveFetcher(
|
|||||||
const unzipPath = await getStorageFolder(storagePath, databaseUrl);
|
const unzipPath = await getStorageFolder(storagePath, databaseUrl);
|
||||||
|
|
||||||
if (isFile(databaseUrl)) {
|
if (isFile(databaseUrl)) {
|
||||||
await readAndUnzip(databaseUrl, unzipPath, progress);
|
await readAndUnzip(databaseUrl, unzipPath, cli, progress);
|
||||||
} else {
|
} else {
|
||||||
await fetchAndUnzip(databaseUrl, unzipPath, progress);
|
await fetchAndUnzip(databaseUrl, unzipPath, cli, progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
progress({
|
progress({
|
||||||
@@ -249,6 +257,7 @@ function validateHttpsUrl(databaseUrl: string) {
|
|||||||
async function readAndUnzip(
|
async function readAndUnzip(
|
||||||
zipUrl: string,
|
zipUrl: string,
|
||||||
unzipPath: string,
|
unzipPath: string,
|
||||||
|
cli?: CodeQLCliServer,
|
||||||
progress?: ProgressCallback
|
progress?: ProgressCallback
|
||||||
) {
|
) {
|
||||||
// TODO: Providing progress as the file is unzipped is currently blocked
|
// TODO: Providing progress as the file is unzipped is currently blocked
|
||||||
@@ -259,16 +268,22 @@ async function readAndUnzip(
|
|||||||
step: 9,
|
step: 9,
|
||||||
message: `Unzipping into ${path.basename(unzipPath)}`
|
message: `Unzipping into ${path.basename(unzipPath)}`
|
||||||
});
|
});
|
||||||
// Must get the zip central directory since streaming the
|
if (cli && await cli.cliConstraints.supportsDatabaseUnbundle()) {
|
||||||
// zip contents may not have correct local file headers.
|
// Use the `database unbundle` command if the installed cli version supports it
|
||||||
// Instead, we can only rely on the central directory.
|
await cli.databaseUnbundle(zipFile, unzipPath);
|
||||||
const directory = await unzipper.Open.file(zipFile);
|
} else {
|
||||||
await directory.extract({ path: unzipPath });
|
// Must get the zip central directory since streaming the
|
||||||
|
// zip contents may not have correct local file headers.
|
||||||
|
// Instead, we can only rely on the central directory.
|
||||||
|
const directory = await unzipper.Open.file(zipFile);
|
||||||
|
await directory.extract({ path: unzipPath });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchAndUnzip(
|
async function fetchAndUnzip(
|
||||||
databaseUrl: string,
|
databaseUrl: string,
|
||||||
unzipPath: string,
|
unzipPath: string,
|
||||||
|
cli?: CodeQLCliServer,
|
||||||
progress?: ProgressCallback
|
progress?: ProgressCallback
|
||||||
) {
|
) {
|
||||||
// Although it is possible to download and stream directly to an unzipped directory,
|
// Although it is possible to download and stream directly to an unzipped directory,
|
||||||
@@ -298,7 +313,8 @@ async function fetchAndUnzip(
|
|||||||
.on('error', reject)
|
.on('error', reject)
|
||||||
);
|
);
|
||||||
|
|
||||||
await readAndUnzip(Uri.file(archivePath).toString(true), unzipPath, progress);
|
await readAndUnzip(Uri.file(archivePath).toString(true), unzipPath, cli, progress);
|
||||||
|
|
||||||
|
|
||||||
// remove archivePath eagerly since these archives can be large.
|
// remove archivePath eagerly since these archives can be large.
|
||||||
await fs.remove(archivePath);
|
await fs.remove(archivePath);
|
||||||
|
|||||||
@@ -451,14 +451,13 @@ export class DatabaseUI extends DisposableObject {
|
|||||||
handleChooseDatabaseInternet = async (
|
handleChooseDatabaseInternet = async (
|
||||||
progress: ProgressCallback,
|
progress: ProgressCallback,
|
||||||
token: CancellationToken
|
token: CancellationToken
|
||||||
): Promise<
|
): Promise<DatabaseItem | undefined> => {
|
||||||
DatabaseItem | undefined
|
|
||||||
> => {
|
|
||||||
return await promptImportInternetDatabase(
|
return await promptImportInternetDatabase(
|
||||||
this.databaseManager,
|
this.databaseManager,
|
||||||
this.storagePath,
|
this.storagePath,
|
||||||
progress,
|
progress,
|
||||||
token
|
token,
|
||||||
|
this.queryServer?.cliServer
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -470,7 +469,8 @@ export class DatabaseUI extends DisposableObject {
|
|||||||
this.databaseManager,
|
this.databaseManager,
|
||||||
this.storagePath,
|
this.storagePath,
|
||||||
progress,
|
progress,
|
||||||
token
|
token,
|
||||||
|
this.queryServer?.cliServer
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -580,7 +580,8 @@ export class DatabaseUI extends DisposableObject {
|
|||||||
this.databaseManager,
|
this.databaseManager,
|
||||||
this.storagePath,
|
this.storagePath,
|
||||||
progress,
|
progress,
|
||||||
token
|
token,
|
||||||
|
this.queryServer?.cliServer
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await this.setCurrentDatabase(progress, token, uri);
|
await this.setCurrentDatabase(progress, token, uri);
|
||||||
@@ -696,7 +697,6 @@ export class DatabaseUI extends DisposableObject {
|
|||||||
token: CancellationToken,
|
token: CancellationToken,
|
||||||
): Promise<DatabaseItem | undefined> {
|
): Promise<DatabaseItem | undefined> {
|
||||||
const uri = await chooseDatabaseDir(byFolder);
|
const uri = await chooseDatabaseDir(byFolder);
|
||||||
|
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -713,7 +713,8 @@ export class DatabaseUI extends DisposableObject {
|
|||||||
this.databaseManager,
|
this.databaseManager,
|
||||||
this.storagePath,
|
this.storagePath,
|
||||||
progress,
|
progress,
|
||||||
token
|
token,
|
||||||
|
this.queryServer?.cliServer
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { expect } from 'chai';
|
|||||||
import { extensions, CancellationToken, Uri, window } from 'vscode';
|
import { extensions, CancellationToken, Uri, window } from 'vscode';
|
||||||
|
|
||||||
import { CodeQLExtensionInterface } from '../../extension';
|
import { CodeQLExtensionInterface } from '../../extension';
|
||||||
|
import { CodeQLCliServer } from '../../cli';
|
||||||
import { DatabaseManager } from '../../databases';
|
import { DatabaseManager } from '../../databases';
|
||||||
import { promptImportLgtmDatabase, importArchiveDatabase, promptImportInternetDatabase } from '../../databaseFetcher';
|
import { promptImportLgtmDatabase, importArchiveDatabase, promptImportInternetDatabase } from '../../databaseFetcher';
|
||||||
import { ProgressCallback } from '../../commandRunner';
|
import { ProgressCallback } from '../../commandRunner';
|
||||||
@@ -17,10 +18,11 @@ describe('Databases', function() {
|
|||||||
this.timeout(60000);
|
this.timeout(60000);
|
||||||
|
|
||||||
const LGTM_URL = 'https://lgtm.com/projects/g/aeisenberg/angular-bind-notifier/';
|
const LGTM_URL = 'https://lgtm.com/projects/g/aeisenberg/angular-bind-notifier/';
|
||||||
|
|
||||||
let databaseManager: DatabaseManager;
|
let databaseManager: DatabaseManager;
|
||||||
let sandbox: sinon.SinonSandbox;
|
let sandbox: sinon.SinonSandbox;
|
||||||
let inputBoxStub: sinon.SinonStub;
|
let inputBoxStub: sinon.SinonStub;
|
||||||
|
let cli: CodeQLCliServer;
|
||||||
let progressCallback: ProgressCallback;
|
let progressCallback: ProgressCallback;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -53,7 +55,7 @@ describe('Databases', function() {
|
|||||||
it('should add a database from a folder', async () => {
|
it('should add a database from a folder', async () => {
|
||||||
const progressCallback = sandbox.spy() as ProgressCallback;
|
const progressCallback = sandbox.spy() as ProgressCallback;
|
||||||
const uri = Uri.file(dbLoc);
|
const uri = Uri.file(dbLoc);
|
||||||
let dbItem = await importArchiveDatabase(uri.toString(true), databaseManager, storagePath, progressCallback, {} as CancellationToken);
|
let dbItem = await importArchiveDatabase(uri.toString(true), databaseManager, storagePath, progressCallback, {} as CancellationToken, cli);
|
||||||
expect(dbItem).to.be.eq(databaseManager.currentDatabaseItem);
|
expect(dbItem).to.be.eq(databaseManager.currentDatabaseItem);
|
||||||
expect(dbItem).to.be.eq(databaseManager.databaseItems[0]);
|
expect(dbItem).to.be.eq(databaseManager.databaseItems[0]);
|
||||||
expect(dbItem).not.to.be.undefined;
|
expect(dbItem).not.to.be.undefined;
|
||||||
@@ -64,7 +66,7 @@ describe('Databases', function() {
|
|||||||
|
|
||||||
it('should add a database from lgtm with only one language', async () => {
|
it('should add a database from lgtm with only one language', async () => {
|
||||||
inputBoxStub.resolves(LGTM_URL);
|
inputBoxStub.resolves(LGTM_URL);
|
||||||
let dbItem = await promptImportLgtmDatabase(databaseManager, storagePath, progressCallback, {} as CancellationToken);
|
let dbItem = await promptImportLgtmDatabase(databaseManager, storagePath, progressCallback, {} as CancellationToken, cli);
|
||||||
expect(dbItem).not.to.be.undefined;
|
expect(dbItem).not.to.be.undefined;
|
||||||
dbItem = dbItem!;
|
dbItem = dbItem!;
|
||||||
expect(dbItem.name).to.eq('aeisenberg_angular-bind-notifier_106179a');
|
expect(dbItem.name).to.eq('aeisenberg_angular-bind-notifier_106179a');
|
||||||
@@ -74,7 +76,7 @@ describe('Databases', function() {
|
|||||||
it('should add a database from a url', async () => {
|
it('should add a database from a url', async () => {
|
||||||
inputBoxStub.resolves(DB_URL);
|
inputBoxStub.resolves(DB_URL);
|
||||||
|
|
||||||
let dbItem = await promptImportInternetDatabase(databaseManager, storagePath, progressCallback, {} as CancellationToken);
|
let dbItem = await promptImportInternetDatabase(databaseManager, storagePath, progressCallback, {} as CancellationToken, cli);
|
||||||
expect(dbItem).not.to.be.undefined;
|
expect(dbItem).not.to.be.undefined;
|
||||||
dbItem = dbItem!;
|
dbItem = dbItem!;
|
||||||
expect(dbItem.name).to.eq('db');
|
expect(dbItem.name).to.eq('db');
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ describe('Queries', function() {
|
|||||||
databaseManager,
|
databaseManager,
|
||||||
storagePath,
|
storagePath,
|
||||||
progress,
|
progress,
|
||||||
token
|
token,
|
||||||
|
cli
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!maybeDbItem) {
|
if (!maybeDbItem) {
|
||||||
|
|||||||
Reference in New Issue
Block a user