Add and use getFeaturePrefix for dependency caching

This commit is contained in:
Michael B. Gale
2025-11-05 16:33:21 +00:00
parent 0cbd930deb
commit 03b2dc2a3f
5 changed files with 183 additions and 30 deletions

36
lib/analyze-action.js generated
View File

@@ -88183,9 +88183,10 @@ async function getTotalCacheSize(paths, logger, quiet = false) {
function shouldStoreCache(kind) {
return kind === "full" /* Full */ || kind === "store" /* Store */;
}
var cacheKeyHashLength = 16;
function createCacheKeyHash(components) {
const componentsJson = JSON.stringify(components);
return crypto.createHash("sha256").update(componentsJson).digest("hex").substring(0, 16);
return crypto.createHash("sha256").update(componentsJson).digest("hex").substring(0, cacheKeyHashLength);
}
// src/config/db-config.ts
@@ -91204,6 +91205,29 @@ async function cacheKey2(codeql, features, language, patterns) {
const hash2 = await glob.hashFiles(patterns.join("\n"));
return `${await cachePrefix2(codeql, features, language)}${hash2}`;
}
async function getFeaturePrefix(codeql, features, language) {
const enabledFeatures = [];
const addFeatureIfEnabled = async (feature) => {
if (await features.getValue(feature, codeql)) {
enabledFeatures.push(feature);
}
};
if (language === "java" /* java */) {
const minimizeJavaJars = await features.getValue(
"java_minimize_dependency_jars" /* JavaMinimizeDependencyJars */,
codeql
);
if (minimizeJavaJars) {
return "minify-";
}
} else if (language === "csharp" /* csharp */) {
await addFeatureIfEnabled("csharp_new_cache_key" /* CsharpNewCacheKey */);
}
if (enabledFeatures.length > 0) {
return `${createCacheKeyHash(enabledFeatures)}-`;
}
return "";
}
async function cachePrefix2(codeql, features, language) {
const runnerOs = getRequiredEnvParam("RUNNER_OS");
const customPrefix = process.env["CODEQL_ACTION_DEPENDENCY_CACHE_PREFIX" /* DEPENDENCY_CACHING_PREFIX */];
@@ -91211,14 +91235,8 @@ async function cachePrefix2(codeql, features, language) {
if (customPrefix !== void 0 && customPrefix.length > 0) {
prefix = `${prefix}-${customPrefix}`;
}
const minimizeJavaJars = await features.getValue(
"java_minimize_dependency_jars" /* JavaMinimizeDependencyJars */,
codeql
);
if (language === "java" /* java */ && minimizeJavaJars) {
prefix = `minify-${prefix}`;
}
return `${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
const featurePrefix = await getFeaturePrefix(codeql, features, language);
return `${featurePrefix}${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
}
// src/diagnostics.ts

36
lib/init-action.js generated
View File

@@ -85266,9 +85266,10 @@ function getCachingKind(input) {
return "none" /* None */;
}
}
var cacheKeyHashLength = 16;
function createCacheKeyHash(components) {
const componentsJson = JSON.stringify(components);
return crypto.createHash("sha256").update(componentsJson).digest("hex").substring(0, 16);
return crypto.createHash("sha256").update(componentsJson).digest("hex").substring(0, cacheKeyHashLength);
}
function getDependencyCachingEnabled() {
const dependencyCaching = getOptionalInput("dependency-caching") || process.env["CODEQL_ACTION_DEPENDENCY_CACHING" /* DEPENDENCY_CACHING */];
@@ -87379,6 +87380,29 @@ async function cacheKey2(codeql, features, language, patterns) {
const hash = await glob.hashFiles(patterns.join("\n"));
return `${await cachePrefix2(codeql, features, language)}${hash}`;
}
async function getFeaturePrefix(codeql, features, language) {
const enabledFeatures = [];
const addFeatureIfEnabled = async (feature) => {
if (await features.getValue(feature, codeql)) {
enabledFeatures.push(feature);
}
};
if (language === "java" /* java */) {
const minimizeJavaJars = await features.getValue(
"java_minimize_dependency_jars" /* JavaMinimizeDependencyJars */,
codeql
);
if (minimizeJavaJars) {
return "minify-";
}
} else if (language === "csharp" /* csharp */) {
await addFeatureIfEnabled("csharp_new_cache_key" /* CsharpNewCacheKey */);
}
if (enabledFeatures.length > 0) {
return `${createCacheKeyHash(enabledFeatures)}-`;
}
return "";
}
async function cachePrefix2(codeql, features, language) {
const runnerOs = getRequiredEnvParam("RUNNER_OS");
const customPrefix = process.env["CODEQL_ACTION_DEPENDENCY_CACHE_PREFIX" /* DEPENDENCY_CACHING_PREFIX */];
@@ -87386,14 +87410,8 @@ async function cachePrefix2(codeql, features, language) {
if (customPrefix !== void 0 && customPrefix.length > 0) {
prefix = `${prefix}-${customPrefix}`;
}
const minimizeJavaJars = await features.getValue(
"java_minimize_dependency_jars" /* JavaMinimizeDependencyJars */,
codeql
);
if (language === "java" /* java */ && minimizeJavaJars) {
prefix = `minify-${prefix}`;
}
return `${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
const featurePrefix = await getFeaturePrefix(codeql, features, language);
return `${featurePrefix}${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
}
// src/diagnostics.ts

View File

@@ -73,6 +73,9 @@ export function getCachingKind(input: string | undefined): CachingKind {
}
}
// The length to which `createCacheKeyHash` truncates hash strings.
export const cacheKeyHashLength = 16;
/**
* Creates a SHA-256 hash of the cache key components to ensure uniqueness
* while keeping the cache key length manageable.
@@ -94,7 +97,7 @@ export function createCacheKeyHash(components: Record<string, any>): string {
.createHash("sha256")
.update(componentsJson)
.digest("hex")
.substring(0, 16);
.substring(0, cacheKeyHashLength);
}
/** Determines whether dependency caching is enabled. */

View File

@@ -0,0 +1,68 @@
import test from "ava";
// import * as sinon from "sinon";
import { cacheKeyHashLength } from "./caching-utils";
import { createStubCodeQL } from "./codeql";
import { getFeaturePrefix } from "./dependency-caching";
import { Feature } from "./feature-flags";
import { KnownLanguage } from "./languages";
import { setupTests, createFeatures } from "./testing-utils";
setupTests(test);
test("getFeaturePrefix - returns empty string if no features are enabled", async (t) => {
const codeql = createStubCodeQL({});
const features = createFeatures([]);
for (const knownLanguage of Object.values(KnownLanguage)) {
const result = await getFeaturePrefix(codeql, features, knownLanguage);
t.deepEqual(result, "", `Expected no feature prefix for ${knownLanguage}`);
}
});
test("getFeaturePrefix - Java - returns 'minify-' if JavaMinimizeDependencyJars is enabled", async (t) => {
const codeql = createStubCodeQL({});
const features = createFeatures([Feature.JavaMinimizeDependencyJars]);
const result = await getFeaturePrefix(codeql, features, KnownLanguage.java);
t.deepEqual(result, "minify-");
});
test("getFeaturePrefix - non-Java - returns '' if JavaMinimizeDependencyJars is enabled", async (t) => {
const codeql = createStubCodeQL({});
const features = createFeatures([Feature.JavaMinimizeDependencyJars]);
for (const knownLanguage of Object.values(KnownLanguage)) {
// Skip Java since we expect a result for it, which is tested in the previous test.
if (knownLanguage === KnownLanguage.java) {
continue;
}
const result = await getFeaturePrefix(codeql, features, knownLanguage);
t.deepEqual(result, "", `Expected no feature prefix for ${knownLanguage}`);
}
});
test("getFeaturePrefix - C# - returns prefix if CsharpNewCacheKey is enabled", async (t) => {
const codeql = createStubCodeQL({});
const features = createFeatures([Feature.CsharpNewCacheKey]);
const result = await getFeaturePrefix(codeql, features, KnownLanguage.csharp);
t.notDeepEqual(result, "");
t.assert(result.endsWith("-"));
// Check the length of the prefix, which should correspond to `cacheKeyHashLength` + 1 for the trailing `-`.
t.is(result.length, cacheKeyHashLength + 1);
});
test("getFeaturePrefix - non-C# - returns '' if CsharpNewCacheKey is enabled", async (t) => {
const codeql = createStubCodeQL({});
const features = createFeatures([Feature.CsharpNewCacheKey]);
for (const knownLanguage of Object.values(KnownLanguage)) {
// Skip C# since we expect a result for it, which is tested in the previous test.
if (knownLanguage === KnownLanguage.csharp) {
continue;
}
const result = await getFeaturePrefix(codeql, features, knownLanguage);
t.deepEqual(result, "", `Expected no feature prefix for ${knownLanguage}`);
}
});

View File

@@ -6,11 +6,11 @@ import * as glob from "@actions/glob";
import { getTemporaryDirectory } from "./actions-util";
import { listActionsCaches } from "./api-client";
import { getTotalCacheSize } from "./caching-utils";
import { createCacheKeyHash, getTotalCacheSize } from "./caching-utils";
import { CodeQL } from "./codeql";
import { Config } from "./config-utils";
import { EnvVar } from "./environment";
import { Feature, Features } from "./feature-flags";
import { Feature, FeatureEnablement, Features } from "./feature-flags";
import { KnownLanguage, Language } from "./languages";
import { Logger } from "./logging";
import { getErrorMessage, getRequiredEnvParam } from "./util";
@@ -442,6 +442,56 @@ async function cacheKey(
return `${await cachePrefix(codeql, features, language)}${hash}`;
}
/**
* If experimental features which the cache contents depend on are enabled for the current language,
* this function returns a prefix that uniquely identifies the set of enabled features. The purpose of
* this is to avoid restoring caches whose contents depended on experimental features, if those
* experimental features are later disabled.
*
* @param codeql The CodeQL instance.
* @param features Information about enabled features.
* @param language The language we are creating the key for.
*
* @returns A cache key prefix identifying the enabled, experimental features that the cache depends on.
*/
export async function getFeaturePrefix(
codeql: CodeQL,
features: FeatureEnablement,
language: Language,
): Promise<string> {
const enabledFeatures: Feature[] = [];
const addFeatureIfEnabled = async (feature: Feature) => {
if (await features.getValue(feature, codeql)) {
enabledFeatures.push(feature);
}
};
if (language === KnownLanguage.java) {
// To ensure a safe rollout of JAR minimization, we change the key when the feature is enabled.
const minimizeJavaJars = await features.getValue(
Feature.JavaMinimizeDependencyJars,
codeql,
);
// To maintain backwards compatibility with this, we return "minify-" instead of a hash.
if (minimizeJavaJars) {
return "minify-";
}
} else if (language === KnownLanguage.csharp) {
await addFeatureIfEnabled(Feature.CsharpNewCacheKey);
}
// If any features that affect the cache are enabled, return a feature prefix by
// computing a hash of the feature array.
if (enabledFeatures.length > 0) {
return `${createCacheKeyHash(enabledFeatures)}-`;
}
// No feature prefix.
return "";
}
/**
* Constructs a prefix for the cache key, comprised of a CodeQL-specific prefix, a version number that
* can be changed to invalidate old caches, the runner's operating system, and the specified language name.
@@ -464,16 +514,12 @@ async function cachePrefix(
prefix = `${prefix}-${customPrefix}`;
}
// To ensure a safe rollout of JAR minimization, we change the key when the feature is enabled.
const minimizeJavaJars = await features.getValue(
Feature.JavaMinimizeDependencyJars,
codeql,
);
if (language === KnownLanguage.java && minimizeJavaJars) {
prefix = `minify-${prefix}`;
}
// Calculate the feature prefix for the cache, if any. This is a hash that identifies
// experimental features that affect the cache contents.
const featurePrefix = await getFeaturePrefix(codeql, features, language);
return `${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
// Assemble the cache key.
return `${featurePrefix}${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
}
/** Represents information about our overall cache usage for CodeQL dependency caches. */