Remove toolchain directories from the cache

This commit is contained in:
Sergey Dolin 2023-10-25 08:53:26 +02:00
parent bfd2fb341f
commit 75d73b8577
13 changed files with 893 additions and 301 deletions

40
.github/workflows/toolchain.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: Validate 'setup-go'
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
schedule:
- cron: 0 0 * * *
jobs:
local-cache:
name: Setup local-cache version
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
go: [1.21]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: substitute go.mod with toolchain
run: |
cp __tests__/toolchain.go.mod go.mod
shell: bash
- name: setup-go ${{ matrix.go }}
uses: ./
with:
go-version: ${{ matrix.go }}
- name: verify go
run: __tests__/verify-go.sh ${{ matrix.go }}
shell: bash

View File

@ -3,6 +3,8 @@ import * as cache from '@actions/cache';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as cacheUtils from '../src/cache-utils'; import * as cacheUtils from '../src/cache-utils';
import {PackageManagerInfo} from '../src/package-managers'; import {PackageManagerInfo} from '../src/package-managers';
import fs, {ObjectEncodingOptions, PathLike} from 'fs';
import {getToolchainDirectoriesFromCachedDirectories} from '../src/cache-utils';
describe('getCommandOutput', () => { describe('getCommandOutput', () => {
//Arrange //Arrange
@ -209,3 +211,178 @@ describe('isCacheFeatureAvailable', () => {
expect(warningSpy).toHaveBeenCalledWith(warningMessage); expect(warningSpy).toHaveBeenCalledWith(warningMessage);
}); });
}); });
describe('parseGoModForToolchainVersion', () => {
const readFileSyncSpy = jest.spyOn(fs, 'readFileSync');
afterEach(() => {
jest.clearAllMocks();
});
it('should return null when go.mod file not exist', async () => {
//Arrange
//Act
const toolchainVersion = cacheUtils.parseGoModForToolchainVersion(
'/tmp/non/exist/foo.bar'
);
//Assert
expect(toolchainVersion).toBeNull();
});
it('should return null when go.mod file is empty', async () => {
//Arrange
readFileSyncSpy.mockImplementation(() => '');
//Act
const toolchainVersion = cacheUtils.parseGoModForToolchainVersion('go.mod');
//Assert
expect(toolchainVersion).toBeNull();
});
it('should return null when go.mod file does not contain toolchain version', async () => {
//Arrange
readFileSyncSpy.mockImplementation(() =>
`
module example-mod
go 1.21.0
require golang.org/x/tools v0.13.0
require (
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sys v0.12.0 // indirect
)
`.replace(/^\s+/gm, '')
);
//Act
const toolchainVersion = cacheUtils.parseGoModForToolchainVersion('go.mod');
//Assert
expect(toolchainVersion).toBeNull();
});
it('should return go version when go.mod file contains go version', () => {
//Arrange
readFileSyncSpy.mockImplementation(() =>
`
module example-mod
go 1.21.0
toolchain go1.21.1
require golang.org/x/tools v0.13.0
require (
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sys v0.12.0 // indirect
)
`.replace(/^\s+/gm, '')
);
//Act
const toolchainVersion = cacheUtils.parseGoModForToolchainVersion('go.mod');
//Assert
expect(toolchainVersion).toBe('1.21.1');
});
it('should return go version when go.mod file contains more than one go version', () => {
//Arrange
readFileSyncSpy.mockImplementation(() =>
`
module example-mod
go 1.21.0
toolchain go1.21.0
toolchain go1.21.1
require golang.org/x/tools v0.13.0
require (
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sys v0.12.0 // indirect
)
`.replace(/^\s+/gm, '')
);
//Act
const toolchainVersion = cacheUtils.parseGoModForToolchainVersion('go.mod');
//Assert
expect(toolchainVersion).toBe('1.21.1');
});
});
describe('getToolchainDirectoriesFromCachedDirectories', () => {
const readdirSyncSpy = jest.spyOn(fs, 'readdirSync');
const existsSyncSpy = jest.spyOn(fs, 'existsSync');
const lstatSync = jest.spyOn(fs, 'lstatSync');
afterEach(() => {
jest.clearAllMocks();
});
it('should return empty array when cacheDirectories is empty', async () => {
const toolcacheDirectories = getToolchainDirectoriesFromCachedDirectories(
'foo',
[]
);
expect(toolcacheDirectories).toEqual([]);
});
it('should return empty array when cacheDirectories does not contain /go/pkg', async () => {
readdirSyncSpy.mockImplementation(dir =>
[`${dir}1`, `${dir}2`, `${dir}3`].map(s => {
const de = new fs.Dirent();
de.name = s;
de.isDirectory = () => true;
return de;
})
);
const toolcacheDirectories = getToolchainDirectoriesFromCachedDirectories(
'1.1.1',
['foo', 'bar']
);
expect(toolcacheDirectories).toEqual([]);
});
it('should return empty array when cacheDirectories does not contain toolchain@v[0-9.]+-go{goVersion}', async () => {
readdirSyncSpy.mockImplementation(dir =>
[`${dir}1`, `${dir}2`, `${dir}3`].map(s => {
const de = new fs.Dirent();
de.name = s;
de.isDirectory = () => true;
return de;
})
);
const toolcacheDirectories = getToolchainDirectoriesFromCachedDirectories(
'foo',
['foo/go/pkg/mod', 'bar']
);
expect(toolcacheDirectories).toEqual([]);
});
it('should return one entry when cacheDirectories contains toolchain@v[0-9.]+-go{goVersion} in /pkg/mod', async () => {
let seqNo = 1;
readdirSyncSpy.mockImplementation(dir =>
[`toolchain@v0.0.1-go1.1.1.arch-${seqNo++}`].map(s => {
const de = new fs.Dirent();
de.name = s;
de.isDirectory = () => true;
return de;
})
);
existsSyncSpy.mockReturnValue(true);
// @ts-ignore - jest does not have relaxed mocks, so we ignore not-implemented methods
lstatSync.mockImplementation(() => ({isDirectory: () => true}));
const toolcacheDirectories = getToolchainDirectoriesFromCachedDirectories(
'1.1.1',
['/foo/go/pkg/mod', 'bar']
);
expect(toolcacheDirectories).toEqual([
'/foo/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.1.1.arch-1'
]);
});
});

