Skip to content

Commit 03b2dc2

Browse files
committed
Add and use getFeaturePrefix for dependency caching
1 parent 0cbd930 commit 03b2dc2

File tree

5 files changed

+183
-30
lines changed

5 files changed

+183
-30
lines changed

lib/analyze-action.js

Lines changed: 27 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/init-action.js

Lines changed: 27 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/caching-utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ export function getCachingKind(input: string | undefined): CachingKind {
7373
}
7474
}
7575

76+
// The length to which `createCacheKeyHash` truncates hash strings.
77+
export const cacheKeyHashLength = 16;
78+
7679
/**
7780
* Creates a SHA-256 hash of the cache key components to ensure uniqueness
7881
* while keeping the cache key length manageable.
@@ -94,7 +97,7 @@ export function createCacheKeyHash(components: Record<string, any>): string {
9497
.createHash("sha256")
9598
.update(componentsJson)
9699
.digest("hex")
97-
.substring(0, 16);
100+
.substring(0, cacheKeyHashLength);
98101
}
99102

100103
/** Determines whether dependency caching is enabled. */

src/dependency-caching.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import test from "ava";
2+
// import * as sinon from "sinon";
3+
4+
import { cacheKeyHashLength } from "./caching-utils";
5+
import { createStubCodeQL } from "./codeql";
6+
import { getFeaturePrefix } from "./dependency-caching";
7+
import { Feature } from "./feature-flags";
8+
import { KnownLanguage } from "./languages";
9+
import { setupTests, createFeatures } from "./testing-utils";
10+
11+
setupTests(test);
12+
13+
test("getFeaturePrefix - returns empty string if no features are enabled", async (t) => {
14+
const codeql = createStubCodeQL({});
15+
const features = createFeatures([]);
16+
17+
for (const knownLanguage of Object.values(KnownLanguage)) {
18+
const result = await getFeaturePrefix(codeql, features, knownLanguage);
19+
t.deepEqual(result, "", `Expected no feature prefix for ${knownLanguage}`);
20+
}
21+
});
22+
23+
test("getFeaturePrefix - Java - returns 'minify-' if JavaMinimizeDependencyJars is enabled", async (t) => {
24+
const codeql = createStubCodeQL({});
25+
const features = createFeatures([Feature.JavaMinimizeDependencyJars]);
26+
27+
const result = await getFeaturePrefix(codeql, features, KnownLanguage.java);
28+
t.deepEqual(result, "minify-");
29+
});
30+
31+
test("getFeaturePrefix - non-Java - returns '' if JavaMinimizeDependencyJars is enabled", async (t) => {
32+
const codeql = createStubCodeQL({});
33+
const features = createFeatures([Feature.JavaMinimizeDependencyJars]);
34+
35+
for (const knownLanguage of Object.values(KnownLanguage)) {
36+
// Skip Java since we expect a result for it, which is tested in the previous test.
37+
if (knownLanguage === KnownLanguage.java) {
38+
continue;
39+
}
40+
const result = await getFeaturePrefix(codeql, features, knownLanguage);
41+
t.deepEqual(result, "", `Expected no feature prefix for ${knownLanguage}`);
42+
}
43+
});
44+
45+
test("getFeaturePrefix - C# - returns prefix if CsharpNewCacheKey is enabled", async (t) => {
46+
const codeql = createStubCodeQL({});
47+
const features = createFeatures([Feature.CsharpNewCacheKey]);
48+
49+
const result = await getFeaturePrefix(codeql, features, KnownLanguage.csharp);
50+
t.notDeepEqual(result, "");
51+
t.assert(result.endsWith("-"));
52+
// Check the length of the prefix, which should correspond to `cacheKeyHashLength` + 1 for the trailing `-`.
53+
t.is(result.length, cacheKeyHashLength + 1);
54+
});
55+
56+
test("getFeaturePrefix - non-C# - returns '' if CsharpNewCacheKey is enabled", async (t) => {
57+
const codeql = createStubCodeQL({});
58+
const features = createFeatures([Feature.CsharpNewCacheKey]);
59+
60+
for (const knownLanguage of Object.values(KnownLanguage)) {
61+
// Skip C# since we expect a result for it, which is tested in the previous test.
62+
if (knownLanguage === KnownLanguage.csharp) {
63+
continue;
64+
}
65+
const result = await getFeaturePrefix(codeql, features, knownLanguage);
66+
t.deepEqual(result, "", `Expected no feature prefix for ${knownLanguage}`);
67+
}
68+
});

