Skip to content

Commit 995dd39

Browse files
committed
add option to set config BUNDLE_FROZEN for bundler install
* tried to remain flexible to allow other options to be set in the future
1 parent 8aeb6ff commit 995dd39

File tree

6 files changed

+171
-4
lines changed

6 files changed

+171
-4
lines changed

.github/workflows/test.yml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,94 @@ jobs:
419419
- name: C:/msys64/mingw64/bin/gcc.exe not installed
420420
run: ruby -e "abort if File.exist?('C:/msys64/mingw64/bin/gcc.exe')"
421421

422+
testBundleFrozen:
423+
strategy:
424+
fail-fast: false
425+
matrix:
426+
os: [ ubuntu-latest, macos-latest, windows-latest ]
427+
ruby: [ '3.1', '3.2', '3.3', '3.4' ]
428+
name: "Test bundle-frozen on ${{ matrix.os }} with Ruby ${{ matrix.ruby }}"
429+
runs-on: ${{ matrix.os }}
430+
steps:
431+
- uses: actions/checkout@v5
432+
- name: Create test Gemfile with locked dependencies
433+
shell: bash
434+
run: |
435+
cat > Gemfile <<'EOF'
436+
source 'https://rubygems.org'
437+
gem 'rake', '~> 13.0'
438+
gem 'minitest', '~> 5.16'
439+
EOF
440+
- name: Generate Gemfile.lock
441+
shell: bash
442+
run: |
443+
gem install bundler
444+
bundle lock
445+
- name: Display Gemfile.lock
446+
shell: bash
447+
run: cat Gemfile.lock
448+
- uses: ./
449+
with:
450+
ruby-version: ${{ matrix.ruby }}
451+
bundler-cache: true
452+
bundle-frozen: true
453+
- name: Verify frozen config was set
454+
shell: bash
455+
run: |
456+
FROZEN_VALUE=$(bundle config frozen)
457+
echo "Bundle frozen config: $FROZEN_VALUE"
458+
if echo "$FROZEN_VALUE" | grep -q "true"; then
459+
echo "✓ Bundle frozen config is set to true"
460+
else
461+
echo "Error: Bundle frozen config was not set to 'true'"
462+
exit 1
463+
fi
464+
- name: Verify bundle install succeeded with frozen lockfile
465+
shell: bash
466+
run: |
467+
bundle exec ruby -v
468+
echo "✓ Bundle install with frozen lockfile succeeded"
469+
- name: Test that modifying Gemfile causes failure with bundle-frozen
470+
shell: bash
471+
run: |
472+
echo "gem 'json', '~> 2.6'" >> Gemfile
473+
if bundle install 2>&1 | grep -q "frozen"; then
474+
echo "✓ Bundle correctly detected frozen lockfile violation"
475+
else
476+
echo "Warning: Expected bundle install to fail or warn about frozen lockfile"
477+
fi
478+
git checkout Gemfile
479+
480+
testBundleNotFrozen:
481+
name: "Test without bundle-frozen (control)"
482+
runs-on: ubuntu-latest
483+
steps:
484+
- uses: actions/checkout@v5
485+
- name: Create test Gemfile
486+
run: |
487+
cat > Gemfile <<'EOF'
488+
source 'https://rubygems.org'
489+
gem 'rake', '~> 13.0'
490+
EOF
491+
- uses: ./
492+
with:
493+
ruby-version: '3.4'
494+
bundler-cache: true
495+
bundle-frozen: false
496+
- name: Verify frozen config was not set
497+
run: |
498+
FROZEN_VALUE=$(bundle config frozen)
499+
echo "Bundle frozen config: $FROZEN_VALUE"
500+
if echo "$FROZEN_VALUE" | grep -q "true"; then
501+
echo "Error: Bundle frozen config should not be set when bundle-frozen is false"
502+
exit 1
503+
fi
504+
echo "✓ Bundle frozen config is not set to true (as expected)"
505+
- name: Verify bundle install succeeded
506+
run: |
507+
bundle exec ruby -v
508+
echo "✓ Bundle install succeeded without frozen flag"
509+
422510
lint:
423511
runs-on: ubuntu-22.04
424512
steps:

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,20 @@ This caching speeds up installing gems significantly and avoids too many request
190190
It needs a `Gemfile` (or `$BUNDLE_GEMFILE` or `gems.rb`) under the [`working-directory`](#working-directory).
191191
If there is a `Gemfile.lock` (or `$BUNDLE_GEMFILE.lock` or `gems.locked`), `bundle config --local deployment true` is used.
192192

193+
#### bundle-frozen
194+
195+
When using `bundler-cache: true`, you can optionally set `bundle-frozen: true` to enforce that the `Gemfile.lock` is not modified during `bundle install`:
196+
```yaml
197+
- uses: ruby/setup-ruby@v1
198+
with:
199+
ruby-version: '3.4'
200+
bundler-cache: true
201+
bundle-frozen: true
202+
```
203+
204+
This runs `bundle config --local frozen true` before bundle install, which disallows changes to the Gemfile.lock.
205+
This is useful in CI to ensure the lockfile is up to date and prevents accidental modifications.
206+
193207
To use a `Gemfile` which is not at the root or has a different name, set `BUNDLE_GEMFILE` in the `env` at the job level
194208
as shown in the [example](#matrix-of-gemfiles).
195209

action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ inputs:
2525
bundler-cache:
2626
description: 'Run "bundle install", and cache the result automatically. Either true or false.'
2727
default: 'false'
28+
bundle-frozen:
29+
description: 'Run "bundle config --local frozen true" before bundle install to disallow changes to the Gemfile.lock. Either true or false.'
30+
default: 'false'
2831
working-directory:
2932
description: 'The working directory to use for resolving paths for .ruby-version, .tool-versions, mise.toml and Gemfile.lock.'
3033
cache-version:

bundler.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,28 @@ export async function installBundler(bundlerVersionInput, rubygemsInputSet, lock
137137
return bundlerVersion
138138
}
139139

140-
export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion, cacheVersion) {
140+
async function applyBundleConfig(configOptions, envOptions = {}) {
141+
// Apply bundle config settings based on input options
142+
// This function makes it easy to add new bundle config options in the future
143+
const configs = []
144+
145+
if (configOptions.frozen === 'true') {
146+
configs.push({ name: 'frozen', value: 'true', description: 'frozen' })
147+
}
148+
149+
// Add more config options here as needed in the future
150+
// Example:
151+
// if (configOptions.jobs) {
152+
// configs.push({ name: 'jobs', value: configOptions.jobs, description: 'jobs' })
153+
// }
154+
155+
for (const config of configs) {
156+
console.log(`Setting bundle config ${config.description} to ${config.value}`)
157+
await exec.exec('bundle', ['config', '--local', config.name, config.value], envOptions)
158+
}
159+
}
160+
161+
export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion, cacheVersion, bundleFrozen = 'false') {
141162
if (gemfile === null) {
142163
console.log('Could not determine gemfile path, skipping "bundle install" and caching')
143164
return false
@@ -158,6 +179,9 @@ export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVer
158179

159180
await exec.exec('bundle', ['config', '--local', 'path', bundleCachePath], envOptions)
160181

182+
// Apply bundle config options
183+
await applyBundleConfig({ frozen: bundleFrozen }, envOptions)
184+
161185
if (fs.existsSync(lockFile)) {
162186
await exec.exec('bundle', ['config', '--local', 'deployment', 'true'], envOptions)
163187
} else {
@@ -196,6 +220,7 @@ export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVer
196220

197221
// Number of jobs should scale with runner, up to a point
198222
const jobs = Math.min(os.availableParallelism(), 8)
223+
199224
// Always run 'bundle install' to list the gems
200225
await exec.exec('bundle', ['install', '--jobs', `${jobs}`])
201226

dist/index.js

Lines changed: 33 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const inputDefaults = {
1414
'rubygems': 'default',
1515
'bundler': 'Gemfile.lock',
1616
'bundler-cache': 'false',
17+
'bundle-frozen': 'false',
1718
'working-directory': '.',
1819
'cache-version': bundler.DEFAULT_CACHE_VERSION,
1920
'self-hosted': 'false',
@@ -97,8 +98,13 @@ export async function setupRuby(options = {}) {
9798
}
9899

99100
if (inputs['bundler-cache'] === 'true') {
101+
// Note: To add new bundle config options in the future:
102+
// 1. Add the input to action.yml
103+
// 2. Add it to inputDefaults above
104+
// 3. Pass it to bundleInstall (or create a bundleConfig object to pass multiple options)
105+
// 4. Update the applyBundleConfig function in bundler.js
100106
await common.time('bundle install', async () =>
101-
bundler.bundleInstall(gemfile, lockFile, platform, engine, version, bundlerVersion, inputs['cache-version']))
107+
bundler.bundleInstall(gemfile, lockFile, platform, engine, version, bundlerVersion, inputs['cache-version'], inputs['bundle-frozen']))
102108
}
103109

104110
core.setOutput('ruby-prefix', rubyPrefix)

0 commit comments

Comments
 (0)