Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,86 @@ jobs:
- name: C:/msys64/mingw64/bin/gcc.exe not installed
run: ruby -e "abort if File.exist?('C:/msys64/mingw64/bin/gcc.exe')"

testBundleFrozen:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
ruby: [ '3.1', '3.2', '3.3', '3.4' ]
name: "Test bundle-frozen on ${{ matrix.os }} with Ruby ${{ matrix.ruby }}"
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- name: Create test Gemfile with locked dependencies
shell: bash
run: |
cat > Gemfile <<'EOF'
source 'https://rubygems.org'
gem 'rake', '~> 13.0'
gem 'minitest', '~> 5.16'
EOF
- uses: ./
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
bundle-frozen: true
- name: Verify frozen config was set
shell: bash
run: |
FROZEN_VALUE=$(bundle config frozen)
echo "Bundle frozen config: $FROZEN_VALUE"
if echo "$FROZEN_VALUE" | grep -q "true"; then
echo "✓ Bundle frozen config is set to true"
else
echo "Error: Bundle frozen config was not set to 'true'"
exit 1
fi
- name: Verify bundle install succeeded with frozen lockfile
shell: bash
run: |
bundle exec ruby -v
echo "✓ Bundle install with frozen lockfile succeeded"
- name: Test that modifying Gemfile causes failure with bundle-frozen
shell: bash
run: |
echo "gem 'json', '~> 2.6'" >> Gemfile
if bundle install 2>&1 | grep -q "frozen"; then
echo "✓ Bundle correctly detected frozen lockfile violation"
else
echo "Warning: Expected bundle install to fail or warn about frozen lockfile"
fi
git checkout Gemfile

testBundleNotFrozen:
name: "Test without bundle-frozen (control)"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Create test Gemfile
run: |
cat > Gemfile <<'EOF'
source 'https://rubygems.org'
gem 'rake', '~> 13.0'
EOF
- uses: ./
with:
ruby-version: '3.4'
bundler-cache: true
bundle-frozen: false
- name: Verify frozen config was not set
run: |
FROZEN_VALUE=$(bundle config frozen)
echo "Bundle frozen config: $FROZEN_VALUE"
if echo "$FROZEN_VALUE" | grep -q "true"; then
echo "Error: Bundle frozen config should not be set when bundle-frozen is false"
exit 1
fi
echo "✓ Bundle frozen config is not set to true (as expected)"
- name: Verify bundle install succeeded
run: |
bundle exec ruby -v
echo "✓ Bundle install succeeded without frozen flag"

lint:
runs-on: ubuntu-22.04
steps:
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,20 @@ This caching speeds up installing gems significantly and avoids too many request
It needs a `Gemfile` (or `$BUNDLE_GEMFILE` or `gems.rb`) under the [`working-directory`](#working-directory).
If there is a `Gemfile.lock` (or `$BUNDLE_GEMFILE.lock` or `gems.locked`), `bundle config --local deployment true` is used.

#### bundle-frozen

When using `bundler-cache: true`, you can optionally set `bundle-frozen: true` to enforce that the `Gemfile.lock` is not modified during `bundle install`:
```yaml
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
bundle-frozen: true
```

This runs `bundle config --local frozen true` before bundle install, which disallows changes to the Gemfile.lock.
This is useful in CI to ensure the lockfile is up to date and prevents accidental modifications.

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
as shown in the [example](#matrix-of-gemfiles).

Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ inputs:
bundler-cache:
description: 'Run "bundle install", and cache the result automatically. Either true or false.'
default: 'false'
bundle-frozen:
description: 'Run "bundle config --local frozen true" before bundle install to disallow changes to the Gemfile.lock. Either true or false.'
default: 'false'
working-directory:
description: 'The working directory to use for resolving paths for .ruby-version, .tool-versions, mise.toml and Gemfile.lock.'
cache-version:
Expand Down
27 changes: 26 additions & 1 deletion bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,28 @@ export async function installBundler(bundlerVersionInput, rubygemsInputSet, lock
return bundlerVersion
}

export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion, cacheVersion) {
async function applyBundleConfig(configOptions, envOptions = {}) {
// Apply bundle config settings based on input options
// This function makes it easy to add new bundle config options in the future
const configs = []

if (configOptions.frozen === 'true') {
configs.push({ name: 'frozen', value: 'true', description: 'frozen' })
}

// Add more config options here as needed in the future
// Example:
// if (configOptions.jobs) {
// configs.push({ name: 'jobs', value: configOptions.jobs, description: 'jobs' })
// }

for (const config of configs) {
console.log(`Setting bundle config ${config.description} to ${config.value}`)
await exec.exec('bundle', ['config', '--local', config.name, config.value], envOptions)
}
}

export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVersion, bundlerVersion, cacheVersion, bundleFrozen = 'false') {
if (gemfile === null) {
console.log('Could not determine gemfile path, skipping "bundle install" and caching')
return false
Expand All @@ -158,6 +179,9 @@ export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVer

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

// Apply bundle config options
await applyBundleConfig({ frozen: bundleFrozen }, envOptions)

if (fs.existsSync(lockFile)) {
await exec.exec('bundle', ['config', '--local', 'deployment', 'true'], envOptions)
} else {
Expand Down Expand Up @@ -196,6 +220,7 @@ export async function bundleInstall(gemfile, lockFile, platform, engine, rubyVer

// Number of jobs should scale with runner, up to a point
const jobs = Math.min(os.availableParallelism(), 8)

// Always run 'bundle install' to list the gems
await exec.exec('bundle', ['install', '--jobs', `${jobs}`])

Expand Down
35 changes: 33 additions & 2 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const inputDefaults = {
'rubygems': 'default',
'bundler': 'Gemfile.lock',
'bundler-cache': 'false',
'bundle-frozen': 'false',
'working-directory': '.',
'cache-version': bundler.DEFAULT_CACHE_VERSION,
'self-hosted': 'false',
Expand Down Expand Up @@ -97,8 +98,13 @@ export async function setupRuby(options = {}) {
}

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

core.setOutput('ruby-prefix', rubyPrefix)
Expand Down