src/dependency-caching.ts

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import * as glob from "@actions/glob";
66

77
import { getTemporaryDirectory } from "./actions-util";
88
import { listActionsCaches } from "./api-client";
9-
import { getTotalCacheSize } from "./caching-utils";
9+
import { createCacheKeyHash, getTotalCacheSize } from "./caching-utils";
1010
import { CodeQL } from "./codeql";
1111
import { Config } from "./config-utils";
1212
import { EnvVar } from "./environment";
13-
import { Feature, Features } from "./feature-flags";
13+
import { Feature, FeatureEnablement, Features } from "./feature-flags";
1414
import { KnownLanguage, Language } from "./languages";
1515
import { Logger } from "./logging";
1616
import { getErrorMessage, getRequiredEnvParam } from "./util";
@@ -442,6 +442,56 @@ async function cacheKey(
442442
return `${await cachePrefix(codeql, features, language)}${hash}`;
443443
}
444444

445+
/**
446+
* If experimental features which the cache contents depend on are enabled for the current language,
447+
* this function returns a prefix that uniquely identifies the set of enabled features. The purpose of
448+
* this is to avoid restoring caches whose contents depended on experimental features, if those
449+
* experimental features are later disabled.
450+
*
451+
* @param codeql The CodeQL instance.
452+
* @param features Information about enabled features.
453+
* @param language The language we are creating the key for.
454+
*
455+
* @returns A cache key prefix identifying the enabled, experimental features that the cache depends on.
456+
*/
457+
export async function getFeaturePrefix(
458+
codeql: CodeQL,
459+
features: FeatureEnablement,
460+
language: Language,
461+
): Promise<string> {
462+
const enabledFeatures: Feature[] = [];
463+
464+
const addFeatureIfEnabled = async (feature: Feature) => {
465+
if (await features.getValue(feature, codeql)) {
466+
enabledFeatures.push(feature);
467+
}
468+
};
469+
470+
if (language === KnownLanguage.java) {
471+
// To ensure a safe rollout of JAR minimization, we change the key when the feature is enabled.
472+
const minimizeJavaJars = await features.getValue(
473+
Feature.JavaMinimizeDependencyJars,
474+
codeql,
475+
);
476+
477+
// To maintain backwards compatibility with this, we return "minify-" instead of a hash.
478+
if (minimizeJavaJars) {
479+
return "minify-";
480+
}
481+
} else if (language === KnownLanguage.csharp) {
482+
await addFeatureIfEnabled(Feature.CsharpNewCacheKey);
483+
}
484+
485+
// If any features that affect the cache are enabled, return a feature prefix by
486+
// computing a hash of the feature array.
487+
if (enabledFeatures.length > 0) {
488+
return `${createCacheKeyHash(enabledFeatures)}-`;
489+
}
490+
491+
// No feature prefix.
492+
return "";
493+
}
494+
445495
/**
446496
* Constructs a prefix for the cache key, comprised of a CodeQL-specific prefix, a version number that
447497
* can be changed to invalidate old caches, the runner's operating system, and the specified language name.
@@ -464,16 +514,12 @@ async function cachePrefix(
464514
prefix = `${prefix}-${customPrefix}`;
465515
}
466516

467-
// To ensure a safe rollout of JAR minimization, we change the key when the feature is enabled.
468-
const minimizeJavaJars = await features.getValue(
469-
Feature.JavaMinimizeDependencyJars,
470-
codeql,
471-
);
472-
if (language === KnownLanguage.java && minimizeJavaJars) {
473-
prefix = `minify-${prefix}`;
474-
}
517+
// Calculate the feature prefix for the cache, if any. This is a hash that identifies
518+
// experimental features that affect the cache contents.
519+
const featurePrefix = await getFeaturePrefix(codeql, features, language);
475520

476-
return `${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
521+
// Assemble the cache key.
522+
return `${featurePrefix}${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
477523
}
478524

479525
/** Represents information about our overall cache usage for CodeQL dependency caches. */

0 commit comments

Comments
 (0)