Skip to content

Commit 444de8c

Browse files
authored
Merge pull request #2072 from github/aeisenberg/mrva-extension-packs
Inject extension pack dependencies into MRVA packs
2 parents 64d97aa + 250dc15 commit 444de8c

7 files changed

Lines changed: 220 additions & 59 deletions

File tree

extensions/ql-vscode/src/cli.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1284,11 +1284,25 @@ export class CodeQLCliServer implements Disposable {
12841284
);
12851285
}
12861286

1287-
async packInstall(dir: string, forceUpdate = false) {
1287+
async packInstall(
1288+
dir: string,
1289+
{ forceUpdate = false, workspaceFolders = [] as string[] } = {},
1290+
) {
12881291
const args = [dir];
12891292
if (forceUpdate) {
12901293
args.push("--mode", "update");
12911294
}
1295+
if (workspaceFolders?.length > 0) {
1296+
if (await this.cliConstraints.supportsAdditionalPacksInstall()) {
1297+
args.push(
1298+
// Allow prerelease packs from the ql submodule.
1299+
"--allow-prerelease",
1300+
// Allow the use of --additional-packs argument without issueing a warning
1301+
"--no-strict-mode",
1302+
...this.getAdditionalPacksArg(workspaceFolders),
1303+
);
1304+
}
1305+
}
12921306
return this.runJsonCodeQlCliCommandWithAuthentication(
12931307
["pack", "install"],
12941308
args,
@@ -1692,6 +1706,13 @@ export class CliVersionConstraint {
16921706
*/
16931707
public static CLI_VERSION_WITH_QLPACKS_KIND = new SemVer("2.12.3");
16941708

1709+
/**
1710+
* CLI version that supports the `--additional-packs` option for the `pack install` command.
1711+
*/
1712+
public static CLI_VERSION_WITH_ADDITIONAL_PACKS_INSTALL = new SemVer(
1713+
"2.12.4",
1714+
);
1715+
16951716
constructor(private readonly cli: CodeQLCliServer) {
16961717
/**/
16971718
}
@@ -1755,4 +1776,10 @@ export class CliVersionConstraint {
17551776
CliVersionConstraint.CLI_VERSION_WITH_QLPACKS_KIND,
17561777
);
17571778
}
1779+
1780+
async supportsAdditionalPacksInstall() {
1781+
return this.isVersionAtLeast(
1782+
CliVersionConstraint.CLI_VERSION_WITH_ADDITIONAL_PACKS_INSTALL,
1783+
);
1784+
}
17581785
}

extensions/ql-vscode/src/pure/ql.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { join } from "path";
22
import { pathExists } from "fs-extra";
33

44
export const QLPACK_FILENAMES = ["qlpack.yml", "codeql-pack.yml"];
5+
export const QLPACK_LOCK_FILENAMES = [
6+
"qlpack.lock.yml",
7+
"codeql-pack.lock.yml",
8+
];
59
export const FALLBACK_QLPACK_FILENAME = QLPACK_FILENAMES[0];
610

711
export async function getQlPackPath(

extensions/ql-vscode/src/quick-query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export async function displayQuickQuery(
143143

144144
if (shouldRewrite) {
145145
await cliServer.clearCache();
146-
await cliServer.packInstall(queriesDir, true);
146+
await cliServer.packInstall(queriesDir, { forceUpdate: true });
147147
}
148148

149149
await Window.showTextDocument(await workspace.openTextDocument(qlFile));

extensions/ql-vscode/src/variant-analysis/run-remote-query.ts

Lines changed: 136 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
getQlPackPath,
3535
FALLBACK_QLPACK_FILENAME,
3636
QLPACK_FILENAMES,
37+
QLPACK_LOCK_FILENAMES,
3738
} from "../pure/ql";
3839

3940
export interface QlPack {
@@ -70,42 +71,23 @@ async function generateQueryPack(
7071
const originalPackRoot = await findPackRoot(queryFile);
7172
const packRelativePath = relative(originalPackRoot, queryFile);
7273
const targetQueryFileName = join(queryPackDir, packRelativePath);
74+
const workspaceFolders = getOnDiskWorkspaceFolders();
7375

7476
let language: string | undefined;
77+
78+
// Check if the query is already in a query pack.
79+
// If so, copy the entire query pack to the temporary directory.
80+
// Otherwise, copy only the query file to the temporary directory
81+
// and generate a synthetic query pack.
7582
if (await getQlPackPath(originalPackRoot)) {
7683
// don't include ql files. We only want the queryFile to be copied.
77-
const toCopy = await cliServer.packPacklist(originalPackRoot, false);
78-
79-
// also copy the lock file (either new name or old name) and the query file itself. These are not included in the packlist.
80-
[
81-
join(originalPackRoot, "qlpack.lock.yml"),
82-
join(originalPackRoot, "codeql-pack.lock.yml"),
84+
await copyExistingQueryPack(
85+
cliServer,
86+
originalPackRoot,
8387
queryFile,
84-
].forEach((absolutePath) => {
85-
if (absolutePath) {
86-
toCopy.push(absolutePath);
87-
}
88-
});
89-
90-
let copiedCount = 0;
91-
await copy(originalPackRoot, queryPackDir, {
92-
filter: (file: string) =>
93-
// copy file if it is in the packlist, or it is a parent directory of a file in the packlist
94-
!!toCopy.find((f) => {
95-
// Normalized paths ensure that Windows drive letters are capitalized consistently.
96-
const normalizedPath = Uri.file(f).fsPath;
97-
const matches =
98-
normalizedPath === file || normalizedPath.startsWith(file + sep);
99-
if (matches) {
100-
copiedCount++;
101-
}
102-
return matches;
103-
}),
104-
});
105-
106-
void extLogger.log(`Copied ${copiedCount} files to ${queryPackDir}`);
107-
108-
await fixPackFile(queryPackDir, packRelativePath);
88+
queryPackDir,
89+
packRelativePath,
90+
);
10991

11092
language = await findLanguage(cliServer, Uri.file(targetQueryFileName));
11193
} else {
@@ -114,20 +96,12 @@ async function generateQueryPack(
11496

11597
// copy only the query file to the query pack directory
11698
// and generate a synthetic query pack
117-
void extLogger.log(`Copying ${queryFile} to ${queryPackDir}`);
118-
await copy(queryFile, targetQueryFileName);
119-
void extLogger.log("Generating synthetic query pack");
120-
const syntheticQueryPack = {
121-
name: QUERY_PACK_NAME,
122-
version: "0.0.0",
123-
dependencies: {
124-
[`codeql/${language}-all`]: "*",
125-
},
126-
defaultSuite: generateDefaultSuite(packRelativePath),
127-
};
128-
await writeFile(
129-
join(queryPackDir, FALLBACK_QLPACK_FILENAME),
130-
dump(syntheticQueryPack),
99+
await createNewQueryPack(
100+
queryFile,
101+
queryPackDir,
102+
targetQueryFileName,
103+
language,
104+
packRelativePath,
131105
);
132106
}
133107
if (!language) {
@@ -149,12 +123,21 @@ async function generateQueryPack(
149123
precompilationOpts = ["--no-precompile"];
150124
}
151125

126+
if (await cliServer.useExtensionPacks()) {
127+
await injectExtensionPacks(cliServer, queryPackDir, workspaceFolders);
128+
}
129+
130+
await cliServer.packInstall(queryPackDir, {
131+
workspaceFolders,
132+
});
133+
134+
// Clear the CLI cache so that the most recent qlpack lock file is used.
135+
await cliServer.clearCache();
136+
152137
const bundlePath = await getPackedBundlePath(queryPackDir);
153138
void extLogger.log(
154139
`Compiling and bundling query pack from ${queryPackDir} to ${bundlePath}. (This may take a while.)`,
155140
);
156-
await cliServer.packInstall(queryPackDir);
157-
const workspaceFolders = getOnDiskWorkspaceFolders();
158141
await cliServer.packBundle(
159142
queryPackDir,
160143
workspaceFolders,
@@ -168,6 +151,70 @@ async function generateQueryPack(
168151
};
169152
}
170153

154+
async function createNewQueryPack(
155+
queryFile: string,
156+
queryPackDir: string,
157+
targetQueryFileName: string,
158+
language: string | undefined,
159+
packRelativePath: string,
160+
) {
161+
void extLogger.log(`Copying ${queryFile} to ${queryPackDir}`);
162+
await copy(queryFile, targetQueryFileName);
163+
void extLogger.log("Generating synthetic query pack");
164+
const syntheticQueryPack = {
165+
name: QUERY_PACK_NAME,
166+
version: "0.0.0",
167+
dependencies: {
168+
[`codeql/${language}-all`]: "*",
169+
},
170+
defaultSuite: generateDefaultSuite(packRelativePath),
171+
};
172+
await writeFile(
173+
join(queryPackDir, FALLBACK_QLPACK_FILENAME),
174+
dump(syntheticQueryPack),
175+
);
176+
}
177+
178+
async function copyExistingQueryPack(
179+
cliServer: cli.CodeQLCliServer,
180+
originalPackRoot: string,
181+
queryFile: string,
182+
queryPackDir: string,
183+
packRelativePath: string,
184+
) {
185+
const toCopy = await cliServer.packPacklist(originalPackRoot, false);
186+
187+
[
188+
// also copy the lock file (either new name or old name) and the query file itself. These are not included in the packlist.
189+
...QLPACK_LOCK_FILENAMES.map((f) => join(originalPackRoot, f)),
190+
queryFile,
191+
].forEach((absolutePath) => {
192+
if (absolutePath) {
193+
toCopy.push(absolutePath);
194+
}
195+
});
196+
197+
let copiedCount = 0;
198+
await copy(originalPackRoot, queryPackDir, {
199+
filter: (file: string) =>
200+
// copy file if it is in the packlist, or it is a parent directory of a file in the packlist
201+
!!toCopy.find((f) => {
202+
// Normalized paths ensure that Windows drive letters are capitalized consistently.
203+
const normalizedPath = Uri.file(f).fsPath;
204+
const matches =
205+
normalizedPath === file || normalizedPath.startsWith(file + sep);
206+
if (matches) {
207+
copiedCount++;
208+
}
209+
return matches;
210+
}),
211+
});
212+
213+
void extLogger.log(`Copied ${copiedCount} files to ${queryPackDir}`);
214+
215+
await fixPackFile(queryPackDir, packRelativePath);
216+
}
217+
171218
async function findPackRoot(queryFile: string): Promise<string> {
172219
// recursively find the directory containing qlpack.yml or codeql-pack.yml
173220
let dir = dirname(queryFile);
@@ -329,19 +376,54 @@ async function fixPackFile(
329376
}
330377
const qlpack = load(await readFile(packPath, "utf8")) as QlPack;
331378

332-
// update pack name
333379
qlpack.name = QUERY_PACK_NAME;
334-
335-
// update default suite
336-
delete qlpack.defaultSuiteFile;
337-
qlpack.defaultSuite = generateDefaultSuite(packRelativePath);
338-
339-
// remove any ${workspace} version references
380+
updateDefaultSuite(qlpack, packRelativePath);
340381
removeWorkspaceRefs(qlpack);
341382

342383
await writeFile(packPath, dump(qlpack));
343384
}
344385

386+
async function injectExtensionPacks(
387+
cliServer: cli.CodeQLCliServer,
388+
queryPackDir: string,
389+
workspaceFolders: string[],
390+
) {
391+
const qlpackFile = await getQlPackPath(queryPackDir);
392+
if (!qlpackFile) {
393+
throw new Error(
394+
`Could not find ${QLPACK_FILENAMES.join(
395+
" or ",
396+
)} file in '${queryPackDir}'`,
397+
);
398+
}
399+
const syntheticQueryPack = load(await readFile(qlpackFile, "utf8")) as QlPack;
400+
401+
const extensionPacks = await cliServer.resolveQlpacks(workspaceFolders, true);
402+
Object.entries(extensionPacks).forEach(([name, paths]) => {
403+
// We are guaranteed that there is at least one path found for each extension pack.
404+
// If there are multiple paths, then we have a problem. This means that there is
405+
// ambiguity in which path to use. This is an error.
406+
if (paths.length > 1) {
407+
throw new Error(
408+
`Multiple versions of extension pack '${name}' found: ${paths.join(
409+
", ",
410+
)}`,
411+
);
412+
}
413+
// Add this extension pack as a dependency. It doesn't matter which
414+
// version we specify, since we are guaranteed that the extension pack
415+
// is resolved from source at the given path.
416+
syntheticQueryPack.dependencies[name] = "*";
417+
});
418+
await writeFile(qlpackFile, dump(syntheticQueryPack));
419+
await cliServer.clearCache();
420+
}
421+
422+
function updateDefaultSuite(qlpack: QlPack, packRelativePath: string) {
423+
delete qlpack.defaultSuiteFile;
424+
qlpack.defaultSuite = generateDefaultSuite(packRelativePath);
425+
}
426+
345427
function generateDefaultSuite(packRelativePath: string) {
346428
return [
347429
{
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
extensions:
2+
- addsTo:
3+
pack: codeql/javascript-all
4+
extensible: sourceModel
5+
data:
6+
- [ "@example/read-write-user-data", "Member[readUserData].ReturnValue.Awaited", "remote" ]
7+
8+
- addsTo:
9+
pack: codeql/javascript-all
10+
extensible: sinkModel
11+
data:
12+
- [ "@example/read-write-user-data", "Member[writeUserData].Argument[0]", "command-line-injection" ]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
name: github/extension-pack-for-testing
2+
version: 0.0.0
3+
library: true
4+
extensionTargets:
5+
github/remote-query-pack: "*"
6+
7+
dataExtensions:
8+
- extension-file.yml

0 commit comments

Comments
 (0)