11import * as fs from 'fs-extra' ;
22import * as yaml from 'js-yaml' ;
33import * as tmp from 'tmp-promise' ;
4+ import * as path from 'path' ;
45
56import * as helpers from '../helpers' ;
67import {
@@ -12,6 +13,11 @@ import {
1213import { CodeQLCliServer } from '../cli' ;
1314import { DatabaseItem } from '../databases' ;
1415import { QlPacksForLanguage } from '../helpers' ;
16+ import { logger } from '../logging' ;
17+ import { createInitialQueryInfo } from '../run-queries-shared' ;
18+ import { CancellationToken , Uri } from 'vscode' ;
19+ import { ProgressCallback } from '../commandRunner' ;
20+ import { QueryRunner } from '../queryRunner' ;
1521
1622export async function qlpackOfDatabase ( cli : CodeQLCliServer , db : DatabaseItem ) : Promise < QlPacksForLanguage > {
1723 if ( db . contents === undefined ) {
@@ -104,3 +110,69 @@ export async function resolveQueries(cli: CodeQLCliServer, qlpacks: QlPacksForLa
104110 void helpers . showAndLogErrorMessage ( errorMessage ) ;
105111 throw new Error ( `Couldn't find any queries tagged ${ tagOfKeyType ( keyType ) } in any of the following packs: ${ packsToSearch . join ( ', ' ) } .` ) ;
106112}
113+
114+ async function resolveContextualQuery ( cli : CodeQLCliServer , query : string ) : Promise < { packPath : string , createdTempLockFile : boolean } > {
115+ // Contextual queries now live within the standard library packs.
116+ // This simplifies distribution (you don't need the standard query pack to use the AST viewer),
117+ // but if the library pack doesn't have a lockfile, we won't be able to find
118+ // other pack dependencies of the library pack.
119+
120+ // Work out the enclosing pack.
121+ const packContents = await cli . packPacklist ( query , false ) ;
122+ const packFilePath = packContents . find ( ( p ) => [ 'codeql-pack.yml' , 'qlpack.yml' ] . includes ( path . basename ( p ) ) ) ;
123+ if ( packFilePath === undefined ) {
124+ // Should not happen; we already resolved this query.
125+ throw new Error ( `Could not find a CodeQL pack file for the pack enclosing the contextual query ${ query } ` ) ;
126+ }
127+ const packPath = path . dirname ( packFilePath ) ;
128+ const lockFilePath = packContents . find ( ( p ) => [ 'codeql-pack.lock.yml' , 'qlpack.lock.yml' ] . includes ( path . basename ( p ) ) ) ;
129+ let createdTempLockFile = false ;
130+ if ( ! lockFilePath ) {
131+ // No lock file, likely because this library pack is in the package cache.
132+ // Create a lock file so that we can resolve dependencies and library path
133+ // for the contextual query.
134+ void logger . log ( `Library pack ${ packPath } is missing a lock file; creating a temporary lock file` ) ;
135+ await cli . packResolveDependencies ( packPath ) ;
136+ createdTempLockFile = true ;
137+ // Clear CLI server pack cache before installing dependencies,
138+ // so that it picks up the new lock file, not the previously cached pack.
139+ void logger . log ( 'Clearing the CodeQL CLI server\'s pack cache' ) ;
140+ await cli . clearCache ( ) ;
141+ // Install dependencies.
142+ void logger . log ( `Installing package dependencies for library pack ${ packPath } ` ) ;
143+ await cli . packInstall ( packPath ) ;
144+ }
145+ return { packPath, createdTempLockFile } ;
146+ }
147+
148+ async function removeTemporaryLockFile ( packPath : string ) {
149+ const tempLockFilePath = path . resolve ( packPath , 'codeql-pack.lock.yml' ) ;
150+ void logger . log ( `Deleting temporary package lock file at ${ tempLockFilePath } ` ) ;
151+ // It's fine if the file doesn't exist.
152+ await fs . promises . rm ( path . resolve ( packPath , 'codeql-pack.lock.yml' ) , { force : true } ) ;
153+ }
154+
155+ export async function runContextualQuery ( query : string , db : DatabaseItem , queryStorageDir : string , qs : QueryRunner , cli : CodeQLCliServer , progress : ProgressCallback , token : CancellationToken , templates : Record < string , string > ) {
156+ const { packPath, createdTempLockFile } = await resolveContextualQuery ( cli , query ) ;
157+ const initialInfo = await createInitialQueryInfo (
158+ Uri . file ( query ) ,
159+ {
160+ name : db . name ,
161+ databaseUri : db . databaseUri . toString ( ) ,
162+ } ,
163+ false
164+ ) ;
165+ void logger . log ( `Running contextual query ${ query } ; results will be stored in ${ queryStorageDir } ` ) ;
166+ const queryResult = await qs . compileAndRunQueryAgainstDatabase (
167+ db ,
168+ initialInfo ,
169+ queryStorageDir ,
170+ progress ,
171+ token ,
172+ templates
173+ ) ;
174+ if ( createdTempLockFile ) {
175+ await removeTemporaryLockFile ( packPath ) ;
176+ }
177+ return queryResult ;
178+ }
0 commit comments