From a2c39006f2d0596026c37f46665187395c6fc34a Mon Sep 17 00:00:00 2001 From: Jakub Pavlik Date: Fri, 14 May 2021 18:13:28 +0200 Subject: [PATCH 1/2] cache-version input allowing a forced change of the cache key --- action.yml | 6 ++++++ bundler.js | 11 +++++++---- dist/index.js | 15 ++++++++++----- index.js | 3 ++- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/action.yml b/action.yml index 66aee82e8..14732f8a5 100644 --- a/action.yml +++ b/action.yml @@ -24,6 +24,12 @@ inputs: description: 'The working directory to use for resolving paths for .ruby-version, .tool-versions and Gemfile.lock.' required: false default: '.' + cache-version: + description: | + Arbitrary string that will be added to the cache key of the bundler cache. Set or change it if you need + to invalidate the cache. + required: false + default: '0' outputs: ruby-prefix: description: 'The prefix of the installed ruby' diff --git a/bundler.js b/bundler.js index 752819904..dfc371955 100644 --- a/bundler.js +++ b/bundler.js @@ -5,6 +5,8 @@ const exec = require('@actions/exec') const cache = require('@actions/cache') const common = require('./common') +export const DEFAULT_CACHE_VERSION = '0' + // The returned gemfile is guaranteed to exist, the lockfile might not exist export function detectGemfiles() { const gemfilePath = process.env['BUNDLE_GEMFILE'] || 'Gemfile' @@ -95,7 +97,7 @@ export async function installBundler(bundlerVersionInput, lockFile, platform, ru return bundlerVersion } -export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion) { +export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion, cacheVersion) { if (gemfile === null) { console.log('Could not determine gemfile path, skipping "bundle install" and caching') return false @@ -128,7 +130,7 @@ export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVer // cache key const paths = [cachePath] - const baseKey = await computeBaseKey(platform, engine, rubyVersion, lockFile) + const baseKey = await computeBaseKey(platform, engine, rubyVersion, lockFile, cacheVersion) const key = `${baseKey}-${await common.hashFile(lockFile)}` // If only Gemfile.lock changes we can reuse part of the cache, and clean old gem versions below const restoreKeys = [`${baseKey}-`] @@ -177,8 +179,9 @@ export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVer return true } -async function computeBaseKey(platform, engine, version, lockFile) { - let key = `setup-ruby-bundler-cache-v3-${platform}-${engine}-${version}` +async function computeBaseKey(platform, engine, version, lockFile, cacheVersion) { + const cacheVersionSuffix = DEFAULT_CACHE_VERSION === cacheVersion ? '' : `-cachever:${cacheVersion}` + let key = `setup-ruby-bundler-cache-v3-${platform}-${engine}-${version}${cacheVersionSuffix}` if (engine !== 'jruby' && common.isHeadVersion(version)) { let revision = ''; diff --git a/dist/index.js b/dist/index.js index 0fcf64bb3..3a7133f6e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -44537,6 +44537,7 @@ module.exports = require("net"); "use strict"; __webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DEFAULT_CACHE_VERSION", function() { return DEFAULT_CACHE_VERSION; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "detectGemfiles", function() { return detectGemfiles; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "installBundler", function() { return installBundler; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bundleInstall", function() { return bundleInstall; }); @@ -44547,6 +44548,8 @@ const exec = __webpack_require__(514) const cache = __webpack_require__(799) const common = __webpack_require__(390) +const DEFAULT_CACHE_VERSION = '0' + // The returned gemfile is guaranteed to exist, the lockfile might not exist function detectGemfiles() { const gemfilePath = process.env['BUNDLE_GEMFILE'] || 'Gemfile' @@ -44637,7 +44640,7 @@ async function installBundler(bundlerVersionInput, lockFile, platform, rubyPrefi return bundlerVersion } -async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion) { +async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion, cacheVersion) { if (gemfile === null) { console.log('Could not determine gemfile path, skipping "bundle install" and caching') return false @@ -44670,7 +44673,7 @@ async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, b // cache key const paths = [cachePath] - const baseKey = await computeBaseKey(platform, engine, rubyVersion, lockFile) + const baseKey = await computeBaseKey(platform, engine, rubyVersion, lockFile, cacheVersion) const key = `${baseKey}-${await common.hashFile(lockFile)}` // If only Gemfile.lock changes we can reuse part of the cache, and clean old gem versions below const restoreKeys = [`${baseKey}-`] @@ -44719,8 +44722,9 @@ async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, b return true } -async function computeBaseKey(platform, engine, version, lockFile) { - let key = `setup-ruby-bundler-cache-v3-${platform}-${engine}-${version}` +async function computeBaseKey(platform, engine, version, lockFile, cacheVersion) { + const cacheVersionSuffix = DEFAULT_CACHE_VERSION === cacheVersion ? '' : `-cachever:${cacheVersion}` + let key = `setup-ruby-bundler-cache-v3-${platform}-${engine}-${version}${cacheVersionSuffix}` if (engine !== 'jruby' && common.isHeadVersion(version)) { let revision = ''; @@ -52071,6 +52075,7 @@ const inputDefaults = { 'bundler': 'default', 'bundler-cache': 'true', 'working-directory': '.', + 'cache-version': bundler.DEFAULT_CACHE_VERSION, } // entry point when this action is run on its own @@ -52126,7 +52131,7 @@ async function setupRuby(options = {}) { if (inputs['bundler-cache'] === 'true') { await common.measure('bundle install', async () => - bundler.bundleInstall(gemfile, lockFile, platform, engine, version, bundlerVersion)) + bundler.bundleInstall(gemfile, lockFile, platform, engine, version, bundlerVersion, inputs['cache-version'])) } } diff --git a/index.js b/index.js index 3a3d888c9..cb2572a95 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ const inputDefaults = { 'bundler': 'default', 'bundler-cache': 'true', 'working-directory': '.', + 'cache-version': bundler.DEFAULT_CACHE_VERSION, } // entry point when this action is run on its own @@ -67,7 +68,7 @@ export async function setupRuby(options = {}) { if (inputs['bundler-cache'] === 'true') { await common.measure('bundle install', async () => - bundler.bundleInstall(gemfile, lockFile, platform, engine, version, bundlerVersion)) + bundler.bundleInstall(gemfile, lockFile, platform, engine, version, bundlerVersion, inputs['cache-version'])) } } From 6e7610366863b4fd7ab99edc9c76232de366b628 Mon Sep 17 00:00:00 2001 From: Jakub Pavlik Date: Fri, 14 May 2021 20:33:42 +0200 Subject: [PATCH 2/2] document cache-version --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 7a5cbfea6..5c3747893 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,20 @@ When there is no lockfile, one is generated with `bundle lock`, which is the sam In other words, it works exactly like `bundle install`. The hash of the generated lockfile is then used for caching, which is the only correct approach. +#### Dealing with a corrupted cache + +In some rare scenarios (like using gems with C extensions whose functionality depends on libraries found on the system +at the time of the gem's build) it may be necessary to ignore contents of the cache and get and build all the gems anew. +In order to achieve this, set the `cache-version` option to any value other than `0` (or change it to a new unique value +if you have already used it before.) + +```yaml + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + cache-version: 1 +``` + #### Caching `bundle install` manually It is also possible to cache gems manually, but this is not recommended because it is verbose and *very difficult* to do correctly.