From d195ecb9ecf4fcc2450edece71364b3c7e12e4f4 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Fri, 8 Jan 2021 20:56:31 +0100 Subject: [PATCH] Move Bundler-related logic to a separate file --- bundler.js | 184 +++++++++++++++++ dist/index.js | 535 ++++++++++++++++++++++++++------------------------ index.js | 187 +----------------- 3 files changed, 463 insertions(+), 443 deletions(-) create mode 100644 bundler.js diff --git a/bundler.js b/bundler.js new file mode 100644 index 0000000..a67f2ed --- /dev/null +++ b/bundler.js @@ -0,0 +1,184 @@ +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') + +// The returned gemfile is guaranteed to exist, the lockfile might not exist +export function detectGemfiles() { + const gemfilePath = process.env['BUNDLE_GEMFILE'] || 'Gemfile' + if (fs.existsSync(gemfilePath)) { + return [gemfilePath, `${gemfilePath}.lock`] + } else if (process.env['BUNDLE_GEMFILE']) { + throw new Error(`$BUNDLE_GEMFILE is set to ${gemfilePath} but does not exist`) + } + + if (fs.existsSync("gems.rb")) { + return ["gems.rb", "gems.locked"] + } + + return [null, null] +} + +function readBundledWithFromGemfileLock(lockFile) { + if (lockFile !== null && fs.existsSync(lockFile)) { + const contents = fs.readFileSync(lockFile, 'utf8') + const lines = contents.split(/\r?\n/) + const bundledWithLine = lines.findIndex(line => /^BUNDLED WITH$/.test(line.trim())) + if (bundledWithLine !== -1) { + const nextLine = lines[bundledWithLine+1] + if (nextLine && /^\d+/.test(nextLine.trim())) { + const bundlerVersion = nextLine.trim() + console.log(`Using Bundler ${bundlerVersion} from ${lockFile} BUNDLED WITH ${bundlerVersion}`) + return bundlerVersion + } + } + } + return null +} + +export async function installBundler(bundlerVersionInput, lockFile, platform, rubyPrefix, engine, rubyVersion) { + let bundlerVersion = bundlerVersionInput + + if (bundlerVersion === 'default' || bundlerVersion === 'Gemfile.lock') { + bundlerVersion = readBundledWithFromGemfileLock(lockFile) + + if (!bundlerVersion) { + bundlerVersion = 'latest' + } + } + + if (bundlerVersion === 'latest') { + bundlerVersion = '2' + } + + if (/^\d+/.test(bundlerVersion)) { + // OK + } else { + throw new Error(`Cannot parse bundler input: ${bundlerVersion}`) + } + + if (engine === 'ruby' && rubyVersion.match(/^2\.[012]/)) { + console.log('Bundler 2 requires Ruby 2.3+, using Bundler 1 on Ruby <= 2.2') + bundlerVersion = '1' + } else if (engine === 'ruby' && rubyVersion.startsWith('2.3')) { + console.log('Ruby 2.3 has a bug with Bundler 2 (https://github.com/rubygems/rubygems/issues/3570), using Bundler 1 instead on Ruby 2.3') + bundlerVersion = '1' + } else if (engine === 'jruby' && rubyVersion.startsWith('9.1')) { // JRuby 9.1 targets Ruby 2.3, treat it the same + console.log('JRuby 9.1 has a bug with Bundler 2 (https://github.com/ruby/setup-ruby/issues/108), using Bundler 1 instead on JRuby 9.1') + bundlerVersion = '1' + } + + if (common.isHeadVersion(rubyVersion) && common.isBundler2Default(engine, rubyVersion) && bundlerVersion.startsWith('2')) { + console.log(`Using Bundler 2 shipped with ${engine}-${rubyVersion}`) + } else if (engine === 'truffleruby' && !common.isHeadVersion(rubyVersion) && bundlerVersion.startsWith('1')) { + console.log(`Using Bundler 1 shipped with ${engine}`) + } else { + const gem = path.join(rubyPrefix, 'bin', 'gem') + const bundlerVersionConstraint = bundlerVersion.match(/^\d+\.\d+\.\d+/) ? bundlerVersion : `~> ${bundlerVersion}` + await exec.exec(gem, ['install', 'bundler', '-v', bundlerVersionConstraint, '--no-document']) + } + + return bundlerVersion +} + +export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion) { + if (gemfile === null) { + console.log('Could not determine gemfile path, skipping "bundle install" and caching') + return false + } + + let envOptions = {} + if (bundlerVersion.startsWith('1') && common.isBundler2Default(engine, rubyVersion)) { + // If Bundler 1 is specified on Rubies which ship with Bundler 2, + // we need to specify which Bundler version to use explicitly until the lockfile exists. + console.log(`Setting BUNDLER_VERSION=${bundlerVersion} for "bundle config|lock" commands below to ensure Bundler 1 is used`) + envOptions = { env: { ...process.env, BUNDLER_VERSION: bundlerVersion } } + } + + // config + const cachePath = 'vendor/bundle' + // An absolute path, so it is reliably under $PWD/vendor/bundle, and not relative to the gemfile's directory + const bundleCachePath = path.join(process.cwd(), cachePath) + + await exec.exec('bundle', ['config', '--local', 'path', bundleCachePath], envOptions) + + if (fs.existsSync(lockFile)) { + await exec.exec('bundle', ['config', '--local', 'deployment', 'true'], envOptions) + } else { + // Generate the lockfile so we can use it to compute the cache key. + // This will also automatically pick up the latest gem versions compatible with the Gemfile. + await exec.exec('bundle', ['lock'], envOptions) + } + + // cache key + const paths = [cachePath] + const baseKey = await computeBaseKey(platform, engine, rubyVersion, lockFile) + 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}-`] + console.log(`Cache key: ${key}`) + + // restore cache & install + let cachedKey = null + try { + cachedKey = await cache.restoreCache(paths, key, restoreKeys) + } catch (error) { + if (error.name === cache.ValidationError.name) { + throw error; + } else { + core.info(`[warning] There was an error restoring the cache ${error.message}`) + } + } + + if (cachedKey) { + console.log(`Found cache for key: ${cachedKey}`) + } + + // Always run 'bundle install' to list the gems + await exec.exec('bundle', ['install', '--jobs', '4']) + + // @actions/cache only allows to save for non-existing keys + if (cachedKey !== key) { + if (cachedKey) { // existing cache but Gemfile.lock differs, clean old gems + await exec.exec('bundle', ['clean']) + } + + // 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}`) + } + } + } + + return true +} + +async function computeBaseKey(platform, engine, version, lockFile) { + let key = `setup-ruby-bundler-cache-v3-${platform}-${engine}-${version}` + + if (engine !== 'jruby' && common.isHeadVersion(version)) { + let revision = ''; + await exec.exec('ruby', ['-e', 'print RUBY_REVISION'], { + silent: true, + listeners: { + stdout: (data) => { + revision += data.toString(); + } + } + }); + key += `-revision-${revision}` + } + + key += `-${lockFile}` + return key +} diff --git a/dist/index.js b/dist/index.js index 84eb1ff..164b481 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3029,7 +3029,7 @@ var os = __webpack_require__(87); var stream = __webpack_require__(413); var fs = __webpack_require__(747); var util = __webpack_require__(669); -var crypto = __webpack_require__(373); +var crypto = __webpack_require__(417); var events = __webpack_require__(614); var coreTracing = __webpack_require__(175); var coreLro = __webpack_require__(94); @@ -23220,7 +23220,7 @@ Object.defineProperty(exports, "__esModule", { }); exports.default = void 0; -var _crypto = _interopRequireDefault(__webpack_require__(373)); +var _crypto = _interopRequireDefault(__webpack_require__(417)); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -28783,7 +28783,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(__webpack_require__(186)); const http_client_1 = __webpack_require__(320); const auth_1 = __webpack_require__(93); -const crypto = __importStar(__webpack_require__(373)); +const crypto = __importStar(__webpack_require__(417)); const fs = __importStar(__webpack_require__(747)); const url_1 = __webpack_require__(835); const utils = __importStar(__webpack_require__(518)); @@ -32589,12 +32589,7 @@ exports.Store = Store; /* 370 */, /* 371 */, /* 372 */, -/* 373 */ -/***/ (function(module) { - -module.exports = require("crypto"); - -/***/ }), +/* 373 */, /* 374 */, /* 375 */, /* 376 */ @@ -32748,7 +32743,7 @@ Object.defineProperty(exports, "__esModule", { }); exports.default = void 0; -var _crypto = _interopRequireDefault(__webpack_require__(373)); +var _crypto = _interopRequireDefault(__webpack_require__(417)); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } @@ -32800,7 +32795,7 @@ const path = __webpack_require__(622) const fs = __webpack_require__(747) const util = __webpack_require__(669) const stream = __webpack_require__(413) -const crypto = __webpack_require__(373) +const crypto = __webpack_require__(417) const core = __webpack_require__(186) const { performance } = __webpack_require__(630) @@ -33198,67 +33193,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de /* 417 */ /***/ (function(module) { -"use strict"; - -module.exports = balanced; -function balanced(a, b, str) { - if (a instanceof RegExp) a = maybeMatch(a, str); - if (b instanceof RegExp) b = maybeMatch(b, str); - - var r = range(a, b, str); - - return r && { - start: r[0], - end: r[1], - pre: str.slice(0, r[0]), - body: str.slice(r[0] + a.length, r[1]), - post: str.slice(r[1] + b.length) - }; -} - -function maybeMatch(reg, str) { - var m = str.match(reg); - return m ? m[0] : null; -} - -balanced.range = range; -function range(a, b, str) { - var begs, beg, left, right, result; - var ai = str.indexOf(a); - var bi = str.indexOf(b, ai + 1); - var i = ai; - - if (ai >= 0 && bi > 0) { - begs = []; - left = str.length; - - while (i >= 0 && !result) { - if (i == ai) { - begs.push(i); - ai = str.indexOf(a, i + 1); - } else if (begs.length == 1) { - result = [ begs.pop(), bi ]; - } else { - beg = begs.pop(); - if (beg < left) { - left = beg; - right = bi; - } - - bi = str.indexOf(b, i + 1); - } - - i = ai < bi && ai >= 0 ? ai : bi; - } - - if (begs.length) { - result = [ left, right ]; - } - } - - return result; -} - +module.exports = require("crypto"); /***/ }), /* 418 */ @@ -44576,7 +44511,201 @@ module.exports = require("net"); /* 638 */, /* 639 */, /* 640 */, -/* 641 */, +/* 641 */ +/***/ (function(__unusedmodule, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* 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; }); +const fs = __webpack_require__(747) +const path = __webpack_require__(622) +const core = __webpack_require__(186) +const exec = __webpack_require__(514) +const cache = __webpack_require__(799) +const common = __webpack_require__(390) + +// The returned gemfile is guaranteed to exist, the lockfile might not exist +function detectGemfiles() { + const gemfilePath = process.env['BUNDLE_GEMFILE'] || 'Gemfile' + if (fs.existsSync(gemfilePath)) { + return [gemfilePath, `${gemfilePath}.lock`] + } else if (process.env['BUNDLE_GEMFILE']) { + throw new Error(`$BUNDLE_GEMFILE is set to ${gemfilePath} but does not exist`) + } + + if (fs.existsSync("gems.rb")) { + return ["gems.rb", "gems.locked"] + } + + return [null, null] +} + +function readBundledWithFromGemfileLock(lockFile) { + if (lockFile !== null && fs.existsSync(lockFile)) { + const contents = fs.readFileSync(lockFile, 'utf8') + const lines = contents.split(/\r?\n/) + const bundledWithLine = lines.findIndex(line => /^BUNDLED WITH$/.test(line.trim())) + if (bundledWithLine !== -1) { + const nextLine = lines[bundledWithLine+1] + if (nextLine && /^\d+/.test(nextLine.trim())) { + const bundlerVersion = nextLine.trim() + console.log(`Using Bundler ${bundlerVersion} from ${lockFile} BUNDLED WITH ${bundlerVersion}`) + return bundlerVersion + } + } + } + return null +} + +async function installBundler(bundlerVersionInput, lockFile, platform, rubyPrefix, engine, rubyVersion) { + let bundlerVersion = bundlerVersionInput + + if (bundlerVersion === 'default' || bundlerVersion === 'Gemfile.lock') { + bundlerVersion = readBundledWithFromGemfileLock(lockFile) + + if (!bundlerVersion) { + bundlerVersion = 'latest' + } + } + + if (bundlerVersion === 'latest') { + bundlerVersion = '2' + } + + if (/^\d+/.test(bundlerVersion)) { + // OK + } else { + throw new Error(`Cannot parse bundler input: ${bundlerVersion}`) + } + + if (engine === 'ruby' && rubyVersion.match(/^2\.[012]/)) { + console.log('Bundler 2 requires Ruby 2.3+, using Bundler 1 on Ruby <= 2.2') + bundlerVersion = '1' + } else if (engine === 'ruby' && rubyVersion.startsWith('2.3')) { + console.log('Ruby 2.3 has a bug with Bundler 2 (https://github.com/rubygems/rubygems/issues/3570), using Bundler 1 instead on Ruby 2.3') + bundlerVersion = '1' + } else if (engine === 'jruby' && rubyVersion.startsWith('9.1')) { // JRuby 9.1 targets Ruby 2.3, treat it the same + console.log('JRuby 9.1 has a bug with Bundler 2 (https://github.com/ruby/setup-ruby/issues/108), using Bundler 1 instead on JRuby 9.1') + bundlerVersion = '1' + } + + if (common.isHeadVersion(rubyVersion) && common.isBundler2Default(engine, rubyVersion) && bundlerVersion.startsWith('2')) { + console.log(`Using Bundler 2 shipped with ${engine}-${rubyVersion}`) + } else if (engine === 'truffleruby' && !common.isHeadVersion(rubyVersion) && bundlerVersion.startsWith('1')) { + console.log(`Using Bundler 1 shipped with ${engine}`) + } else { + const gem = path.join(rubyPrefix, 'bin', 'gem') + const bundlerVersionConstraint = bundlerVersion.match(/^\d+\.\d+\.\d+/) ? bundlerVersion : `~> ${bundlerVersion}` + await exec.exec(gem, ['install', 'bundler', '-v', bundlerVersionConstraint, '--no-document']) + } + + return bundlerVersion +} + +async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion) { + if (gemfile === null) { + console.log('Could not determine gemfile path, skipping "bundle install" and caching') + return false + } + + let envOptions = {} + if (bundlerVersion.startsWith('1') && common.isBundler2Default(engine, rubyVersion)) { + // If Bundler 1 is specified on Rubies which ship with Bundler 2, + // we need to specify which Bundler version to use explicitly until the lockfile exists. + console.log(`Setting BUNDLER_VERSION=${bundlerVersion} for "bundle config|lock" commands below to ensure Bundler 1 is used`) + envOptions = { env: { ...process.env, BUNDLER_VERSION: bundlerVersion } } + } + + // config + const cachePath = 'vendor/bundle' + // An absolute path, so it is reliably under $PWD/vendor/bundle, and not relative to the gemfile's directory + const bundleCachePath = path.join(process.cwd(), cachePath) + + await exec.exec('bundle', ['config', '--local', 'path', bundleCachePath], envOptions) + + if (fs.existsSync(lockFile)) { + await exec.exec('bundle', ['config', '--local', 'deployment', 'true'], envOptions) + } else { + // Generate the lockfile so we can use it to compute the cache key. + // This will also automatically pick up the latest gem versions compatible with the Gemfile. + await exec.exec('bundle', ['lock'], envOptions) + } + + // cache key + const paths = [cachePath] + const baseKey = await computeBaseKey(platform, engine, rubyVersion, lockFile) + 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}-`] + console.log(`Cache key: ${key}`) + + // restore cache & install + let cachedKey = null + try { + cachedKey = await cache.restoreCache(paths, key, restoreKeys) + } catch (error) { + if (error.name === cache.ValidationError.name) { + throw error; + } else { + core.info(`[warning] There was an error restoring the cache ${error.message}`) + } + } + + if (cachedKey) { + console.log(`Found cache for key: ${cachedKey}`) + } + + // Always run 'bundle install' to list the gems + await exec.exec('bundle', ['install', '--jobs', '4']) + + // @actions/cache only allows to save for non-existing keys + if (cachedKey !== key) { + if (cachedKey) { // existing cache but Gemfile.lock differs, clean old gems + await exec.exec('bundle', ['clean']) + } + + // 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}`) + } + } + } + + return true +} + +async function computeBaseKey(platform, engine, version, lockFile) { + let key = `setup-ruby-bundler-cache-v3-${platform}-${engine}-${version}` + + if (engine !== 'jruby' && common.isHeadVersion(version)) { + let revision = ''; + await exec.exec('ruby', ['-e', 'print RUBY_REVISION'], { + silent: true, + listeners: { + stdout: (data) => { + revision += data.toString(); + } + } + }); + key += `-revision-${revision}` + } + + key += `-${lockFile}` + return key +} + + +/***/ }), /* 642 */, /* 643 */, /* 644 */, @@ -47446,7 +47575,72 @@ exports.default = _default; /***/ }), /* 758 */, /* 759 */, -/* 760 */, +/* 760 */ +/***/ (function(module) { + +"use strict"; + +module.exports = balanced; +function balanced(a, b, str) { + if (a instanceof RegExp) a = maybeMatch(a, str); + if (b instanceof RegExp) b = maybeMatch(b, str); + + var r = range(a, b, str); + + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + a.length, r[1]), + post: str.slice(r[1] + b.length) + }; +} + +function maybeMatch(reg, str) { + var m = str.match(reg); + return m ? m[0] : null; +} + +balanced.range = range; +function range(a, b, str) { + var begs, beg, left, right, result; + var ai = str.indexOf(a); + var bi = str.indexOf(b, ai + 1); + var i = ai; + + if (ai >= 0 && bi > 0) { + begs = []; + left = str.length; + + while (i >= 0 && !result) { + if (i == ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length == 1) { + result = [ begs.pop(), bi ]; + } else { + beg = begs.pop(); + if (beg < left) { + left = beg; + right = bi; + } + + bi = str.indexOf(b, i + 1); + } + + i = ai < bi && ai >= 0 ? ai : bi; + } + + if (begs.length) { + result = [ left, right ]; + } + } + + return result; +} + + +/***/ }), /* 761 */ /***/ (function(module) { @@ -48853,7 +49047,7 @@ exports.safeTrimTrailingSeparator = safeTrimTrailingSeparator; /***/ (function(module, __unusedexports, __webpack_require__) { var concatMap = __webpack_require__(891); -var balanced = __webpack_require__(417); +var balanced = __webpack_require__(760); module.exports = expandTop; @@ -49141,7 +49335,7 @@ exports.TraceAPI = TraceAPI; // Unique ID creation requires a high quality random # generator. In node.js // this is pretty straight-forward - we use the crypto API. -var crypto = __webpack_require__(373); +var crypto = __webpack_require__(417); module.exports = function nodeRNG() { return crypto.randomBytes(16); @@ -51831,9 +52025,8 @@ const os = __webpack_require__(87) const fs = __webpack_require__(747) const path = __webpack_require__(622) const core = __webpack_require__(186) -const exec = __webpack_require__(514) -const cache = __webpack_require__(799) const common = __webpack_require__(390) +const bundler = __webpack_require__(641) const windows = common.windows @@ -51890,36 +52083,20 @@ async function setupRuby(options = {}) { } if (inputs['bundler'] !== 'none') { - const [gemfile, lockFile] = detectGemfiles() + const [gemfile, lockFile] = bundler.detectGemfiles() const bundlerVersion = await common.measure('Installing Bundler', async () => - installBundler(inputs['bundler'], lockFile, platform, rubyPrefix, engine, version)) + bundler.installBundler(inputs['bundler'], lockFile, platform, rubyPrefix, engine, version)) if (inputs['bundler-cache'] === 'true') { await common.measure('bundle install', async () => - bundleInstall(gemfile, lockFile, platform, engine, version, bundlerVersion)) + bundler.bundleInstall(gemfile, lockFile, platform, engine, version, bundlerVersion)) } } core.setOutput('ruby-prefix', rubyPrefix) } -// The returned gemfile is guaranteed to exist, the lockfile might not exist -function detectGemfiles() { - const gemfilePath = process.env['BUNDLE_GEMFILE'] || 'Gemfile' - if (fs.existsSync(gemfilePath)) { - return [gemfilePath, `${gemfilePath}.lock`] - } else if (process.env['BUNDLE_GEMFILE']) { - throw new Error(`$BUNDLE_GEMFILE is set to ${gemfilePath} but does not exist`) - } - - if (fs.existsSync("gems.rb")) { - return ["gems.rb", "gems.locked"] - } - - return [null, null] -} - function parseRubyEngineAndVersion(rubyVersion) { if (rubyVersion === 'default') { if (fs.existsSync('.ruby-version')) { @@ -52003,168 +52180,6 @@ function envPreInstall() { } } -function readBundledWithFromGemfileLock(lockFile) { - if (lockFile !== null && fs.existsSync(lockFile)) { - const contents = fs.readFileSync(lockFile, 'utf8') - const lines = contents.split(/\r?\n/) - const bundledWithLine = lines.findIndex(line => /^BUNDLED WITH$/.test(line.trim())) - if (bundledWithLine !== -1) { - const nextLine = lines[bundledWithLine+1] - if (nextLine && /^\d+/.test(nextLine.trim())) { - const bundlerVersion = nextLine.trim() - console.log(`Using Bundler ${bundlerVersion} from ${lockFile} BUNDLED WITH ${bundlerVersion}`) - return bundlerVersion - } - } - } - return null -} - -async function installBundler(bundlerVersionInput, lockFile, platform, rubyPrefix, engine, rubyVersion) { - let bundlerVersion = bundlerVersionInput - - if (bundlerVersion === 'default' || bundlerVersion === 'Gemfile.lock') { - bundlerVersion = readBundledWithFromGemfileLock(lockFile) - - if (!bundlerVersion) { - bundlerVersion = 'latest' - } - } - - if (bundlerVersion === 'latest') { - bundlerVersion = '2' - } - - if (/^\d+/.test(bundlerVersion)) { - // OK - } else { - throw new Error(`Cannot parse bundler input: ${bundlerVersion}`) - } - - if (engine === 'ruby' && rubyVersion.match(/^2\.[012]/)) { - console.log('Bundler 2 requires Ruby 2.3+, using Bundler 1 on Ruby <= 2.2') - bundlerVersion = '1' - } else if (engine === 'ruby' && rubyVersion.startsWith('2.3')) { - console.log('Ruby 2.3 has a bug with Bundler 2 (https://github.com/rubygems/rubygems/issues/3570), using Bundler 1 instead on Ruby 2.3') - bundlerVersion = '1' - } else if (engine === 'jruby' && rubyVersion.startsWith('9.1')) { // JRuby 9.1 targets Ruby 2.3, treat it the same - console.log('JRuby 9.1 has a bug with Bundler 2 (https://github.com/ruby/setup-ruby/issues/108), using Bundler 1 instead on JRuby 9.1') - bundlerVersion = '1' - } - - if (common.isHeadVersion(rubyVersion) && common.isBundler2Default(engine, rubyVersion) && bundlerVersion.startsWith('2')) { - console.log(`Using Bundler 2 shipped with ${engine}-${rubyVersion}`) - } else if (engine === 'truffleruby' && !common.isHeadVersion(rubyVersion) && bundlerVersion.startsWith('1')) { - console.log(`Using Bundler 1 shipped with ${engine}`) - } else { - const gem = path.join(rubyPrefix, 'bin', 'gem') - const bundlerVersionConstraint = bundlerVersion.match(/^\d+\.\d+\.\d+/) ? bundlerVersion : `~> ${bundlerVersion}` - await exec.exec(gem, ['install', 'bundler', '-v', bundlerVersionConstraint, '--no-document']) - } - - return bundlerVersion -} - -async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion) { - if (gemfile === null) { - console.log('Could not determine gemfile path, skipping "bundle install" and caching') - return false - } - - let envOptions = {} - if (bundlerVersion.startsWith('1') && common.isBundler2Default(engine, rubyVersion)) { - // If Bundler 1 is specified on Rubies which ship with Bundler 2, - // we need to specify which Bundler version to use explicitly until the lockfile exists. - console.log(`Setting BUNDLER_VERSION=${bundlerVersion} for "bundle config|lock" commands below to ensure Bundler 1 is used`) - envOptions = { env: { ...process.env, BUNDLER_VERSION: bundlerVersion } } - } - - // config - const cachePath = 'vendor/bundle' - // An absolute path, so it is reliably under $PWD/vendor/bundle, and not relative to the gemfile's directory - const bundleCachePath = path.join(process.cwd(), cachePath) - - await exec.exec('bundle', ['config', '--local', 'path', bundleCachePath], envOptions) - - if (fs.existsSync(lockFile)) { - await exec.exec('bundle', ['config', '--local', 'deployment', 'true'], envOptions) - } else { - // Generate the lockfile so we can use it to compute the cache key. - // This will also automatically pick up the latest gem versions compatible with the Gemfile. - await exec.exec('bundle', ['lock'], envOptions) - } - - // cache key - const paths = [cachePath] - const baseKey = await computeBaseKey(platform, engine, rubyVersion, lockFile) - 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}-`] - console.log(`Cache key: ${key}`) - - // restore cache & install - let cachedKey = null - try { - cachedKey = await cache.restoreCache(paths, key, restoreKeys) - } catch (error) { - if (error.name === cache.ValidationError.name) { - throw error; - } else { - core.info(`[warning] There was an error restoring the cache ${error.message}`) - } - } - - if (cachedKey) { - console.log(`Found cache for key: ${cachedKey}`) - } - - // Always run 'bundle install' to list the gems - await exec.exec('bundle', ['install', '--jobs', '4']) - - // @actions/cache only allows to save for non-existing keys - if (cachedKey !== key) { - if (cachedKey) { // existing cache but Gemfile.lock differs, clean old gems - await exec.exec('bundle', ['clean']) - } - - // 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}`) - } - } - } - - return true -} - -async function computeBaseKey(platform, engine, version, lockFile) { - let key = `setup-ruby-bundler-cache-v3-${platform}-${engine}-${version}` - - if (engine !== 'jruby' && common.isHeadVersion(version)) { - let revision = ''; - await exec.exec('ruby', ['-e', 'print RUBY_REVISION'], { - silent: true, - listeners: { - stdout: (data) => { - revision += data.toString(); - } - } - }); - key += `-revision-${revision}` - } - - key += `-${lockFile}` - return key -} - if (__filename.endsWith('index.js')) { run() } @@ -53979,7 +53994,7 @@ Object.defineProperty(exports, "__esModule", { }); exports.default = rng; -var _crypto = _interopRequireDefault(__webpack_require__(373)); +var _crypto = _interopRequireDefault(__webpack_require__(417)); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } diff --git a/index.js b/index.js index ae962ae..997d085 100644 --- a/index.js +++ b/index.js @@ -2,9 +2,8 @@ const os = require('os') 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 bundler = require('./bundler') const windows = common.windows @@ -61,36 +60,20 @@ export async function setupRuby(options = {}) { } if (inputs['bundler'] !== 'none') { - const [gemfile, lockFile] = detectGemfiles() + const [gemfile, lockFile] = bundler.detectGemfiles() const bundlerVersion = await common.measure('Installing Bundler', async () => - installBundler(inputs['bundler'], lockFile, platform, rubyPrefix, engine, version)) + bundler.installBundler(inputs['bundler'], lockFile, platform, rubyPrefix, engine, version)) if (inputs['bundler-cache'] === 'true') { await common.measure('bundle install', async () => - bundleInstall(gemfile, lockFile, platform, engine, version, bundlerVersion)) + bundler.bundleInstall(gemfile, lockFile, platform, engine, version, bundlerVersion)) } } core.setOutput('ruby-prefix', rubyPrefix) } -// The returned gemfile is guaranteed to exist, the lockfile might not exist -function detectGemfiles() { - const gemfilePath = process.env['BUNDLE_GEMFILE'] || 'Gemfile' - if (fs.existsSync(gemfilePath)) { - return [gemfilePath, `${gemfilePath}.lock`] - } else if (process.env['BUNDLE_GEMFILE']) { - throw new Error(`$BUNDLE_GEMFILE is set to ${gemfilePath} but does not exist`) - } - - if (fs.existsSync("gems.rb")) { - return ["gems.rb", "gems.locked"] - } - - return [null, null] -} - function parseRubyEngineAndVersion(rubyVersion) { if (rubyVersion === 'default') { if (fs.existsSync('.ruby-version')) { @@ -174,166 +157,4 @@ function envPreInstall() { } } -function readBundledWithFromGemfileLock(lockFile) { - if (lockFile !== null && fs.existsSync(lockFile)) { - const contents = fs.readFileSync(lockFile, 'utf8') - const lines = contents.split(/\r?\n/) - const bundledWithLine = lines.findIndex(line => /^BUNDLED WITH$/.test(line.trim())) - if (bundledWithLine !== -1) { - const nextLine = lines[bundledWithLine+1] - if (nextLine && /^\d+/.test(nextLine.trim())) { - const bundlerVersion = nextLine.trim() - console.log(`Using Bundler ${bundlerVersion} from ${lockFile} BUNDLED WITH ${bundlerVersion}`) - return bundlerVersion - } - } - } - return null -} - -async function installBundler(bundlerVersionInput, lockFile, platform, rubyPrefix, engine, rubyVersion) { - let bundlerVersion = bundlerVersionInput - - if (bundlerVersion === 'default' || bundlerVersion === 'Gemfile.lock') { - bundlerVersion = readBundledWithFromGemfileLock(lockFile) - - if (!bundlerVersion) { - bundlerVersion = 'latest' - } - } - - if (bundlerVersion === 'latest') { - bundlerVersion = '2' - } - - if (/^\d+/.test(bundlerVersion)) { - // OK - } else { - throw new Error(`Cannot parse bundler input: ${bundlerVersion}`) - } - - if (engine === 'ruby' && rubyVersion.match(/^2\.[012]/)) { - console.log('Bundler 2 requires Ruby 2.3+, using Bundler 1 on Ruby <= 2.2') - bundlerVersion = '1' - } else if (engine === 'ruby' && rubyVersion.startsWith('2.3')) { - console.log('Ruby 2.3 has a bug with Bundler 2 (https://github.com/rubygems/rubygems/issues/3570), using Bundler 1 instead on Ruby 2.3') - bundlerVersion = '1' - } else if (engine === 'jruby' && rubyVersion.startsWith('9.1')) { // JRuby 9.1 targets Ruby 2.3, treat it the same - console.log('JRuby 9.1 has a bug with Bundler 2 (https://github.com/ruby/setup-ruby/issues/108), using Bundler 1 instead on JRuby 9.1') - bundlerVersion = '1' - } - - if (common.isHeadVersion(rubyVersion) && common.isBundler2Default(engine, rubyVersion) && bundlerVersion.startsWith('2')) { - console.log(`Using Bundler 2 shipped with ${engine}-${rubyVersion}`) - } else if (engine === 'truffleruby' && !common.isHeadVersion(rubyVersion) && bundlerVersion.startsWith('1')) { - console.log(`Using Bundler 1 shipped with ${engine}`) - } else { - const gem = path.join(rubyPrefix, 'bin', 'gem') - const bundlerVersionConstraint = bundlerVersion.match(/^\d+\.\d+\.\d+/) ? bundlerVersion : `~> ${bundlerVersion}` - await exec.exec(gem, ['install', 'bundler', '-v', bundlerVersionConstraint, '--no-document']) - } - - return bundlerVersion -} - -async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion) { - if (gemfile === null) { - console.log('Could not determine gemfile path, skipping "bundle install" and caching') - return false - } - - let envOptions = {} - if (bundlerVersion.startsWith('1') && common.isBundler2Default(engine, rubyVersion)) { - // If Bundler 1 is specified on Rubies which ship with Bundler 2, - // we need to specify which Bundler version to use explicitly until the lockfile exists. - console.log(`Setting BUNDLER_VERSION=${bundlerVersion} for "bundle config|lock" commands below to ensure Bundler 1 is used`) - envOptions = { env: { ...process.env, BUNDLER_VERSION: bundlerVersion } } - } - - // config - const cachePath = 'vendor/bundle' - // An absolute path, so it is reliably under $PWD/vendor/bundle, and not relative to the gemfile's directory - const bundleCachePath = path.join(process.cwd(), cachePath) - - await exec.exec('bundle', ['config', '--local', 'path', bundleCachePath], envOptions) - - if (fs.existsSync(lockFile)) { - await exec.exec('bundle', ['config', '--local', 'deployment', 'true'], envOptions) - } else { - // Generate the lockfile so we can use it to compute the cache key. - // This will also automatically pick up the latest gem versions compatible with the Gemfile. - await exec.exec('bundle', ['lock'], envOptions) - } - - // cache key - const paths = [cachePath] - const baseKey = await computeBaseKey(platform, engine, rubyVersion, lockFile) - 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}-`] - console.log(`Cache key: ${key}`) - - // restore cache & install - let cachedKey = null - try { - cachedKey = await cache.restoreCache(paths, key, restoreKeys) - } catch (error) { - if (error.name === cache.ValidationError.name) { - throw error; - } else { - core.info(`[warning] There was an error restoring the cache ${error.message}`) - } - } - - if (cachedKey) { - console.log(`Found cache for key: ${cachedKey}`) - } - - // Always run 'bundle install' to list the gems - await exec.exec('bundle', ['install', '--jobs', '4']) - - // @actions/cache only allows to save for non-existing keys - if (cachedKey !== key) { - if (cachedKey) { // existing cache but Gemfile.lock differs, clean old gems - await exec.exec('bundle', ['clean']) - } - - // 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}`) - } - } - } - - return true -} - -async function computeBaseKey(platform, engine, version, lockFile) { - let key = `setup-ruby-bundler-cache-v3-${platform}-${engine}-${version}` - - if (engine !== 'jruby' && common.isHeadVersion(version)) { - let revision = ''; - await exec.exec('ruby', ['-e', 'print RUBY_REVISION'], { - silent: true, - listeners: { - stdout: (data) => { - revision += data.toString(); - } - } - }); - key += `-revision-${revision}` - } - - key += `-${lockFile}` - return key -} - if (__filename.endsWith('index.js')) { run() }