mirror of
https://github.com/ruby/setup-ruby.git
synced 2026-09-13 22:44:20 +02:00
Implement automatic bundle install and caching
* Not yet enabled by default.
This commit is contained in:
@@ -39,6 +39,7 @@ jobs:
|
||||
- uses: ./
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
bundler-cache: true
|
||||
- run: ruby -v
|
||||
|
||||
- name: build compiler
|
||||
|
||||
@@ -116,8 +116,27 @@ Otherwise, the latest Bundler version is installed (except for Ruby 2.2 and 2.3
|
||||
|
||||
This behavior can be customized, see [action.yml](action.yml) for details about the `bundler` input.
|
||||
|
||||
### Caching `bundle install`
|
||||
### Caching `bundle install` automatically
|
||||
|
||||
This action provides a way to automatically run `bundle install` and cache the result:
|
||||
```yaml
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
```
|
||||
|
||||
This caching speeds up installing gems significantly and avoids too many requests to RubyGems.org.
|
||||
It needs a `Gemfile` under the [`working-directory`](#working-directory).
|
||||
The caching works whether there is a `Gemfile.lock` or not.
|
||||
If there is a `Gemfile.lock`, `bundle config --local deployment true` is used.
|
||||
|
||||
To perform caching, this action will use `bundle config --local path vendor/bundle`.
|
||||
Therefore, the Bundler `path` should not be changed in your workflow for the cache to work.
|
||||
|
||||
### Caching `bundle install` manually
|
||||
|
||||
You can also cache gems manually,
|
||||
but this is not recommended because it is verbose and very difficult to use a correct cache key.
|
||||
You can cache the installed gems with these two steps:
|
||||
|
||||
```yaml
|
||||
@@ -140,7 +159,8 @@ When using `.ruby-version`, replace `${{ matrix.ruby }}` with `${{ hashFiles('.r
|
||||
When using `.tool-versions`, replace `${{ matrix.ruby }}` with `${{ hashFiles('.tool-versions') }}`.
|
||||
|
||||
This uses the [cache action](https://github.com/actions/cache).
|
||||
The code above is a more complete version of the [Ruby - Bundler example](https://github.com/actions/cache/blob/master/examples.md#ruby---bundler).
|
||||
The code above is a more complete version of the [Ruby - Bundler example](https://github.com/actions/cache/blob/master/examples.md#ruby---\
|
||||
bundler).
|
||||
Make sure to include `use-ruby` in the `key` to avoid conflicting with previous caches.
|
||||
|
||||
### Working Directory
|
||||
|
||||
@@ -13,6 +13,10 @@ inputs:
|
||||
description: 'The version of Bundler to install. Either none, 1, 2, latest or Gemfile.lock. The default tries Gemfile.lock and otherwise uses latest.'
|
||||
required: false
|
||||
default: 'default'
|
||||
bundler-cache:
|
||||
description: 'Run "bundle install", and cache the result automatically. Either true or false.'
|
||||
required: false
|
||||
default: 'false'
|
||||
working-directory:
|
||||
description: 'The working directory to use for resolving paths for .ruby-version, .tool-versions and Gemfile.lock.'
|
||||
required: false
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
const util = require('util')
|
||||
const stream = require('stream')
|
||||
const crypto = require('crypto')
|
||||
const core = require('@actions/core')
|
||||
const { performance } = require('perf_hooks')
|
||||
|
||||
@@ -20,6 +23,14 @@ export function isHeadVersion(rubyVersion) {
|
||||
return rubyVersion === 'head' || rubyVersion === 'debug' || rubyVersion === 'mingw' || rubyVersion === 'mswin'
|
||||
}
|
||||
|
||||
export async function hashFile(file) {
|
||||
// See https://github.com/actions/runner/blob/master/src/Misc/expressionFunc/hashFiles/src/hashFiles.ts
|
||||
const hash = crypto.createHash('sha256')
|
||||
const pipeline = util.promisify(stream.pipeline)
|
||||
await pipeline(fs.createReadStream(file), hash)
|
||||
return hash.digest('hex')
|
||||
}
|
||||
|
||||
export function getVirtualEnvironmentName() {
|
||||
const platform = os.platform()
|
||||
if (platform === 'linux') {
|
||||
|
||||
+3751
-195
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,13 @@ const fs = require('fs')
|
||||
const path = require('path')
|
||||
const core = require('@actions/core')
|
||||
const exec = require('@actions/exec')
|
||||
const cache = require('@actions/cache')
|
||||
const common = require('./common')
|
||||
|
||||
const inputDefaults = {
|
||||
'ruby-version': 'default',
|
||||
'bundler': 'default',
|
||||
'bundler-cache': 'true',
|
||||
'working-directory': '.',
|
||||
}
|
||||
|
||||
@@ -50,6 +52,11 @@ export async function setupRuby(options) {
|
||||
if (inputs['bundler'] !== 'none') {
|
||||
await common.measure('Installing Bundler', async () =>
|
||||
installBundler(inputs['bundler'], platform, rubyPrefix, engine, version))
|
||||
|
||||
if (inputs['bundler-cache'] === 'true') {
|
||||
await common.measure('bundle install', async () =>
|
||||
bundleInstall(platform, engine, version))
|
||||
}
|
||||
}
|
||||
|
||||
core.setOutput('ruby-prefix', rubyPrefix)
|
||||
@@ -194,4 +201,81 @@ async function installBundler(bundlerVersionInput, platform, rubyPrefix, engine,
|
||||
}
|
||||
}
|
||||
|
||||
async function bundleInstall(platform, engine, version) {
|
||||
if (!fs.existsSync('Gemfile')) {
|
||||
console.log('No Gemfile, skipping "bundle install" and caching')
|
||||
return
|
||||
}
|
||||
|
||||
// config
|
||||
const path = 'vendor/bundle'
|
||||
const hasGemfileLock = fs.existsSync('Gemfile.lock');
|
||||
if (hasGemfileLock) {
|
||||
await exec.exec('bundle', ['config', '--local', 'deployment', 'true'])
|
||||
}
|
||||
await exec.exec('bundle', ['config', '--local', 'path', path])
|
||||
|
||||
// cache key
|
||||
const paths = [path]
|
||||
const baseKey = await computeBaseKey(platform, engine, version)
|
||||
let key = baseKey
|
||||
let restoreKeys
|
||||
if (hasGemfileLock) {
|
||||
key += `-Gemfile.lock-${await common.hashFile('Gemfile.lock')}`
|
||||
// If only Gemfile.lock we can reuse some of the cache (but it will keep old gem versions in the cache)
|
||||
restoreKeys = [`${baseKey}-Gemfile.lock-`]
|
||||
} else {
|
||||
// Only exact key, to never mix native gems of different platforms or Ruby versions
|
||||
restoreKeys = []
|
||||
}
|
||||
console.log(`Cache key: ${key}`)
|
||||
|
||||
// restore cache & install
|
||||
const cachedKey = await cache.restoreCache(paths, key, restoreKeys)
|
||||
if (cachedKey) {
|
||||
console.log(`Found cache for key: ${cachedKey}`)
|
||||
}
|
||||
|
||||
let alreadyInstalled = false
|
||||
if (cachedKey === key) {
|
||||
const exitCode = await exec.exec('bundle', ['check'], { ignoreReturnCode: true })
|
||||
alreadyInstalled = (exitCode === 0)
|
||||
}
|
||||
|
||||
if (!alreadyInstalled) {
|
||||
await exec.exec('bundle', ['install', '--jobs', '4'])
|
||||
|
||||
// Error handling from https://github.com/actions/cache/blob/master/src/save.ts
|
||||
console.log('Saving cache')
|
||||
try {
|
||||
await cache.saveCache(paths, key)
|
||||
} catch (error) {
|
||||
if (error.name === cache.ValidationError.name) {
|
||||
throw error;
|
||||
} else if (error.name === cache.ReserveCacheError.name) {
|
||||
core.info(error.message);
|
||||
} else {
|
||||
core.info(`[warning]${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function computeBaseKey(platform, engine, version) {
|
||||
let baseKey = `setup-ruby-bundle-install-${platform}-${engine}-${version}`
|
||||
if (engine === 'ruby' && common.isHeadVersion(version)) {
|
||||
let revision = '';
|
||||
await exec.exec('ruby', ['-e', 'print RUBY_REVISION'], {
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
revision += data.toString();
|
||||
}
|
||||
}
|
||||
});
|
||||
baseKey += `-revision-${revision}`
|
||||
}
|
||||
return baseKey
|
||||
}
|
||||
|
||||
if (__filename.endsWith('index.js')) { run() }
|
||||
|
||||
Reference in New Issue
Block a user