@@ -15,16 +15,27 @@ import { KnownLanguage, Language } from "./languages";
1515import { Logger } from "./logging" ;
1616import { getErrorMessage , getRequiredEnvParam } from "./util" ;
1717
18+ class NoMatchingFilesError extends Error {
19+ constructor ( msg ?: string ) {
20+ super ( msg ) ;
21+
22+ this . name = "NoMatchingFilesError" ;
23+ }
24+ }
25+
1826/**
1927 * Caching configuration for a particular language.
2028 */
2129interface CacheConfig {
2230 /** Gets the paths of directories on the runner that should be included in the cache. */
2331 getDependencyPaths : ( ) => string [ ] ;
2432 /**
25- * Gets patterns for the paths of files whose contents affect which dependencies are used
26- * by a project. We find all files which match these patterns, calculate a hash for
27- * their contents, and use that hash as part of the cache key.
33+ * Gets an array of glob patterns for the paths of files whose contents affect which dependencies are used
34+ * by a project. This function also checks whether there are any matching files and throws
35+ * a `NoMatchingFilesError` error if no files match.
36+ *
37+ * The glob patterns are intended to be used for cache keys, where we find all files which match these
38+ * patterns, calculate a hash for their contents, and use that hash as part of the cache key.
2839 */
2940 getHashPatterns : ( codeql : CodeQL , features : Features ) => Promise < string [ ] > ;
3041}
@@ -60,36 +71,55 @@ export function getJavaDependencyDirs(): string[] {
6071 ] ;
6172}
6273
74+ /**
75+ * Checks that there are files which match `patterns`. If there are matching files for any of the patterns,
76+ * this function returns all `patterns`. Otherwise, a `NoMatchingFilesError` is thrown.
77+ *
78+ * @param patterns The glob patterns to find matching files for.
79+ * @returns The array of glob patterns if there are matching files.
80+ */
81+ async function makePatternCheck ( patterns : string [ ] ) : Promise < string [ ] > {
82+ const globber = await makeGlobber ( patterns ) ;
83+
84+ if ( ( await globber . glob ( ) ) . length === 0 ) {
85+ throw new NoMatchingFilesError ( ) ;
86+ }
87+
88+ return patterns ;
89+ }
90+
6391/**
6492 * Default caching configurations per language.
6593 */
6694const defaultCacheConfigs : { [ language : string ] : CacheConfig } = {
6795 java : {
6896 getDependencyPaths : getJavaDependencyDirs ,
69- getHashPatterns : async ( ) => [
70- // Maven
71- "**/pom.xml" ,
72- // Gradle
73- "**/*.gradle*" ,
74- "**/gradle-wrapper.properties" ,
75- "buildSrc/**/Versions.kt" ,
76- "buildSrc/**/Dependencies.kt" ,
77- "gradle/*.versions.toml" ,
78- "**/versions.properties" ,
79- ] ,
97+ getHashPatterns : async ( ) =>
98+ makePatternCheck ( [
99+ // Maven
100+ "**/pom.xml" ,
101+ // Gradle
102+ "**/*.gradle*" ,
103+ "**/gradle-wrapper.properties" ,
104+ "buildSrc/**/Versions.kt" ,
105+ "buildSrc/**/Dependencies.kt" ,
106+ "gradle/*.versions.toml" ,
107+ "**/versions.properties" ,
108+ ] ) ,
80109 } ,
81110 csharp : {
82111 getDependencyPaths : ( ) => [ join ( os . homedir ( ) , ".nuget" , "packages" ) ] ,
83- getHashPatterns : async ( ) => [
84- // NuGet
85- "**/packages.lock.json" ,
86- // Paket
87- "**/paket.lock" ,
88- ] ,
112+ getHashPatterns : async ( ) =>
113+ makePatternCheck ( [
114+ // NuGet
115+ "**/packages.lock.json" ,
116+ // Paket
117+ "**/paket.lock" ,
118+ ] ) ,
89119 } ,
90120 go : {
91121 getDependencyPaths : ( ) => [ join ( os . homedir ( ) , "go" , "pkg" , "mod" ) ] ,
92- getHashPatterns : async ( ) => [ "**/go.sum" ] ,
122+ getHashPatterns : async ( ) => makePatternCheck ( [ "**/go.sum" ] ) ,
93123 } ,
94124} ;
95125
@@ -119,6 +149,37 @@ export interface DependencyCacheRestoreStatus {
119149/** An array of `DependencyCacheRestoreStatus` objects for each analysed language with a caching configuration. */
120150export type DependencyCacheRestoreStatusReport = DependencyCacheRestoreStatus [ ] ;
121151
152+ /**
153+ * A wrapper around `cacheConfig.getHashPatterns` which catches `NoMatchingFilesError` errors,
154+ * and logs that there are no files to calculate a hash for the cache key from.
155+ *
156+ * @param codeql The CodeQL instance to use.
157+ * @param features Information about which FFs are enabled.
158+ * @param language The language the `CacheConfig` is for. For use in the log message.
159+ * @param cacheConfig The caching configuration to call `getHashPatterns` on.
160+ * @param logger The logger to write the log message to if there is an error.
161+ * @returns An array of glob patterns to use for hashing files, or `undefined` if there are no matching files.
162+ */
163+ async function checkHashPatterns (
164+ codeql : CodeQL ,
165+ features : Features ,
166+ language : Language ,
167+ cacheConfig : CacheConfig ,
168+ logger : Logger ,
169+ ) : Promise < string [ ] | undefined > {
170+ try {
171+ return cacheConfig . getHashPatterns ( codeql , features ) ;
172+ } catch ( err ) {
173+ if ( err instanceof NoMatchingFilesError ) {
174+ logger . info (
175+ `Skipping download of dependency cache for ${ language } as we cannot calculate a hash for the cache key.` ,
176+ ) ;
177+ return undefined ;
178+ }
179+ throw err ;
180+ }
181+ }
182+
122183/**
123184 * Attempts to restore dependency caches for the languages being analyzed.
124185 *
@@ -149,14 +210,15 @@ export async function downloadDependencyCaches(
149210
150211 // Check that we can find files to calculate the hash for the cache key from, so we don't end up
151212 // with an empty string.
152- const patterns = await cacheConfig . getHashPatterns ( codeql , features ) ;
153- const globber = await makeGlobber ( patterns ) ;
154-
155- if ( ( await globber . glob ( ) ) . length === 0 ) {
213+ const patterns = await checkHashPatterns (
214+ codeql ,
215+ features ,
216+ language ,
217+ cacheConfig ,
218+ logger ,
219+ ) ;
220+ if ( patterns === undefined ) {
156221 status . push ( { language, hit_kind : CacheHitKind . NoHash } ) ;
157- logger . info (
158- `Skipping download of dependency cache for ${ language } as we cannot calculate a hash for the cache key.` ,
159- ) ;
160222 continue ;
161223 }
162224
@@ -245,14 +307,14 @@ export async function uploadDependencyCaches(
245307
246308 // Check that we can find files to calculate the hash for the cache key from, so we don't end up
247309 // with an empty string.
248- const patterns = await cacheConfig . getHashPatterns ( codeql , features ) ;
249- const globber = await makeGlobber ( patterns ) ;
250-
251- if ( ( await globber . glob ( ) ) . length === 0 ) {
252- status . push ( { language , result : CacheStoreResult . NoHash } ) ;
253- logger . info (
254- `Skipping upload of dependency cache for ${ language } as we cannot calculate a hash for the cache key.` ,
255- ) ;
310+ const patterns = await checkHashPatterns (
311+ codeql ,
312+ features ,
313+ language ,
314+ cacheConfig ,
315+ logger ,
316+ ) ;
317+ if ( patterns === undefined ) {
256318 continue ;
257319 }
258320
0 commit comments