Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PoC PR for next-gen caching #426

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions __tests__/cache-restore.test.ts
Expand Up @@ -41,7 +41,7 @@ describe('restoreCache', () => {

//Act + Assert
await expect(async () => {
await cacheRestore.restoreCache(
await cacheRestore.restoreModCache(
versionSpec,
packageManager,
cacheDependencyPath
Expand All @@ -66,7 +66,7 @@ describe('restoreCache', () => {
});

//Act + Assert
await cacheRestore.restoreCache(
await cacheRestore.restoreModCache(
versionSpec,
packageManager,
cacheDependencyPath
Expand All @@ -89,7 +89,7 @@ describe('restoreCache', () => {
});

//Act + Assert
await cacheRestore.restoreCache(
await cacheRestore.restoreModCache(
versionSpec,
packageManager,
cacheDependencyPath
Expand Down
21 changes: 18 additions & 3 deletions action.yml
Expand Up @@ -13,17 +13,32 @@ inputs:
description: Used to pull Go distributions from go-versions. Since there's a default, this is typically not supplied by the user. When running this action on github.com, the default value is sufficient. When running on GHES, you can pass a personal access token for github.com if you are experiencing rate limiting.
default: ${{ github.server_url == 'https://github.com' && github.token || '' }}
cache:
description: Used to specify whether caching is needed. Set to true, if you'd like to enable caching.
description: Used to specify whether caching is needed. Set to true, if you'd like to enable caching for both modules and intermediate build results.
default: true
cache-mod:
description: Used to specify whether modules caching is needed. Set to false, if you've found it increase the overall build time.
default: true
cache-build:
description: Used to specify whether caching of intermediate build files is needed. Set to false, if you've found it increase the overall build time.
default: true
cache-dependency-path:
description: 'Used to specify the path to a dependency file - go.sum'
description: 'Used to specify the path or glob pattern to a file(s) the caching of modules depends on, default: go.sum'
cache-build-path:
description: 'Used to specify the path or glob pattern to a file(s) that affect the caching of intermediate build results, default: **/*.go'
cache-id:
description: 'Used to modify cache ID if the parallel workflows must not share the same cache, default: none'
cache-lookup-only:
description: 'Use the cache created by another workflow, but do not update it'
default: false
architecture:
description: 'Target architecture for Go to use. Examples: x86, x64. Will use system architecture by default.'
outputs:
go-version:
description: 'The installed Go version. Useful when given a version range as input.'
cache-hit:
description: 'A boolean value to indicate if a cache was hit'
description: 'A boolean value to indicate if a modules cache was hit'
cache-build-hit:
description: 'A boolean value to indicate if a intermediate build results cache was hit'
runs:
using: 'node20'
main: 'dist/setup/index.js'
Expand Down
20 changes: 16 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -32,10 +32,12 @@
"@actions/http-client": "^2.0.1",
"@actions/io": "^1.0.2",
"@actions/tool-cache": "^1.5.5",
"lodash.memoize": "^4.1.2",
"semver": "^6.3.1"
},
"devDependencies": {
"@types/jest": "^27.0.2",
"@types/lodash": "^4.14.198",
"@types/node": "^16.11.25",
"@types/semver": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
Expand Down
60 changes: 51 additions & 9 deletions src/cache-restore.ts
Expand Up @@ -6,9 +6,10 @@ import fs from 'fs';

import {State, Outputs} from './constants';
import {PackageManagerInfo} from './package-managers';
import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils';
import {getBuildCachePath, getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils';
import {getInput} from "@actions/core";

export const restoreCache = async (
export const restoreModCache = async (
versionSpec: string,
packageManager: string,
cacheDependencyPath?: string
Expand All @@ -25,27 +26,68 @@ export const restoreCache = async (

if (!fileHash) {
throw new Error(
'Some specified paths were not resolved, unable to cache dependencies.'
'Some specified paths were not resolved, unable to cache modules.'
);
}

const linuxVersion =
process.env.RUNNER_OS === 'Linux' ? `${process.env.ImageOS}-` : '';
const primaryKey = `setup-go-${platform}-${linuxVersion}go-${versionSpec}-${fileHash}`;
core.debug(`primary key is ${primaryKey}`);
const cacheIdInput = getInput('cache-id')
const cacheId = cacheIdInput ? `${cacheIdInput}-` : ''
const primaryKey = `setup-go-${platform}-${linuxVersion}go-${versionSpec}-${cacheId}${fileHash}`;
core.debug(`Primary key for modules cache is ${primaryKey}`);

core.saveState(State.CachePrimaryKey, primaryKey);
core.saveState(State.CacheModPrimaryKey, primaryKey);

const cacheKey = await cache.restoreCache(cachePaths, primaryKey);
core.setOutput(Outputs.CacheHit, Boolean(cacheKey));
core.setOutput(Outputs.CacheModHit, Boolean(cacheKey));

if (!cacheKey) {
core.info(`Modules cache is not found`);
core.setOutput(Outputs.CacheModHit, false);
return;
}

core.saveState(State.CacheModMatchedKey, cacheKey);
core.info(`Modules cache restored from key: ${cacheKey}`);
};

export const restoreBuildCache = async (
versionSpec: string,
cacheBuildPath: string
) => {
const platform = process.env.RUNNER_OS;

const cachePath = await getBuildCachePath()

const fileHash = await glob.hashFiles(cacheBuildPath);

if (!fileHash) {
throw new Error(
`The paths ${cacheBuildPath} were not resolved, unable to cache intermediate build files.`
);
}

const linuxVersion =
process.env.RUNNER_OS === 'Linux' ? `${process.env.ImageOS}-` : '';
const cacheIdInput = getInput('cache-id')
const cacheId = cacheIdInput ? `${cacheIdInput}-` : ''
const keyPrefix = `setup-go-build-${platform}-${linuxVersion}go-${versionSpec}-${cacheId}`;
const primaryKey = `${keyPrefix}-${fileHash}`;
core.debug(`Primary key for intermediate build files cache is ${primaryKey}`);

core.saveState(State.CacheBuildPrimaryKey, primaryKey);

const cacheKey = await cache.restoreCache([cachePath], primaryKey, [keyPrefix]);
core.setOutput(Outputs.CacheBuildHit, Boolean(cacheKey));

if (!cacheKey) {
core.info(`Cache is not found`);
core.setOutput(Outputs.CacheHit, false);
core.setOutput(Outputs.CacheBuildHit, false);
return;
}

core.saveState(State.CacheMatchedKey, cacheKey);
core.saveState(State.CacheBuildMatchedKey, cacheKey);
core.info(`Cache restored from key: ${cacheKey}`);
};

Expand Down
61 changes: 56 additions & 5 deletions src/cache-save.ts
Expand Up @@ -2,7 +2,13 @@ import * as core from '@actions/core';
import * as cache from '@actions/cache';
import fs from 'fs';
import {State} from './constants';
import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils';
import {
getBuildCachePath,
getCacheDirectoryPath,
getPackageManagerInfo,
needBuildCache,
needModCache
} from './cache-utils';

// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
Expand All @@ -13,8 +19,11 @@ process.on('uncaughtException', e => {
});

export async function run() {
if (core.getInput('cache-lookup-only').toLowerCase() === 'true')
return;
try {
await cachePackages();
await cacheBuild();
} catch (error) {
let message = 'Unknown error!';
if (error instanceof Error) {
Expand All @@ -28,15 +37,15 @@ export async function run() {
}

const cachePackages = async () => {
const cacheInput = core.getBooleanInput('cache');
if (!cacheInput) {
const needCache = needModCache()
if (!needCache) {
return;
}

const packageManager = 'default';

const state = core.getState(State.CacheMatchedKey);
const primaryKey = core.getState(State.CachePrimaryKey);
const state = core.getState(State.CacheModMatchedKey);
const primaryKey = core.getState(State.CacheModPrimaryKey);

const packageManagerInfo = await getPackageManagerInfo(packageManager);

Expand Down Expand Up @@ -80,6 +89,48 @@ const cachePackages = async () => {
core.info(`Cache saved with the key: ${primaryKey}`);
};

const cacheBuild = async () => {
const needCache = needBuildCache()
if (!needCache) {
return;
}

const state = core.getState(State.CacheBuildMatchedKey);
const primaryKey = core.getState(State.CacheBuildPrimaryKey);

const cachePath = await getBuildCachePath()

if (!fs.existsSync(cachePath)) {
core.warning('There are no intermediate build files cache folders on the disk');
return;
}

if (!fs.existsSync(cachePath)) {
logWarning( `Cache folder path is retrieved but doesn't exist on disk: ${cachePath}` );
return;
}

if (!primaryKey) {
core.info(
'Primary key for intermediate build files cache was not generated. Please check the log messages above for more errors or information'
);
return;
}

if (primaryKey === state) {
core.info(
`Cache hit occurred on the primary key ${primaryKey} for intermediate build files cache, not saving cache.`
);
return;
}

const cacheId = await cache.saveCache([cachePath], primaryKey);
if (cacheId === -1) {
return;
}
core.info(`Cache saved with the key: ${primaryKey}`);
};

function logWarning(message: string): void {
const warningPrefix = '[warning]';
core.info(`${warningPrefix}${message}`);
Expand Down