@@ -14,16 +14,27 @@ import { KnownLanguage, Language } from "./languages";
1414import { Logger } from "./logging" ;
1515import { getRequiredEnvParam } from "./util" ;
1616
17+ class NoMatchingFilesError extends Error {
18+ constructor ( msg ?: string ) {
19+ super ( msg ) ;
20+
21+ this . name = "NoMatchingFilesError" ;
22+ }
23+ }
24+
1725/**
1826 * Caching configuration for a particular language.
1927 */
2028interface CacheConfig {
2129 /** Gets the paths of directories on the runner that should be included in the cache. */
2230 getDependencyPaths : ( ) => string [ ] ;
2331 /**
24- * Gets patterns for the paths of files whose contents affect which dependencies are used
25- * by a project. We find all files which match these patterns, calculate a hash for
26- * their contents, and use that hash as part of the cache key.
32+ * Gets an array of glob patterns for the paths of files whose contents affect which dependencies are used
33+ * by a project. This function also checks whether there are any matching files and throws
34+ * a `NoMatchingFilesError` error if no files match.
35+ *
36+ * The glob patterns are intended to be used for cache keys, where we find all files which match these
37+ * patterns, calculate a hash for their contents, and use that hash as part of the cache key.
2738 */
2839 getHashPatterns : ( codeql : CodeQL , features : Features ) => Promise < string [ ] > ;
2940}
@@ -59,43 +70,93 @@ export function getJavaDependencyDirs(): string[] {
5970 ] ;
6071}
6172
73+ /**
74+ * Checks that there are files which match `patterns`. If there are matching files for any of the patterns,
75+ * this function returns all `patterns`. Otherwise, a `NoMatchingFilesError` is thrown.
76+ *
77+ * @param patterns The glob patterns to find matching files for.
78+ * @returns The array of glob patterns if there are matching files.
79+ */
80+ async function makePatternCheck ( patterns : string [ ] ) : Promise < string [ ] > {
81+ const globber = await makeGlobber ( patterns ) ;
82+
83+ if ( ( await globber . glob ( ) ) . length === 0 ) {
84+ throw new NoMatchingFilesError ( ) ;
85+ }
86+
87+ return patterns ;
88+ }
89+
6290/**
6391 * Default caching configurations per language.
6492 */
6593const defaultCacheConfigs : { [ language : string ] : CacheConfig } = {
6694 java : {
6795 getDependencyPaths : getJavaDependencyDirs ,
68- getHashPatterns : async ( ) => [
69- // Maven
70- "**/pom.xml" ,
71- // Gradle
72- "**/*.gradle*" ,
73- "**/gradle-wrapper.properties" ,
74- "buildSrc/**/Versions.kt" ,
75- "buildSrc/**/Dependencies.kt" ,
76- "gradle/*.versions.toml" ,
77- "**/versions.properties" ,
78- ] ,
96+ getHashPatterns : async ( ) =>
97+ makePatternCheck ( [
98+ // Maven
99+ "**/pom.xml" ,
100+ // Gradle
101+ "**/*.gradle*" ,
102+ "**/gradle-wrapper.properties" ,
103+ "buildSrc/**/Versions.kt" ,
104+ "buildSrc/**/Dependencies.kt" ,
105+ "gradle/*.versions.toml" ,
106+ "**/versions.properties" ,
107+ ] ) ,
79108 } ,
80109 csharp : {
81110 getDependencyPaths : ( ) => [ join ( os . homedir ( ) , ".nuget" , "packages" ) ] ,
82- getHashPatterns : async ( ) => [
83- // NuGet
84- "**/packages.lock.json" ,
85- // Paket
86- "**/paket.lock" ,
87- ] ,
111+ getHashPatterns : async ( ) =>
112+ makePatternCheck ( [
113+ // NuGet
114+ "**/packages.lock.json" ,
115+ // Paket
116+ "**/paket.lock" ,
117+ ] ) ,
88118 } ,
89119 go : {
90120 getDependencyPaths : ( ) => [ join ( os . homedir ( ) , "go" , "pkg" , "mod" ) ] ,
91- getHashPatterns : async ( ) => [ "**/go.sum" ] ,
121+ getHashPatterns : async ( ) => makePatternCheck ( [ "**/go.sum" ] ) ,
92122 } ,
93123} ;
94124
95125async function makeGlobber ( patterns : string [ ] ) : Promise < glob . Globber > {
96126 return glob . create ( patterns . join ( "\n" ) ) ;
97127}
98128
129+ /**
130+ * A wrapper around `cacheConfig.getHashPatterns` which catches `NoMatchingFilesError` errors,
131+ * and logs that there are no files to calculate a hash for the cache key from.
132+ *
133+ * @param codeql The CodeQL instance to use.
134+ * @param features Information about which FFs are enabled.
135+ * @param language The language the `CacheConfig` is for. For use in the log message.
136+ * @param cacheConfig The caching configuration to call `getHashPatterns` on.
137+ * @param logger The logger to write the log message to if there is an error.
138+ * @returns An array of glob patterns to use for hashing files, or `undefined` if there are no matching files.
139+ */
140+ async function checkHashPatterns (
141+ codeql : CodeQL ,
142+ features : Features ,
143+ language : Language ,
144+ cacheConfig : CacheConfig ,
145+ logger : Logger ,
146+ ) : Promise < string [ ] | undefined > {
147+ try {
148+ return cacheConfig . getHashPatterns ( codeql , features ) ;
149+ } catch ( err ) {
150+ if ( err instanceof NoMatchingFilesError ) {
151+ logger . info (
152+ `Skipping download of dependency cache for ${ language } as we cannot calculate a hash for the cache key.` ,
153+ ) ;
154+ return undefined ;
155+ }
156+ throw err ;
157+ }
158+ }
159+
99160/**
100161 * Attempts to restore dependency caches for the languages being analyzed.
101162 *
@@ -125,13 +186,14 @@ export async function downloadDependencyCaches(
125186
126187 // Check that we can find files to calculate the hash for the cache key from, so we don't end up
127188 // with an empty string.
128- const patterns = await cacheConfig . getHashPatterns ( codeql , features ) ;
129- const globber = await makeGlobber ( patterns ) ;
130-
131- if ( ( await globber . glob ( ) ) . length === 0 ) {
132- logger . info (
133- `Skipping download of dependency cache for ${ language } as we cannot calculate a hash for the cache key.` ,
134- ) ;
189+ const patterns = await checkHashPatterns (
190+ codeql ,
191+ features ,
192+ language ,
193+ cacheConfig ,
194+ logger ,
195+ ) ;
196+ if ( patterns === undefined ) {
135197 continue ;
136198 }
137199
@@ -189,13 +251,14 @@ export async function uploadDependencyCaches(
189251
190252 // Check that we can find files to calculate the hash for the cache key from, so we don't end up
191253 // with an empty string.
192- const patterns = await cacheConfig . getHashPatterns ( codeql , features ) ;
193- const globber = await makeGlobber ( patterns ) ;
194-
195- if ( ( await globber . glob ( ) ) . length === 0 ) {
196- logger . info (
197- `Skipping upload of dependency cache for ${ language } as we cannot calculate a hash for the cache key.` ,
198- ) ;
254+ const patterns = await checkHashPatterns (
255+ codeql ,
256+ features ,
257+ language ,
258+ cacheConfig ,
259+ logger ,
260+ ) ;
261+ if ( patterns === undefined ) {
199262 continue ;
200263 }
201264
0 commit comments