From ea535994369ef12c3a1eb8d089771380c96c23a2 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 16 Aug 2024 09:55:42 +0200 Subject: [PATCH 1/3] ci: Stabilize CI cache key Ensure it only changes if actual dependencies change. --- .../actions/install-dependencies/action.yml | 4 +- scripts/dependency-hash-key.js | 71 +++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 scripts/dependency-hash-key.js diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml index 8cb80ac7440e..5fbf87c67d79 100644 --- a/.github/actions/install-dependencies/action.yml +++ b/.github/actions/install-dependencies/action.yml @@ -9,11 +9,9 @@ outputs: runs: using: "composite" steps: - # we use a hash of yarn.lock as our cache key, because if it hasn't changed, our dependencies haven't changed, - # so no need to reinstall them - name: Compute dependency cache key id: compute_lockfile_hash - run: echo "hash=dependencies-${{ hashFiles('yarn.lock', 'packages/*/package.json', 'dev-packages/*/package.json') }}" >> "$GITHUB_OUTPUT" + run: node ./scripts/dependency-hash-key.js >> "$GITHUB_OUTPUT" shell: bash - name: Check dependency cache diff --git a/scripts/dependency-hash-key.js b/scripts/dependency-hash-key.js new file mode 100644 index 000000000000..5841df78d52e --- /dev/null +++ b/scripts/dependency-hash-key.js @@ -0,0 +1,71 @@ +const crypto = require('crypto'); +const fs = require('fs'); +const path = require('path'); + +/** + * Build a cache key for the dependencies of the monorepo. + * In addition to the content of the yarn.lock file, we also include + * dependencies of all workspace packages in the cache key. + * This ensures that we get a consistent cache key even if a dependency change does not affect + * the yarn.lock file. + */ +function outputDependencyCacheKey() { + const lockfileContent = fs.readFileSync(path.join(process.cwd(), 'yarn.lock'), 'utf8'); + + const hashParts = [lockfileContent]; + + const packageJson = require(path.join(process.cwd(), 'package.json')); + + const workspacePackages = packageJson.workspaces || []; + + // Get the (e.g. @sentry/browser) of all workspace packages + // we want to ignore their version numbers later + const workspacePackageNames = getWorkspacePackageNames(workspacePackages); + + // Add the dependencies of the workspace itself + hashParts.push(getNormalizedDependencies(packageJson, workspacePackageNames)); + + // Now for each workspace package, add the dependencies + workspacePackages.forEach(workspace => { + const packageJsonPath = path.join(process.cwd(), workspace, 'package.json'); + const packageJson = require(packageJsonPath); + hashParts.push(getNormalizedDependencies(packageJson, workspacePackageNames)); + }); + + const hash = crypto.createHash('md5').update(hashParts.join('\n')).digest('hex'); + // eslint-disable-next-line no-console + console.log(`hash=${hash}`); +} + +function getNormalizedDependencies(packageJson, workspacePackageNames) { + const { dependencies, devDependencies } = packageJson; + + const mergedDependencies = { + ...devDependencies, + ...dependencies, + }; + + const normalizedDependencies = {}; + + // Sort the keys to ensure a consistent order + Object.keys(mergedDependencies) + .sort() + .forEach(key => { + // If the dependency is a workspace package, ignore the version + // No need to invalidate a cache after every release + const version = workspacePackageNames.includes(key) ? '**workspace**' : mergedDependencies[key]; + normalizedDependencies[key] = version; + }); + + return JSON.stringify(normalizedDependencies); +} + +function getWorkspacePackageNames(workspacePackages) { + return workspacePackages.map(workspace => { + const packageJsonPath = path.join(process.cwd(), workspace, 'package.json'); + const packageJson = require(packageJsonPath); + return packageJson.name; + }); +} + +outputDependencyCacheKey(); From 2b724879c0938716a285e3bc37f45c7d177951eb Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 16 Aug 2024 10:00:34 +0200 Subject: [PATCH 2/3] prepend dependencies --- scripts/dependency-hash-key.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/dependency-hash-key.js b/scripts/dependency-hash-key.js index 5841df78d52e..20eb31f0815e 100644 --- a/scripts/dependency-hash-key.js +++ b/scripts/dependency-hash-key.js @@ -33,8 +33,10 @@ function outputDependencyCacheKey() { }); const hash = crypto.createHash('md5').update(hashParts.join('\n')).digest('hex'); + // We log the output in a way that the GitHub Actions can append it to the output + // We prefix it with `dependencies-` so it is easier to identify in the logs // eslint-disable-next-line no-console - console.log(`hash=${hash}`); + console.log(`hash=dependencies-${hash}`); } function getNormalizedDependencies(packageJson, workspacePackageNames) { From 25f8c8fd06854d28d06984cce6c84c5b24e53860 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 16 Aug 2024 10:18:01 +0200 Subject: [PATCH 3/3] Update scripts/dependency-hash-key.js Co-authored-by: Charly Gomez --- scripts/dependency-hash-key.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dependency-hash-key.js b/scripts/dependency-hash-key.js index 20eb31f0815e..55e38e4a385e 100644 --- a/scripts/dependency-hash-key.js +++ b/scripts/dependency-hash-key.js @@ -18,7 +18,7 @@ function outputDependencyCacheKey() { const workspacePackages = packageJson.workspaces || []; - // Get the (e.g. @sentry/browser) of all workspace packages + // Get the package name (e.g. @sentry/browser) of all workspace packages // we want to ignore their version numbers later const workspacePackageNames = getWorkspacePackageNames(workspacePackages);