View File

@ -0,0 +1,13 @@
module example-mod
go 1.21.0
toolchain go1.21.0
toolchain go1.21.1
require golang.org/x/tools v0.13.0
require (
golang.org/x/mod v0.12.0 // indirect
golang.org/x/sys v0.12.0 // indirect
)

52
__tests__/utils.test.ts Normal file
View File

@ -0,0 +1,52 @@
import {isSelfHosted} from '../src/utils';
describe('utils', () => {
describe('isSelfHosted', () => {
let AGENT_ISSELFHOSTED: string | undefined;
let RUNNER_ENVIRONMENT: string | undefined;
beforeEach(() => {
AGENT_ISSELFHOSTED = process.env['AGENT_ISSELFHOSTED'];
delete process.env['AGENT_ISSELFHOSTED'];
RUNNER_ENVIRONMENT = process.env['RUNNER_ENVIRONMENT'];
delete process.env['RUNNER_ENVIRONMENT'];
});
afterEach(() => {
if (AGENT_ISSELFHOSTED === undefined) {
delete process.env['AGENT_ISSELFHOSTED'];
} else {
process.env['AGENT_ISSELFHOSTED'] = AGENT_ISSELFHOSTED;
}
if (RUNNER_ENVIRONMENT === undefined) {
delete process.env['RUNNER_ENVIRONMENT'];
} else {
process.env['RUNNER_ENVIRONMENT'] = RUNNER_ENVIRONMENT;
}
});
it('isSelfHosted should be true if no environment variables set', () => {
expect(isSelfHosted()).toBeTruthy();
});
it('isSelfHosted should be true if environment variable is not set to denote GitHub hosted', () => {
process.env['RUNNER_ENVIRONMENT'] = 'some';
expect(isSelfHosted()).toBeTruthy();
});
it('isSelfHosted should be true if environment variable set to denote Azure Pipelines self hosted', () => {
process.env['AGENT_ISSELFHOSTED'] = '1';
expect(isSelfHosted()).toBeTruthy();
});
it('isSelfHosted should be false if environment variable set to denote GitHub hosted', () => {
process.env['RUNNER_ENVIRONMENT'] = 'github-hosted';
expect(isSelfHosted()).toBeFalsy();
});
it('isSelfHosted should be false if environment variable is not set to denote Azure Pipelines self hosted', () => {
process.env['AGENT_ISSELFHOSTED'] = 'some';
expect(isSelfHosted()).toBeFalsy();
});
});
});

View File

@ -58546,6 +58546,15 @@ const cachePackages = () => __awaiter(void 0, void 0, void 0, function* () {
core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`); core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`);
return; return;
} }
const toolchainVersion = core.getState(constants_1.State.ToolchainVersion);
// toolchainVersion is always null for self-hosted runners
if (toolchainVersion) {
const toolchainDirectories = cache_utils_1.getToolchainDirectoriesFromCachedDirectories(toolchainVersion, cachePaths);
toolchainDirectories.forEach(toolchainDirectory => {
core.warning(`Toolchain version ${toolchainVersion} will be removed from cache: ${toolchainDirectory}`);
fs_1.default.rmSync(toolchainDirectory, { recursive: true });
});
}
const cacheId = yield cache.saveCache(cachePaths, primaryKey); const cacheId = yield cache.saveCache(cachePaths, primaryKey);
if (cacheId === -1) { if (cacheId === -1) {
return; return;
@ -58594,12 +58603,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectoryPath = exports.getPackageManagerInfo = exports.getCommandOutput = void 0; exports.getToolchainDirectoriesFromCachedDirectories = exports.parseGoModForToolchainVersion = exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectoryPath = exports.getPackageManagerInfo = exports.getCommandOutput = void 0;
const cache = __importStar(__nccwpck_require__(7799)); const cache = __importStar(__nccwpck_require__(7799));
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const exec = __importStar(__nccwpck_require__(1514)); const exec = __importStar(__nccwpck_require__(1514));
const package_managers_1 = __nccwpck_require__(6663); const package_managers_1 = __nccwpck_require__(6663);
const fs_1 = __importDefault(__nccwpck_require__(7147));
const getCommandOutput = (toolCommand) => __awaiter(void 0, void 0, void 0, function* () { const getCommandOutput = (toolCommand) => __awaiter(void 0, void 0, void 0, function* () {
let { stdout, stderr, exitCode } = yield exec.getExecOutput(toolCommand, undefined, { ignoreReturnCode: true }); let { stdout, stderr, exitCode } = yield exec.getExecOutput(toolCommand, undefined, { ignoreReturnCode: true });
if (exitCode) { if (exitCode) {
@ -58654,6 +58667,42 @@ function isCacheFeatureAvailable() {
return false; return false;
} }
exports.isCacheFeatureAvailable = isCacheFeatureAvailable; exports.isCacheFeatureAvailable = isCacheFeatureAvailable;
function parseGoModForToolchainVersion(goModPath) {
try {
const goMod = fs_1.default.readFileSync(goModPath, 'utf8');
const matches = Array.from(goMod.matchAll(/^toolchain\s+go(\S+)/gm));
if (matches && matches.length > 0) {
return matches[matches.length - 1][1];
}
}
catch (error) {
if (error.message && error.message.startsWith('ENOENT')) {
core.warning(`go.mod file not found at ${goModPath}, can't parse toolchain version`);
return null;
}
throw error;
}
return null;
}
exports.parseGoModForToolchainVersion = parseGoModForToolchainVersion;
function isDirent(item) {
return item instanceof fs_1.default.Dirent;
}
function getToolchainDirectoriesFromCachedDirectories(goVersion, cacheDirectories) {
const re = new RegExp(`^toolchain@v[0-9.]+-go${goVersion}\\.`);
return (cacheDirectories
// This line should be replaced with separating the cache directory from build artefact directory
// see PoC PR: https://github.com/actions/setup-go/pull/426
// Till then, the workaround is expected to work in most cases, and it won't cause any harm
.filter(dir => dir.endsWith('/pkg/mod'))
.map(dir => `${dir}/golang.org`)
.flatMap(dir => fs_1.default
.readdirSync(dir)
.map(subdir => (isDirent(subdir) ? subdir.name : dir))
.filter(subdir => re.test(subdir))
.map(subdir => `${dir}/${subdir}`)));
}
exports.getToolchainDirectoriesFromCachedDirectories = getToolchainDirectoriesFromCachedDirectories;
/***/ }), /***/ }),
@ -58669,6 +58718,7 @@ var State;
(function (State) { (function (State) {
State["CachePrimaryKey"] = "CACHE_KEY"; State["CachePrimaryKey"] = "CACHE_KEY";
State["CacheMatchedKey"] = "CACHE_RESULT"; State["CacheMatchedKey"] = "CACHE_RESULT";
State["ToolchainVersion"] = "TOOLCACHE_VERSION";
})(State = exports.State || (exports.State = {})); })(State = exports.State || (exports.State = {}));
var Outputs; var Outputs;
(function (Outputs) { (function (Outputs) {

67
dist/setup/index.js vendored
View File

@ -61191,6 +61191,7 @@ const path_1 = __importDefault(__nccwpck_require__(1017));
const fs_1 = __importDefault(__nccwpck_require__(7147)); const fs_1 = __importDefault(__nccwpck_require__(7147));
const constants_1 = __nccwpck_require__(9042); const constants_1 = __nccwpck_require__(9042);
const cache_utils_1 = __nccwpck_require__(1678); const cache_utils_1 = __nccwpck_require__(1678);
const utils_1 = __nccwpck_require__(1314);
const restoreCache = (versionSpec, packageManager, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () { const restoreCache = (versionSpec, packageManager, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
const packageManagerInfo = yield cache_utils_1.getPackageManagerInfo(packageManager); const packageManagerInfo = yield cache_utils_1.getPackageManagerInfo(packageManager);
const platform = process.env.RUNNER_OS; const platform = process.env.RUNNER_OS;
@ -61198,6 +61199,15 @@ const restoreCache = (versionSpec, packageManager, cacheDependencyPath) => __awa
const dependencyFilePath = cacheDependencyPath const dependencyFilePath = cacheDependencyPath
? cacheDependencyPath ? cacheDependencyPath
: findDependencyFile(packageManagerInfo); : findDependencyFile(packageManagerInfo);
// In order to do not duplicate evaluation of dependency paths, we get
// toolchain Version here and pass to the saveCache via the state
if (!utils_1.isSelfHosted()) {
const toolchainVersion = cacheDependencyPath && path_1.default.basename(cacheDependencyPath) === 'go.mod'
? cache_utils_1.parseGoModForToolchainVersion(cacheDependencyPath)
: null;
toolchainVersion &&
core.saveState(constants_1.State.ToolchainVersion, toolchainVersion);
}
const fileHash = yield glob.hashFiles(dependencyFilePath); const fileHash = yield glob.hashFiles(dependencyFilePath);
if (!fileHash) { if (!fileHash) {
throw new Error('Some specified paths were not resolved, unable to cache dependencies.'); throw new Error('Some specified paths were not resolved, unable to cache dependencies.');
@ -61264,12 +61274,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectoryPath = exports.getPackageManagerInfo = exports.getCommandOutput = void 0; exports.getToolchainDirectoriesFromCachedDirectories = exports.parseGoModForToolchainVersion = exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectoryPath = exports.getPackageManagerInfo = exports.getCommandOutput = void 0;
const cache = __importStar(__nccwpck_require__(7799)); const cache = __importStar(__nccwpck_require__(7799));
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const exec = __importStar(__nccwpck_require__(1514)); const exec = __importStar(__nccwpck_require__(1514));
const package_managers_1 = __nccwpck_require__(6663); const package_managers_1 = __nccwpck_require__(6663);
const fs_1 = __importDefault(__nccwpck_require__(7147));
const getCommandOutput = (toolCommand) => __awaiter(void 0, void 0, void 0, function* () { const getCommandOutput = (toolCommand) => __awaiter(void 0, void 0, void 0, function* () {
let { stdout, stderr, exitCode } = yield exec.getExecOutput(toolCommand, undefined, { ignoreReturnCode: true }); let { stdout, stderr, exitCode } = yield exec.getExecOutput(toolCommand, undefined, { ignoreReturnCode: true });
if (exitCode) { if (exitCode) {
@ -61324,6 +61338,42 @@ function isCacheFeatureAvailable() {
return false; return false;
} }
exports.isCacheFeatureAvailable = isCacheFeatureAvailable; exports.isCacheFeatureAvailable = isCacheFeatureAvailable;
function parseGoModForToolchainVersion(goModPath) {
try {
const goMod = fs_1.default.readFileSync(goModPath, 'utf8');
const matches = Array.from(goMod.matchAll(/^toolchain\s+go(\S+)/gm));
if (matches && matches.length > 0) {
return matches[matches.length - 1][1];
}
}
catch (error) {
if (error.message && error.message.startsWith('ENOENT')) {
core.warning(`go.mod file not found at ${goModPath}, can't parse toolchain version`);
return null;
}
throw error;
}
return null;
}
exports.parseGoModForToolchainVersion = parseGoModForToolchainVersion;
function isDirent(item) {
return item instanceof fs_1.default.Dirent;
}
function getToolchainDirectoriesFromCachedDirectories(goVersion, cacheDirectories) {
const re = new RegExp(`^toolchain@v[0-9.]+-go${goVersion}\\.`);
return (cacheDirectories
// This line should be replaced with separating the cache directory from build artefact directory
// see PoC PR: https://github.com/actions/setup-go/pull/426
// Till then, the workaround is expected to work in most cases, and it won't cause any harm
.filter(dir => dir.endsWith('/pkg/mod'))
.map(dir => `${dir}/golang.org`)
.flatMap(dir => fs_1.default
.readdirSync(dir)
.map(subdir => (isDirent(subdir) ? subdir.name : dir))
.filter(subdir => re.test(subdir))
.map(subdir => `${dir}/${subdir}`)));
}
exports.getToolchainDirectoriesFromCachedDirectories = getToolchainDirectoriesFromCachedDirectories;
/***/ }), /***/ }),
@ -61339,6 +61389,7 @@ var State;
(function (State) { (function (State) {
State["CachePrimaryKey"] = "CACHE_KEY"; State["CachePrimaryKey"] = "CACHE_KEY";
State["CacheMatchedKey"] = "CACHE_RESULT"; State["CacheMatchedKey"] = "CACHE_RESULT";
State["ToolchainVersion"] = "TOOLCACHE_VERSION";
})(State = exports.State || (exports.State = {})); })(State = exports.State || (exports.State = {}));
var Outputs; var Outputs;
(function (Outputs) { (function (Outputs) {
@ -61495,8 +61546,7 @@ function cacheWindowsDir(extPath, tool, version, arch) {
if (os_1.default.platform() !== 'win32') if (os_1.default.platform() !== 'win32')
return false; return false;
// make sure the action runs in the hosted environment // make sure the action runs in the hosted environment
if (process.env['RUNNER_ENVIRONMENT'] !== 'github-hosted' && if (utils_1.isSelfHosted())
process.env['AGENT_ISSELFHOSTED'] === '1')
return false; return false;
const defaultToolCacheRoot = process.env['RUNNER_TOOL_CACHE']; const defaultToolCacheRoot = process.env['RUNNER_TOOL_CACHE'];
if (!defaultToolCacheRoot) if (!defaultToolCacheRoot)
@ -61975,12 +62025,21 @@ exports.getArch = getArch;
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.StableReleaseAlias = void 0; exports.isSelfHosted = exports.StableReleaseAlias = void 0;
var StableReleaseAlias; var StableReleaseAlias;
(function (StableReleaseAlias) { (function (StableReleaseAlias) {
StableReleaseAlias["Stable"] = "stable"; StableReleaseAlias["Stable"] = "stable";
StableReleaseAlias["OldStable"] = "oldstable"; StableReleaseAlias["OldStable"] = "oldstable";
})(StableReleaseAlias = exports.StableReleaseAlias || (exports.StableReleaseAlias = {})); })(StableReleaseAlias = exports.StableReleaseAlias || (exports.StableReleaseAlias = {}));
const isSelfHosted = () => process.env['AGENT_ISSELFHOSTED'] === '1' ||
(process.env['AGENT_ISSELFHOSTED'] === undefined &&
process.env['RUNNER_ENVIRONMENT'] !== 'github-hosted');
exports.isSelfHosted = isSelfHosted;
/* the above is simplified from:
process.env['RUNNER_ENVIRONMENT'] === undefined && process.env['AGENT_ISSELFHOSTED'] === '1'
||
process.env['AGENT_ISSELFHOSTED'] === undefined && process.env['RUNNER_ENVIRONMENT'] !== 'github-hosted'
*/
/***/ }), /***/ }),

684
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,12 @@ import fs from 'fs';
import {State, Outputs} from './constants'; import {State, Outputs} from './constants';
import {PackageManagerInfo} from './package-managers'; import {PackageManagerInfo} from './package-managers';
import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils'; import {
getCacheDirectoryPath,
getPackageManagerInfo,
parseGoModForToolchainVersion
} from './cache-utils';
import {isSelfHosted} from './utils';
export const restoreCache = async ( export const restoreCache = async (
versionSpec: string, versionSpec: string,
@ -21,6 +26,18 @@ export const restoreCache = async (
const dependencyFilePath = cacheDependencyPath const dependencyFilePath = cacheDependencyPath
? cacheDependencyPath ? cacheDependencyPath
: findDependencyFile(packageManagerInfo); : findDependencyFile(packageManagerInfo);
// In order to do not duplicate evaluation of dependency paths, we get
// toolchain Version here and pass to the saveCache via the state
if (!isSelfHosted()) {
const toolchainVersion =
cacheDependencyPath && path.basename(cacheDependencyPath) === 'go.mod'
? parseGoModForToolchainVersion(cacheDependencyPath)
: null;
toolchainVersion &&
core.saveState(State.ToolchainVersion, toolchainVersion);
}
const fileHash = await glob.hashFiles(dependencyFilePath); const fileHash = await glob.hashFiles(dependencyFilePath);
if (!fileHash) { if (!fileHash) {

View File

@ -2,7 +2,11 @@ import * as core from '@actions/core';
import * as cache from '@actions/cache'; import * as cache from '@actions/cache';
import fs from 'fs'; import fs from 'fs';
import {State} from './constants'; import {State} from './constants';
import {getCacheDirectoryPath, getPackageManagerInfo} from './cache-utils'; import {
getCacheDirectoryPath,
getPackageManagerInfo,
getToolchainDirectoriesFromCachedDirectories
} from './cache-utils';
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in // 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 // @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
@ -73,6 +77,21 @@ const cachePackages = async () => {
return; return;
} }
const toolchainVersion = core.getState(State.ToolchainVersion);
// toolchainVersion is always null for self-hosted runners
if (toolchainVersion) {
const toolchainDirectories = getToolchainDirectoriesFromCachedDirectories(
toolchainVersion,
cachePaths
);
toolchainDirectories.forEach(toolchainDirectory => {
core.warning(
`Toolchain version ${toolchainVersion} will be removed from cache: ${toolchainDirectory}`
);
fs.rmSync(toolchainDirectory, {recursive: true});
});
}
const cacheId = await cache.saveCache(cachePaths, primaryKey); const cacheId = await cache.saveCache(cachePaths, primaryKey);
if (cacheId === -1) { if (cacheId === -1) {
return; return;

View File

@ -2,6 +2,7 @@ import * as cache from '@actions/cache';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import {supportedPackageManagers, PackageManagerInfo} from './package-managers'; import {supportedPackageManagers, PackageManagerInfo} from './package-managers';
import fs from 'fs';
export const getCommandOutput = async (toolCommand: string) => { export const getCommandOutput = async (toolCommand: string) => {
let {stdout, stderr, exitCode} = await exec.getExecOutput( let {stdout, stderr, exitCode} = await exec.getExecOutput(
@ -83,3 +84,50 @@ export function isCacheFeatureAvailable(): boolean {
); );
return false; return false;
} }
export function parseGoModForToolchainVersion(
goModPath: string
): string | null {
try {
const goMod = fs.readFileSync(goModPath, 'utf8');
const matches = Array.from(goMod.matchAll(/^toolchain\s+go(\S+)/gm));
if (matches && matches.length > 0) {
return matches[matches.length - 1][1];
}
} catch (error) {
if (error.message && error.message.startsWith('ENOENT')) {
core.warning(
`go.mod file not found at ${goModPath}, can't parse toolchain version`
);
return null;
}
throw error;
}
return null;
}
function isDirent(item: fs.Dirent | string): item is fs.Dirent {
return item instanceof fs.Dirent;
}
export function getToolchainDirectoriesFromCachedDirectories(
goVersion: string,
cacheDirectories: string[]
): string[] {
const re = new RegExp(`^toolchain@v[0-9.]+-go${goVersion}\\.`);
return (
cacheDirectories
// This line should be replaced with separating the cache directory from build artefact directory
// see PoC PR: https://github.com/actions/setup-go/pull/426
// Till then, the workaround is expected to work in most cases, and it won't cause any harm
.filter(dir => dir.endsWith('/pkg/mod'))
.map(dir => `${dir}/golang.org`)
.flatMap(dir =>
fs
.readdirSync(dir)
.map(subdir => (isDirent(subdir) ? subdir.name : dir))
.filter(subdir => re.test(subdir))
.map(subdir => `${dir}/${subdir}`)
)
);
}

View File

@ -1,6 +1,7 @@
export enum State { export enum State {
CachePrimaryKey = 'CACHE_KEY', CachePrimaryKey = 'CACHE_KEY',
CacheMatchedKey = 'CACHE_RESULT' CacheMatchedKey = 'CACHE_RESULT',
ToolchainVersion = 'TOOLCACHE_VERSION'
} }
export enum Outputs { export enum Outputs {

View File

@ -6,7 +6,7 @@ import * as httpm from '@actions/http-client';
import * as sys from './system'; import * as sys from './system';
import fs from 'fs'; import fs from 'fs';
import os from 'os'; import os from 'os';
import {StableReleaseAlias} from './utils'; import {isSelfHosted, StableReleaseAlias} from './utils';
type InstallationType = 'dist' | 'manifest'; type InstallationType = 'dist' | 'manifest';
@ -175,11 +175,7 @@ async function cacheWindowsDir(
if (os.platform() !== 'win32') return false; if (os.platform() !== 'win32') return false;
// make sure the action runs in the hosted environment // make sure the action runs in the hosted environment
if ( if (isSelfHosted()) return false;
process.env['RUNNER_ENVIRONMENT'] !== 'github-hosted' &&
process.env['AGENT_ISSELFHOSTED'] === '1'
)
return false;
const defaultToolCacheRoot = process.env['RUNNER_TOOL_CACHE']; const defaultToolCacheRoot = process.env['RUNNER_TOOL_CACHE'];
if (!defaultToolCacheRoot) return false; if (!defaultToolCacheRoot) return false;

View File

@ -2,3 +2,13 @@ export enum StableReleaseAlias {
Stable = 'stable', Stable = 'stable',
OldStable = 'oldstable' OldStable = 'oldstable'
} }
export const isSelfHosted = (): boolean =>
process.env['AGENT_ISSELFHOSTED'] === '1' ||
(process.env['AGENT_ISSELFHOSTED'] === undefined &&
process.env['RUNNER_ENVIRONMENT'] !== 'github-hosted');
/* the above is simplified from:
process.env['RUNNER_ENVIRONMENT'] === undefined && process.env['AGENT_ISSELFHOSTED'] === '1'
||
process.env['AGENT_ISSELFHOSTED'] === undefined && process.env['RUNNER_ENVIRONMENT'] !== 'github-hosted'
*/