Implementation of caching functionality for setup-go action (#228)

This commit is contained in:
IvanZosimov
2022-05-25 12:07:29 +02:00
committed by GitHub
parent fcdc43634a
commit b22fbbc292
72 changed files with 126791 additions and 8387 deletions

62
src/cache-restore.ts Normal file
View File

@ -0,0 +1,62 @@
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import * as glob from '@actions/glob';
import path from 'path';
import fs from 'fs';
import {State, Outputs} from './constants';
import {PackageManagerInfo} from './package-managers';
import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils';
export const restoreCache = async (
packageManager: string,
cacheDependencyPath?: string
) => {
const packageManagerInfo = await getPackageManagerInfo(packageManager);
const platform = process.env.RUNNER_OS;
const versionSpec = core.getInput('go-version');
const cachePaths = await getCacheDirectoryPath(packageManagerInfo);
const dependencyFilePath = cacheDependencyPath
? cacheDependencyPath
: findDependencyFile(packageManagerInfo);
const fileHash = await glob.hashFiles(dependencyFilePath);
if (!fileHash) {
throw new Error(
'Some specified paths were not resolved, unable to cache dependencies.'
);
}
const primaryKey = `setup-go-${platform}-go-${versionSpec}-${fileHash}`;
core.debug(`primary key is ${primaryKey}`);
core.saveState(State.CachePrimaryKey, primaryKey);
const cacheKey = await cache.restoreCache(cachePaths, primaryKey);
core.setOutput(Outputs.CacheHit, Boolean(cacheKey));
if (!cacheKey) {
core.info(`Cache is not found`);
return;
}
core.saveState(State.CacheMatchedKey, cacheKey);
core.info(`Cache restored from key: ${cacheKey}`);
};
const findDependencyFile = (packageManager: PackageManagerInfo) => {
let dependencyFile = packageManager.dependencyFilePattern;
const workspace = process.env.GITHUB_WORKSPACE!;
const rootContent = fs.readdirSync(workspace);
const goSumFileExists = rootContent.includes(dependencyFile);
if (!goSumFileExists) {
throw new Error(
`Dependencies file is not found in ${workspace}. Supported file pattern: ${dependencyFile}`
);
}
return path.join(workspace, dependencyFile);
};

80
src/cache-save.ts Normal file
View File

@ -0,0 +1,80 @@
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';
// 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
// throw an uncaught exception. Instead of failing this action, just warn.
process.on('uncaughtException', e => {
const warningPrefix = '[warning]';
core.info(`${warningPrefix}${e.message}`);
});
export async function run() {
try {
await cachePackages();
} catch (error) {
core.setFailed(error.message);
}
}
const cachePackages = async () => {
const cacheInput = core.getBooleanInput('cache');
if (!cacheInput) {
return;
}
const packageManager = 'default';
const state = core.getState(State.CacheMatchedKey);
const primaryKey = core.getState(State.CachePrimaryKey);
const packageManagerInfo = await getPackageManagerInfo(packageManager);
const cachePaths = await getCacheDirectoryPath(packageManagerInfo);
const nonExistingPaths = cachePaths.filter(
cachePath => !fs.existsSync(cachePath)
);
if (nonExistingPaths.length === cachePaths.length) {
throw new Error(`There are no cache folders on the disk`);
}
if (nonExistingPaths.length) {
logWarning(
`Cache folder path is retrieved but doesn't exist on disk: ${nonExistingPaths.join(
', '
)}`
);
}
if (primaryKey === state) {
core.info(
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
);
return;
}
try {
await cache.saveCache(cachePaths, primaryKey);
core.info(`Cache saved with the key: ${primaryKey}`);
} catch (error) {
if (error.name === cache.ValidationError.name) {
throw error;
} else if (error.name === cache.ReserveCacheError.name) {
core.info(error.message);
} else {
core.warning(`${error.message}`);
}
}
};
export function logWarning(message: string): void {
const warningPrefix = '[warning]';
core.info(`${warningPrefix}${message}`);
}
run();

75
src/cache-utils.ts Normal file
View File

@ -0,0 +1,75 @@
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import * as exec from '@actions/exec';
import {supportedPackageManagers, PackageManagerInfo} from './package-managers';
export const getCommandOutput = async (toolCommand: string) => {
let {stdout, stderr, exitCode} = await exec.getExecOutput(
toolCommand,
undefined,
{ignoreReturnCode: true}
);
if (exitCode) {
stderr = !stderr.trim()
? `The '${toolCommand}' command failed with exit code: ${exitCode}`
: stderr;
throw new Error(stderr);
}
return stdout.trim();
};
export const getPackageManagerInfo = async (packageManager: string) => {
if (!supportedPackageManagers[packageManager]) {
throw new Error(
`It's not possible to use ${packageManager}, please, check correctness of the package manager name spelling.`
);
}
const obtainedPackageManager = supportedPackageManagers[packageManager];
return obtainedPackageManager;
};
export const getCacheDirectoryPath = async (
packageManagerInfo: PackageManagerInfo
) => {
let pathList = await Promise.all(
packageManagerInfo.cacheFolderCommandList.map(command =>
getCommandOutput(command)
)
);
const emptyPaths = pathList.filter(item => !item);
if (emptyPaths.length) {
throw new Error(`Could not get cache folder paths.`);
}
return pathList;
};
export function isGhes(): boolean {
const ghUrl = new URL(
process.env['GITHUB_SERVER_URL'] || 'https://github.com'
);
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM';
}
export function isCacheFeatureAvailable(): boolean {
if (!cache.isFeatureAvailable()) {
if (isGhes()) {
throw new Error(
'Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.'
);
} else {
core.warning(
'The runner was not able to contact the cache service. Caching will be skipped'
);
}
return false;
}
return true;
}

8
src/constants.ts Normal file
View File

@ -0,0 +1,8 @@
export enum State {
CachePrimaryKey = 'CACHE_KEY',
CacheMatchedKey = 'CACHE_RESULT'
}
export enum Outputs {
CacheHit = 'cache-hit'
}

View File

@ -3,9 +3,10 @@ import * as io from '@actions/io';
import * as installer from './installer';
import * as semver from 'semver';
import path from 'path';
import {restoreCache} from './cache-restore';
import {isGhes, isCacheFeatureAvailable} from './cache-utils';
import cp from 'child_process';
import fs from 'fs';
import {URL} from 'url';
export async function run() {
try {
@ -15,6 +16,7 @@ export async function run() {
//
const versionSpec = resolveVersionInput();
const cache = core.getBooleanInput('cache');
core.info(`Setup go version spec ${versionSpec}`);
if (versionSpec) {
@ -39,8 +41,14 @@ export async function run() {
core.info(`Successfully set up Go version ${versionSpec}`);
}
if (cache && isCacheFeatureAvailable()) {
const packageManager = 'default';
const cacheDependencyPath = core.getInput('cache-dependency-path');
await restoreCache(packageManager, cacheDependencyPath);
}
// add problem matchers
const matchersPath = path.join(__dirname, '..', 'matchers.json');
const matchersPath = path.join(__dirname, '../..', 'matchers.json');
core.info(`##[add-matcher]${matchersPath}`);
// output the version actually being used
@ -90,13 +98,6 @@ export async function addBinToPath(): Promise<boolean> {
return added;
}
function isGhes(): boolean {
const ghUrl = new URL(
process.env['GITHUB_SERVER_URL'] || 'https://github.com'
);
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM';
}
export function parseGoVersion(versionString: string): string {
// get the installed version as an Action output
// based on go/src/cmd/go/internal/version/version.go:

15
src/package-managers.ts Normal file
View File

@ -0,0 +1,15 @@
type SupportedPackageManagers = {
[prop: string]: PackageManagerInfo;
};
export interface PackageManagerInfo {
dependencyFilePattern: string;
cacheFolderCommandList: string[];
}
export const supportedPackageManagers: SupportedPackageManagers = {
default: {
dependencyFilePattern: 'go.sum',
cacheFolderCommandList: ['go env GOMODCACHE', 'go env GOCACHE']
}
};