Skip to content

Commit e33196f

Browse files
authoredJun 27, 2023
Do not ivalidate the cache entirely on lock file change (#744)
* Do not ivalidate the cache entirely on yarn3 lock file change * Use cache prefix if all sub-projects are yarn managed * Rename functions & add e2e tests
1 parent c6722d3 commit e33196f

File tree

7 files changed

+358
-16
lines changed

7 files changed

+358
-16
lines changed
 

‎.github/workflows/e2e-cache.yml

+83-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ jobs:
146146
- uses: actions/checkout@v3
147147

148148
- name: prepare sub-projects
149-
run: __tests__/prepare-subprojects.sh
149+
run: __tests__/prepare-yarn-subprojects.sh yarn1
150150

151151
# expect
152152
# - no errors
@@ -161,3 +161,85 @@ jobs:
161161
cache-dependency-path: |
162162
**/*.lock
163163
yarn.lock
164+
165+
yarn-subprojects-berry-local:
166+
name: Test yarn subprojects all locally managed
167+
strategy:
168+
matrix:
169+
node-version: [12, 14, 16]
170+
runs-on: ubuntu-latest
171+
172+
steps:
173+
- uses: actions/checkout@v3
174+
175+
- name: prepare sub-projects
176+
run: __tests__/prepare-yarn-subprojects.sh
177+
178+
# expect
179+
# - no errors
180+
# - log
181+
# ##[info]All dependencies are managed locally by yarn3, the previous cache can be used
182+
# ##[debug]["node-cache-Linux-yarn-401024703386272f1a950c9f014cbb1bb79a7a5b6e1fb00e8b90d06734af41ee","node-cache-Linux-yarn"]
183+
- name: Setup Node
184+
uses: ./
185+
with:
186+
node-version: ${{ matrix.node-version }}
187+
cache: 'yarn'
188+
cache-dependency-path: |
189+
sub2/*.lock
190+
sub3/*.lock
191+
192+
yarn-subprojects-berry-global:
193+
name: Test yarn subprojects some locally managed
194+
strategy:
195+
matrix:
196+
node-version: [12, 14, 16]
197+
runs-on: ubuntu-latest
198+
199+
steps:
200+
- uses: actions/checkout@v3
201+
202+
- name: prepare sub-projects
203+
run: __tests__/prepare-yarn-subprojects.sh global
204+
205+
# expect
206+
# - no errors
207+
# - log must
208+
# ##[debug]"/home/runner/work/setup-node-test/setup-node-test/sub2" dependencies are managed by yarn 3 locally
209+
# ##[debug]"/home/runner/work/setup-node-test/setup-node-test/sub3" dependencies are not managed by yarn 3 locally
210+
- name: Setup Node
211+
uses: ./
212+
with:
213+
node-version: ${{ matrix.node-version }}
214+
cache: 'yarn'
215+
cache-dependency-path: |
216+
sub2/*.lock
217+
sub3/*.lock
218+
219+
yarn-subprojects-berry-git:
220+
name: Test yarn subprojects managed by git
221+
strategy:
222+
matrix:
223+
node-version: [12, 14, 16]
224+
runs-on: ubuntu-latest
225+
226+
steps:
227+
- uses: actions/checkout@v3
228+
229+
- name: prepare sub-projects
230+
run: /bin/bash __tests__/prepare-yarn-subprojects.sh keepcache
231+
232+
# expect
233+
# - no errors
234+
# - log
235+
# [debug]"/home/runner/work/setup-node-test/setup-node-test/sub2" has .yarn/cache - dependencies are kept in the repository
236+
# [debug]"/home/runner/work/setup-node-test/setup-node-test/sub3" has .yarn/cache - dependencies are kept in the repository
237+
# [debug]["node-cache-Linux-yarn-401024703386272f1a950c9f014cbb1bb79a7a5b6e1fb00e8b90d06734af41ee"]
238+
- name: Setup Node
239+
uses: ./
240+
with:
241+
node-version: ${{ matrix.node-version }}
242+
cache: 'yarn'
243+
cache-dependency-path: |
244+
sub2/*.lock
245+
sub3/*.lock

‎__tests__/cache-utils.test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
PackageManagerInfo,
77
isCacheFeatureAvailable,
88
supportedPackageManagers,
9-
getCommandOutput
9+
getCommandOutput,
10+
resetProjectDirectoriesMemoized
1011
} from '../src/cache-utils';
1112
import fs from 'fs';
1213
import * as cacheUtils from '../src/cache-utils';
@@ -103,6 +104,8 @@ describe('cache-utils', () => {
103104
(pattern: string): Promise<Globber> =>
104105
MockGlobber.create(['/foo', '/bar'])
105106
);
107+
108+
resetProjectDirectoriesMemoized();
106109
});
107110

108111
afterEach(() => {

‎__tests__/prepare-subprojects.sh ‎__tests__/prepare-yarn-subprojects.sh

+15-4
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,20 @@ cat <<EOT >package.json
3232
EOT
3333
yarn set version 3.5.1
3434
yarn install
35+
if [ x$1 = 'xglobal' ];then
36+
echo enableGlobalCache
37+
echo 'enableGlobalCache: true' >> .yarnrc.yml
38+
fi
3539

36-
echo "create yarn1 project in the root"
3740
cd ..
38-
cat <<EOT >package.json
41+
if [ x$1 != 'xkeepcache' -a x$2 != 'xkeepcache' ]; then
42+
rm -rf sub2/.yarn/cache
43+
rm -rf sub3/.yarn/cache
44+
fi
45+
46+
if [ x$1 = 'xyarn1' ];then
47+
echo "create yarn1 project in the root"
48+
cat <<EOT >package.json
3949
{
4050
"name": "subproject",
4151
"dependencies": {
@@ -44,5 +54,6 @@ cat <<EOT >package.json
4454
}
4555
}
4656
EOT
47-
yarn set version 1.22.19
48-
yarn install
57+
yarn set version 1.22.19
58+
yarn install
59+
fi

‎dist/cache-save/index.js

+69-2
Original file line numberDiff line numberDiff line change
@@ -60434,7 +60434,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
6043460434
return (mod && mod.__esModule) ? mod : { "default": mod };
6043560435
};
6043660436
Object.defineProperty(exports, "__esModule", ({ value: true }));
60437-
exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectories = exports.getPackageManagerInfo = exports.getCommandOutputNotEmpty = exports.getCommandOutput = exports.supportedPackageManagers = void 0;
60437+
exports.isCacheFeatureAvailable = exports.isGhes = exports.repoHasYarnBerryManagedDependencies = exports.getCacheDirectories = exports.resetProjectDirectoriesMemoized = exports.getPackageManagerInfo = exports.getCommandOutputNotEmpty = exports.getCommandOutput = exports.supportedPackageManagers = void 0;
6043860438
const core = __importStar(__nccwpck_require__(2186));
6043960439
const exec = __importStar(__nccwpck_require__(1514));
6044060440
const cache = __importStar(__nccwpck_require__(7799));
@@ -60503,6 +60503,19 @@ const getPackageManagerInfo = (packageManager) => __awaiter(void 0, void 0, void
6050360503
}
6050460504
});
6050560505
exports.getPackageManagerInfo = getPackageManagerInfo;
60506+
/**
60507+
* getProjectDirectoriesFromCacheDependencyPath is called twice during `restoreCache`
60508+
* - first through `getCacheDirectories`
60509+
* - second from `repoHasYarn3ManagedCache`
60510+
*
60511+
* it contains expensive IO operation and thus should be memoized
60512+
*/
60513+
let projectDirectoriesMemoized = null;
60514+
/**
60515+
* unit test must reset memoized variables
60516+
*/
60517+
const resetProjectDirectoriesMemoized = () => (projectDirectoriesMemoized = null);
60518+
exports.resetProjectDirectoriesMemoized = resetProjectDirectoriesMemoized;
6050660519
/**
6050760520
* Expands (converts) the string input `cache-dependency-path` to list of directories that
6050860521
* may be project roots
@@ -60511,6 +60524,9 @@ exports.getPackageManagerInfo = getPackageManagerInfo;
6051160524
* @return list of directories and possible
6051260525
*/
6051360526
const getProjectDirectoriesFromCacheDependencyPath = (cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
60527+
if (projectDirectoriesMemoized !== null) {
60528+
return projectDirectoriesMemoized;
60529+
}
6051460530
const globber = yield glob.create(cacheDependencyPath);
6051560531
const cacheDependenciesPaths = yield globber.glob();
6051660532
const existingDirectories = cacheDependenciesPaths
@@ -60519,6 +60535,7 @@ const getProjectDirectoriesFromCacheDependencyPath = (cacheDependencyPath) => __
6051960535
.filter(directory => fs_1.default.lstatSync(directory).isDirectory());
6052060536
if (!existingDirectories.length)
6052160537
core.warning(`No existing directories found containing cache-dependency-path="${cacheDependencyPath}"`);
60538+
projectDirectoriesMemoized = existingDirectories;
6052260539
return existingDirectories;
6052360540
});
6052460541
/**
@@ -60531,7 +60548,7 @@ const getProjectDirectoriesFromCacheDependencyPath = (cacheDependencyPath) => __
6053160548
const getCacheDirectoriesFromCacheDependencyPath = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
6053260549
const projectDirectories = yield getProjectDirectoriesFromCacheDependencyPath(cacheDependencyPath);
6053360550
const cacheFoldersPaths = yield Promise.all(projectDirectories.map((projectDirectory) => __awaiter(void 0, void 0, void 0, function* () {
60534-
const cacheFolderPath = packageManagerInfo.getCacheFolderPath(projectDirectory);
60551+
const cacheFolderPath = yield packageManagerInfo.getCacheFolderPath(projectDirectory);
6053560552
core.debug(`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"`);
6053660553
return cacheFolderPath;
6053760554
})));
@@ -60565,6 +60582,56 @@ const getCacheDirectories = (packageManagerInfo, cacheDependencyPath) => __await
6056560582
return getCacheDirectoriesForRootProject(packageManagerInfo);
6056660583
});
6056760584
exports.getCacheDirectories = getCacheDirectories;
60585+
/**
60586+
* A function to check if the directory is a yarn project configured to manage
60587+
* obsolete dependencies in the local cache
60588+
* @param directory - a path to the folder
60589+
* @return - true if the directory's project is yarn managed
60590+
* - if there's .yarn/cache folder do not mess with the dependencies kept in the repo, return false
60591+
* - global cache is not managed by yarn @see https://yarnpkg.com/features/offline-cache, return false
60592+
* - if local cache is not explicitly enabled (not yarn3), return false
60593+
* - return true otherwise
60594+
*/
60595+
const projectHasYarnBerryManagedDependencies = (directory) => __awaiter(void 0, void 0, void 0, function* () {
60596+
const workDir = directory || process.env.GITHUB_WORKSPACE || '.';
60597+
core.debug(`check if "${workDir}" has locally managed yarn3 dependencies`);
60598+
// if .yarn/cache directory exists the cache is managed by version control system
60599+
const yarnCacheFile = path_1.default.join(workDir, '.yarn', 'cache');
60600+
if (fs_1.default.existsSync(yarnCacheFile) &&
60601+
fs_1.default.lstatSync(yarnCacheFile).isDirectory()) {
60602+
core.debug(`"${workDir}" has .yarn/cache - dependencies are kept in the repository`);
60603+
return Promise.resolve(false);
60604+
}
60605+
// NOTE: yarn1 returns 'undefined' with return code = 0
60606+
const enableGlobalCache = yield exports.getCommandOutput('yarn config get enableGlobalCache', workDir);
60607+
// only local cache is not managed by yarn
60608+
const managed = enableGlobalCache.includes('false');
60609+
if (managed) {
60610+
core.debug(`"${workDir}" dependencies are managed by yarn 3 locally`);
60611+
return true;
60612+
}
60613+
else {
60614+
core.debug(`"${workDir}" dependencies are not managed by yarn 3 locally`);
60615+
return false;
60616+
}
60617+
});
60618+
/**
60619+
* A function to report the repo contains Yarn managed projects
60620+
* @param packageManagerInfo - used to make sure current package manager is yarn
60621+
* @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
60622+
* expected to be the result of `core.getInput('cache-dependency-path')`
60623+
* @return - true if all project directories configured to be Yarn managed
60624+
*/
60625+
const repoHasYarnBerryManagedDependencies = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
60626+
if (packageManagerInfo.name !== 'yarn')
60627+
return false;
60628+
const yarnDirs = cacheDependencyPath
60629+
? yield getProjectDirectoriesFromCacheDependencyPath(cacheDependencyPath)
60630+
: [''];
60631+
const isManagedList = yield Promise.all(yarnDirs.map(projectHasYarnBerryManagedDependencies));
60632+
return isManagedList.every(Boolean);
60633+
});
60634+
exports.repoHasYarnBerryManagedDependencies = repoHasYarnBerryManagedDependencies;
6056860635
function isGhes() {
6056960636
const ghUrl = new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com');
6057060637
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM';

‎dist/setup/index.js

+80-4
Original file line numberDiff line numberDiff line change
@@ -71153,10 +71153,19 @@ const restoreCache = (packageManager, cacheDependencyPath) => __awaiter(void 0,
7115371153
if (!fileHash) {
7115471154
throw new Error('Some specified paths were not resolved, unable to cache dependencies.');
7115571155
}
71156-
const primaryKey = `node-cache-${platform}-${packageManager}-${fileHash}`;
71156+
const keyPrefix = `node-cache-${platform}-${packageManager}`;
71157+
const primaryKey = `${keyPrefix}-${fileHash}`;
7115771158
core.debug(`primary key is ${primaryKey}`);
7115871159
core.saveState(constants_1.State.CachePrimaryKey, primaryKey);
71159-
const cacheKey = yield cache.restoreCache(cachePaths, primaryKey);
71160+
const isManagedByYarnBerry = yield cache_utils_1.repoHasYarnBerryManagedDependencies(packageManagerInfo, cacheDependencyPath);
71161+
let cacheKey;
71162+
if (isManagedByYarnBerry) {
71163+
core.info('All dependencies are managed locally by yarn3, the previous cache can be used');
71164+
cacheKey = yield cache.restoreCache(cachePaths, primaryKey, [keyPrefix]);
71165+
}
71166+
else {
71167+
cacheKey = yield cache.restoreCache(cachePaths, primaryKey);
71168+
}
7116071169
core.setOutput('cache-hit', Boolean(cacheKey));
7116171170
if (!cacheKey) {
7116271171
core.info(`${packageManager} cache is not found`);
@@ -71217,7 +71226,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
7121771226
return (mod && mod.__esModule) ? mod : { "default": mod };
7121871227
};
7121971228
Object.defineProperty(exports, "__esModule", ({ value: true }));
71220-
exports.isCacheFeatureAvailable = exports.isGhes = exports.getCacheDirectories = exports.getPackageManagerInfo = exports.getCommandOutputNotEmpty = exports.getCommandOutput = exports.supportedPackageManagers = void 0;
71229+
exports.isCacheFeatureAvailable = exports.isGhes = exports.repoHasYarnBerryManagedDependencies = exports.getCacheDirectories = exports.resetProjectDirectoriesMemoized = exports.getPackageManagerInfo = exports.getCommandOutputNotEmpty = exports.getCommandOutput = exports.supportedPackageManagers = void 0;
7122171230
const core = __importStar(__nccwpck_require__(2186));
7122271231
const exec = __importStar(__nccwpck_require__(1514));
7122371232
const cache = __importStar(__nccwpck_require__(7799));
@@ -71286,6 +71295,19 @@ const getPackageManagerInfo = (packageManager) => __awaiter(void 0, void 0, void
7128671295
}
7128771296
});
7128871297
exports.getPackageManagerInfo = getPackageManagerInfo;
71298+
/**
71299+
* getProjectDirectoriesFromCacheDependencyPath is called twice during `restoreCache`
71300+
* - first through `getCacheDirectories`
71301+
* - second from `repoHasYarn3ManagedCache`
71302+
*
71303+
* it contains expensive IO operation and thus should be memoized
71304+
*/
71305+
let projectDirectoriesMemoized = null;
71306+
/**
71307+
* unit test must reset memoized variables
71308+
*/
71309+
const resetProjectDirectoriesMemoized = () => (projectDirectoriesMemoized = null);
71310+
exports.resetProjectDirectoriesMemoized = resetProjectDirectoriesMemoized;
7128971311
/**
7129071312
* Expands (converts) the string input `cache-dependency-path` to list of directories that
7129171313
* may be project roots
@@ -71294,6 +71316,9 @@ exports.getPackageManagerInfo = getPackageManagerInfo;
7129471316
* @return list of directories and possible
7129571317
*/
7129671318
const getProjectDirectoriesFromCacheDependencyPath = (cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
71319+
if (projectDirectoriesMemoized !== null) {
71320+
return projectDirectoriesMemoized;
71321+
}
7129771322
const globber = yield glob.create(cacheDependencyPath);
7129871323
const cacheDependenciesPaths = yield globber.glob();
7129971324
const existingDirectories = cacheDependenciesPaths
@@ -71302,6 +71327,7 @@ const getProjectDirectoriesFromCacheDependencyPath = (cacheDependencyPath) => __
7130271327
.filter(directory => fs_1.default.lstatSync(directory).isDirectory());
7130371328
if (!existingDirectories.length)
7130471329
core.warning(`No existing directories found containing cache-dependency-path="${cacheDependencyPath}"`);
71330+
projectDirectoriesMemoized = existingDirectories;
7130571331
return existingDirectories;
7130671332
});
7130771333
/**
@@ -71314,7 +71340,7 @@ const getProjectDirectoriesFromCacheDependencyPath = (cacheDependencyPath) => __
7131471340
const getCacheDirectoriesFromCacheDependencyPath = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
7131571341
const projectDirectories = yield getProjectDirectoriesFromCacheDependencyPath(cacheDependencyPath);
7131671342
const cacheFoldersPaths = yield Promise.all(projectDirectories.map((projectDirectory) => __awaiter(void 0, void 0, void 0, function* () {
71317-
const cacheFolderPath = packageManagerInfo.getCacheFolderPath(projectDirectory);
71343+
const cacheFolderPath = yield packageManagerInfo.getCacheFolderPath(projectDirectory);
7131871344
core.debug(`${packageManagerInfo.name}'s cache folder "${cacheFolderPath}" configured for the directory "${projectDirectory}"`);
7131971345
return cacheFolderPath;
7132071346
})));
@@ -71348,6 +71374,56 @@ const getCacheDirectories = (packageManagerInfo, cacheDependencyPath) => __await
7134871374
return getCacheDirectoriesForRootProject(packageManagerInfo);
7134971375
});
7135071376
exports.getCacheDirectories = getCacheDirectories;
71377+
/**
71378+
* A function to check if the directory is a yarn project configured to manage
71379+
* obsolete dependencies in the local cache
71380+
* @param directory - a path to the folder
71381+
* @return - true if the directory's project is yarn managed
71382+
* - if there's .yarn/cache folder do not mess with the dependencies kept in the repo, return false
71383+
* - global cache is not managed by yarn @see https://yarnpkg.com/features/offline-cache, return false
71384+
* - if local cache is not explicitly enabled (not yarn3), return false
71385+
* - return true otherwise
71386+
*/
71387+
const projectHasYarnBerryManagedDependencies = (directory) => __awaiter(void 0, void 0, void 0, function* () {
71388+
const workDir = directory || process.env.GITHUB_WORKSPACE || '.';
71389+
core.debug(`check if "${workDir}" has locally managed yarn3 dependencies`);
71390+
// if .yarn/cache directory exists the cache is managed by version control system
71391+
const yarnCacheFile = path_1.default.join(workDir, '.yarn', 'cache');
71392+
if (fs_1.default.existsSync(yarnCacheFile) &&
71393+
fs_1.default.lstatSync(yarnCacheFile).isDirectory()) {
71394+
core.debug(`"${workDir}" has .yarn/cache - dependencies are kept in the repository`);
71395+
return Promise.resolve(false);
71396+
}
71397+
// NOTE: yarn1 returns 'undefined' with return code = 0
71398+
const enableGlobalCache = yield exports.getCommandOutput('yarn config get enableGlobalCache', workDir);
71399+
// only local cache is not managed by yarn
71400+
const managed = enableGlobalCache.includes('false');
71401+
if (managed) {
71402+
core.debug(`"${workDir}" dependencies are managed by yarn 3 locally`);
71403+
return true;
71404+
}
71405+
else {
71406+
core.debug(`"${workDir}" dependencies are not managed by yarn 3 locally`);
71407+
return false;
71408+
}
71409+
});
71410+
/**
71411+
* A function to report the repo contains Yarn managed projects
71412+
* @param packageManagerInfo - used to make sure current package manager is yarn
71413+
* @param cacheDependencyPath - either a single string or multiline string with possible glob patterns
71414+
* expected to be the result of `core.getInput('cache-dependency-path')`
71415+
* @return - true if all project directories configured to be Yarn managed
71416+
*/
71417+
const repoHasYarnBerryManagedDependencies = (packageManagerInfo, cacheDependencyPath) => __awaiter(void 0, void 0, void 0, function* () {
71418+
if (packageManagerInfo.name !== 'yarn')
71419+
return false;
71420+
const yarnDirs = cacheDependencyPath
71421+
? yield getProjectDirectoriesFromCacheDependencyPath(cacheDependencyPath)
71422+
: [''];
71423+
const isManagedList = yield Promise.all(yarnDirs.map(projectHasYarnBerryManagedDependencies));
71424+
return isManagedList.every(Boolean);
71425+
});
71426+
exports.repoHasYarnBerryManagedDependencies = repoHasYarnBerryManagedDependencies;
7135171427
function isGhes() {
7135271428
const ghUrl = new URL(process.env['GITHUB_SERVER_URL'] || 'https://github.com');
7135371429
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM';

0 commit comments

Comments
 (0)
Failed to load comments.