Skip to content

Fetch custom Ruby version file specified by ruby file: option in Gemfile#14230

Closed
wt-l00 wants to merge 1 commit intodependabot:mainfrom
wt-l00:support_ruby_file_syntax
Closed

Fetch custom Ruby version file specified by ruby file: option in Gemfile#14230
wt-l00 wants to merge 1 commit intodependabot:mainfrom
wt-l00:support_ruby_file_syntax

Conversation

@wt-l00
Copy link

@wt-l00 wt-l00 commented Feb 20, 2026

What are you trying to accomplish?

Gemfile supports a ruby file: "custom-ruby-version" syntax to read the Ruby version from an arbitrary file (e.g. mise.toml). However, the Bundler file fetcher only fetched .ruby-version and .tool-versions as version constraint files, and did not fetch the custom version file specified by the ruby file: option.

Without fetching this file, Dependabot cannot correctly interpret Ruby version constraints, which may cause dependency updates to fail.

This PR fixes the issue by detecting the ruby file: option in the Gemfile and including the specified file in the set of fetched files.

Anything you want to highlight for special attention from reviewers?

  • ruby_file_version_filename extracts the filename from the Gemfile content using a regex (/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
  • File fetching delegates to the existing fetch_support_file method, so error handling follows the same behavior as other support files (.ruby-version,
    .tool-versions).

How will you know you've accomplished your goal?

The added spec verifies that when fetching files from a repository whose Gemfile contains ruby file: "custom-ruby-version", the specified custom version file is included in the fetched files, and the total number of fetched files is 3 (Gemfile, Gemfile.lock, and custom-ruby-version).

Checklist

  • I have run the complete test suite to ensure all tests and linters pass.
  • I have thoroughly tested my code changes to ensure they work as expected, including adding additional tests for new functionality.
  • I have written clear and descriptive commit messages.
  • I have provided a detailed description of the changes in the pull request, including the problem it addresses, how it fixes the problem, and any relevant details about the implementation.
  • I have ensured that the code is well-documented and easy to understand.

@wt-l00 wt-l00 requested a review from a team as a code owner February 20, 2026 04:42
Copilot AI review requested due to automatic review settings February 20, 2026 04:42
@github-actions github-actions bot added the L: ruby:bundler RubyGems via bundler label Feb 20, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for fetching custom Ruby version files specified via the ruby file: option in Gemfiles. This is a newly supported Gemfile syntax that allows specifying the Ruby version in arbitrary files (e.g., mise.toml) rather than just .ruby-version or .tool-versions. Without this feature, Dependabot cannot correctly interpret Ruby version constraints when this syntax is used, potentially causing dependency updates to fail.

Changes:

  • Added file fetching support for custom ruby version files in the Bundler FileFetcher
  • Added test fixtures for the new ruby_file_option project configuration
  • Added test coverage across file_fetcher, file_parser/file_preparer, file_updater, and update_checker specs

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
bundler/lib/dependabot/bundler/file_fetcher.rb Added ruby_file_version_file and ruby_file_version_filename methods to detect and fetch custom ruby version files from Gemfiles
bundler/spec/fixtures/projects/bundler2/ruby_file_option/Gemfile Test fixture with ruby file: "custom-ruby-version" syntax
bundler/spec/fixtures/projects/bundler2/ruby_file_option/Gemfile.lock Corresponding lockfile for ruby_file_option test project
bundler/spec/fixtures/projects/bundler2/ruby_file_option/custom-ruby-version Custom version file containing "2.2.0"
bundler/spec/fixtures/github/gemfile_with_ruby_file_option_content.json Mock GitHub API response for Gemfile with ruby file: option
bundler/spec/fixtures/github/custom_ruby_version_content.json Mock GitHub API response for custom-ruby-version file
bundler/spec/fixtures/github/contents_ruby_with_custom_version_file.json Mock GitHub API directory listing including custom version file
bundler/spec/dependabot/bundler/file_fetcher_spec.rb Test case verifying custom version file is fetched
bundler/spec/dependabot/bundler/file_parser/file_preparer_spec.rb Test case verifying custom version file content is prepared correctly
bundler/spec/dependabot/bundler/file_updater_spec.rb Test case verifying updates work with custom version files
bundler/spec/dependabot/bundler/update_checker/latest_version_finder_spec.rb Test case verifying version resolution works with custom version files

Comment on lines +142 to +162
sig { returns(T.nilable(DependencyFile)) }
def ruby_file_version_file
return unless gemfile

@ruby_file_version_file ||= T.let(
begin
filename = ruby_file_version_filename
fetch_support_file(filename) if filename
end,
T.nilable(Dependabot::DependencyFile)
)
end

sig { returns(T.nilable(String)) }
def ruby_file_version_filename
content = gemfile&.content
return unless content

match = content.match(/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
match&.captures&.first
end
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file_preparer needs to be updated to include custom ruby version files in the prepared_dependency_files. Currently, the prepared_dependency_files method on lines 32-40 explicitly lists ruby_version_file and tool_versions_file, but does not include the newly fetched ruby_file_version_file.

Without this, even though the custom ruby version file is fetched by the FileFetcher, it won't be passed to the FileParser or FileUpdater, which will cause dependency resolution to fail when a Gemfile uses the "ruby file:" option.

Add a ruby_file_version_file method similar to ruby_version_file (lines 83-85) and tool_versions_file (lines 87-90), and include it in the array on line 32-40.

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +162
sig { returns(T.nilable(DependencyFile)) }
def ruby_file_version_file
return unless gemfile

@ruby_file_version_file ||= T.let(
begin
filename = ruby_file_version_filename
fetch_support_file(filename) if filename
end,
T.nilable(Dependabot::DependencyFile)
)
end

sig { returns(T.nilable(String)) }
def ruby_file_version_filename
content = gemfile&.content
return unless content

match = content.match(/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
match&.captures&.first
end
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lockfile_updater needs to write custom ruby version files when creating temporary dependency files. Currently, write_temporary_dependency_files (lines 128-144) calls write_ruby_version_file and write_tool_versions_file to handle .ruby-version and .tool-versions files, but it doesn't handle custom ruby version files specified via the "ruby file:" option.

Add a write_ruby_file_version_file method similar to write_ruby_version_file (lines 147-153) and write_tool_versions_file (lines 156-162), and call it from write_temporary_dependency_files. Also add a ruby_file_version_file method similar to lines 212-214 and 217-219 to locate the custom version file from dependency_files.

Copilot uses AI. Check for mistakes.
content = gemfile&.content
return unless content

match = content.match(/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern could be made more robust to handle edge cases. Consider these improvements:

  1. The pattern should handle optional commas after the file: option (Gemfile syntax allows both "ruby file: 'x'" and "ruby file: 'x',")
  2. Consider supporting both old-style hash syntax (ruby :file => "x") and new-style (ruby file: "x")
  3. The pattern doesn't handle comments on the same line (e.g., "ruby file: 'x' # comment")

While the current pattern /^\sruby\s+file:\s'"['"]/ will work for basic cases, consider making it more flexible to match actual Gemfile parsing behavior. You could use a pattern like:

/^\sruby\s+(?:file:\s|:file\s*=>\s*)'"['"]/

This handles both hash syntaxes and is more aligned with Ruby conventions.

Suggested change
match = content.match(/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
match = content.match(/^\s*ruby\s+(?:file:\s*|:file\s*=>\s*)['"]([^'"]+)['"]\s*,?.*/)

Copilot uses AI. Check for mistakes.
return unless content

match = content.match(/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
match&.captures&.first
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern allows any non-quote characters in the filename ([^'"]+), which could include path separators like "/" or "..". While the base FileFetcher class handles path sanitization via File.dirname/File.basename and .cleanpath, it would be more secure and clear to explicitly validate that the extracted filename doesn't contain path separators.

Consider adding validation after extracting the filename:

filename = match&.captures&.first
return if filename&.include?("/") || filename&.include?("\\")
filename

This makes the security boundary explicit at the parsing level rather than relying solely on downstream sanitization.

Suggested change
match&.captures&.first
filename = match&.captures&.first
return if filename&.include?("/") || filename&.include?("\\")
filename

Copilot uses AI. Check for mistakes.
Comment on lines +142 to +162
sig { returns(T.nilable(DependencyFile)) }
def ruby_file_version_file
return unless gemfile

@ruby_file_version_file ||= T.let(
begin
filename = ruby_file_version_filename
fetch_support_file(filename) if filename
end,
T.nilable(Dependabot::DependencyFile)
)
end

sig { returns(T.nilable(String)) }
def ruby_file_version_filename
content = gemfile&.content
return unless content

match = content.match(/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
match&.captures&.first
end
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The update_checker/file_preparer also needs to be updated to include custom ruby version files in the prepared_dependency_files. Currently, prepared_dependency_files on lines 118-124 includes ruby_version_file and tool_versions_file, but doesn't include the custom ruby version file specified via the "ruby file:" option.

Add a ruby_file_version_file method similar to ruby_version_file (lines 194-196) and tool_versions_file (lines 199-201), and include it in the array on lines 118-124.

Copilot uses AI. Check for mistakes.
…mfile

Add support for Bundler's `ruby file: "filename"` syntax, which allows
managing the Ruby version via an external file. The FileFetcher now
fetches the referenced file as a support file.

- Add `ruby_file_version_filename` to extract the filename from Gemfile
  content using a regex
- Add `ruby_file_version_file` to fetch the file via `fetch_support_file`
- Wire it into `fetch_files` following the same pattern as `.ruby-version` and `.tool-versions`

ref: ruby/rubygems@fb9354b
@wt-l00 wt-l00 force-pushed the support_ruby_file_syntax branch from ce61a43 to 0420775 Compare February 20, 2026 05:06
@wt-l00 wt-l00 closed this Feb 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L: ruby:bundler RubyGems via bundler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments