feat: split Go module and build caches

Reuse downloaded modules across Go versions, runner architectures, and
runner images on the same OS instead of duplicating them in each build
cache archive.

Restore and save each entry independently, running operations in parallel
when safe. Serialize restores for overlapping or aliased paths, preserve
the original paths for the post step, and report cache-hit only when both
entries match their primary keys.

Update the documented manual restore keys and generated action bundles.
This commit is contained in:
qmuntal
2026-10-05 15:05:38 +02:00
parent 90ad2b35f6
commit ad9941188f
13 changed files with 1035 additions and 335 deletions
+48 -13
View File
@@ -6,13 +6,19 @@ import fs from 'fs';
import {State, Outputs} from './constants.js';
import {PackageManagerInfo} from './package-managers.js';
import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils.js';
import {
CacheEntry,
canRestoreCachesInParallel,
getCacheDirectoryPath,
getPackageManagerInfo
} from './cache-utils.js';
export const restoreCache = async (
versionSpec: string,
packageManager: string,
cacheDependencyPath?: string
) => {
core.setOutput(Outputs.CacheHit, false);
const packageManagerInfo = await getPackageManagerInfo(packageManager);
const platform = process.env.RUNNER_OS;
const arch = process.arch;
@@ -32,22 +38,51 @@ export const restoreCache = async (
const linuxVersion =
process.env.RUNNER_OS === 'Linux' ? `${process.env.ImageOS}-` : '';
const primaryKey = `setup-go-${platform}-${arch}-${linuxVersion}go-${versionSpec}-${fileHash}`;
core.debug(`primary key is ${primaryKey}`);
const primaryKeys = {
// Downloaded module sources can be reused across Go versions and runner
// architectures. Keep OS isolation for filesystem compatibility.
modules: `setup-go-modules-${platform}-${fileHash}`,
build: `setup-go-build-${platform}-${arch}-${linuxVersion}go-${versionSpec}-${fileHash}`
};
const entries: CacheEntry[] = cachePaths.map(directory => ({
...directory,
primaryKey: primaryKeys[directory.kind]
}));
core.saveState(State.CachePrimaryKey, primaryKey);
const restoreEntry = async (entry: CacheEntry) => {
core.debug(`${entry.kind} primary key is ${entry.primaryKey}`);
try {
entry.matchedKey = await cache.restoreCache(
[entry.path],
entry.primaryKey
);
if (entry.matchedKey) {
core.info(`${entry.kind} cache restored from key: ${entry.matchedKey}`);
} else {
core.info(`${entry.kind} cache is not found`);
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
core.warning(`Restore ${entry.kind} cache failed: ${message}`);
}
};
const cacheKey = await cache.restoreCache(cachePaths, primaryKey);
core.setOutput(Outputs.CacheHit, Boolean(cacheKey));
if (!cacheKey) {
core.info(`Cache is not found`);
core.setOutput(Outputs.CacheHit, false);
return;
if (canRestoreCachesInParallel(entries)) {
await Promise.all(entries.map(restoreEntry));
} else {
for (const entry of entries) {
await restoreEntry(entry);
}
}
core.saveState(State.CacheMatchedKey, cacheKey);
core.info(`Cache restored from key: ${cacheKey}`);
// Preserve the resolved paths so the post step uses the same cache versions,
// even if a later step changes the Go environment.
core.saveState(State.CacheEntries, entries);
core.setOutput(
Outputs.CacheHit,
entries.length === packageManagerInfo.cacheFolderCommandList.length &&
entries.every(entry => entry.matchedKey === entry.primaryKey)
);
};
const findDependencyFile = (packageManager: PackageManagerInfo) => {