Skip to content

Commit 0420775

Browse files
committed
Fetch custom Ruby version file specified by ruby file: option in Gemfile
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
1 parent 6bb397e commit 0420775

File tree

14 files changed

+275
-1
lines changed

14 files changed

+275
-1
lines changed

bundler/lib/dependabot/bundler/file_fetcher.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def fetch_files
5050
fetched_files += gemspecs
5151
fetched_files << T.must(ruby_version_file) if ruby_version_file
5252
fetched_files << T.must(tool_versions_file) if tool_versions_file
53+
fetched_files << T.must(ruby_file_version_file) if ruby_file_version_file
5354
fetched_files += path_gemspecs
5455
fetched_files += find_included_files(fetched_files)
5556

@@ -138,6 +139,28 @@ def tool_versions_file
138139
@tool_versions_file ||= T.let(fetch_support_file(".tool-versions"), T.nilable(Dependabot::DependencyFile))
139140
end
140141

142+
sig { returns(T.nilable(DependencyFile)) }
143+
def ruby_file_version_file
144+
return unless gemfile
145+
146+
@ruby_file_version_file ||= T.let(
147+
begin
148+
filename = ruby_file_version_filename
149+
fetch_support_file(filename) if filename
150+
end,
151+
T.nilable(Dependabot::DependencyFile)
152+
)
153+
end
154+
155+
sig { returns(T.nilable(String)) }
156+
def ruby_file_version_filename
157+
content = gemfile&.content
158+
return unless content
159+
160+
match = content.match(/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
161+
match&.captures&.first
162+
end
163+
141164
sig { returns(T::Array[DependencyFile]) }
142165
def path_gemspecs
143166
gemspec_files = T.let([], T::Array[Dependabot::DependencyFile])

bundler/lib/dependabot/bundler/file_parser/file_preparer.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def prepared_dependency_files
3535
lockfile,
3636
ruby_version_file,
3737
tool_versions_file,
38+
ruby_file_version_file,
3839
*imported_ruby_files,
3940
*specification_files
4041
].compact
@@ -89,6 +90,24 @@ def tool_versions_file
8990
dependency_files.find { |f| f.name == ".tool-versions" }
9091
end
9192

93+
# `ruby file: "..."` で指定されたカスタム Ruby バージョンファイルを返す
94+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
95+
def ruby_file_version_file
96+
filename = ruby_file_version_filename
97+
return unless filename
98+
99+
dependency_files.find { |f| f.name == filename }
100+
end
101+
102+
sig { returns(T.nilable(String)) }
103+
def ruby_file_version_filename
104+
content = gemfile&.content
105+
return unless content
106+
107+
match = content.match(/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
108+
match&.captures&.first
109+
end
110+
92111
sig { returns(T::Array[Dependabot::DependencyFile]) }
93112
def imported_ruby_files
94113
dependency_files

bundler/lib/dependabot/bundler/file_updater/lockfile_updater.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ def write_temporary_dependency_files
132132
write_gemspecs(top_level_gemspecs)
133133
write_ruby_version_file
134134
write_tool_versions_file
135+
write_ruby_file_version_file
135136
write_gemspecs(path_gemspecs)
136137
write_specification_files
137138
write_imported_ruby_files
@@ -161,6 +162,16 @@ def write_tool_versions_file
161162
File.write(path, T.must(tool_versions_file).content)
162163
end
163164

165+
# `ruby file: "..."` で指定されたカスタム Ruby バージョンファイルを一時ディレクトリに書き出す
166+
sig { void }
167+
def write_ruby_file_version_file
168+
return unless ruby_file_version_file
169+
170+
path = T.must(ruby_file_version_file).name
171+
FileUtils.mkdir_p(Pathname.new(path).dirname)
172+
File.write(path, T.must(ruby_file_version_file).content)
173+
end
174+
164175
sig { params(files: T::Array[Dependabot::DependencyFile]).void }
165176
def write_gemspecs(files)
166177
files.each do |file|
@@ -218,6 +229,24 @@ def tool_versions_file
218229
dependency_files.find { |f| f.name == ".tool-versions" }
219230
end
220231

232+
# `ruby file: "..."` で指定されたカスタム Ruby バージョンファイルを返す
233+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
234+
def ruby_file_version_file
235+
filename = ruby_file_version_filename
236+
return unless filename
237+
238+
dependency_files.find { |f| f.name == filename }
239+
end
240+
241+
sig { returns(T.nilable(String)) }
242+
def ruby_file_version_filename
243+
content = gemfile&.content
244+
return unless content
245+
246+
match = content.match(/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
247+
match&.captures&.first
248+
end
249+
221250
sig { params(lockfile_body: String).returns(String) }
222251
def post_process_lockfile(lockfile_body)
223252
lockfile_body = reorder_git_dependencies(lockfile_body)

bundler/lib/dependabot/bundler/update_checker/file_preparer.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,12 @@ def prepared_dependency_files
114114
)
115115
end
116116

117-
# No editing required for lockfile or Ruby version file
117+
# lockfile Ruby バージョンファイルは編集不要のためそのまま追加
118118
files += [
119119
lockfile,
120120
ruby_version_file,
121121
tool_versions_file,
122+
ruby_file_version_file,
122123
*imported_ruby_files,
123124
*specification_files
124125
].compact
@@ -200,6 +201,24 @@ def tool_versions_file
200201
dependency_files.find { |f| f.name == ".tool-versions" }
201202
end
202203

204+
# `ruby file: "..."` で指定されたカスタム Ruby バージョンファイルを返す
205+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
206+
def ruby_file_version_file
207+
filename = ruby_file_version_filename
208+
return unless filename
209+
210+
dependency_files.find { |f| f.name == filename }
211+
end
212+
213+
sig { returns(T.nilable(String)) }
214+
def ruby_file_version_filename
215+
content = gemfile&.content
216+
return unless content
217+
218+
match = content.match(/^\s*ruby\s+file:\s*['"]([^'"]+)['"]/)
219+
match&.captures&.first
220+
end
221+
203222
sig { returns(T::Array[Dependabot::DependencyFile]) }
204223
def path_gemspecs
205224
all = dependency_files.select { |f| f.name.end_with?(".gemspec") }

bundler/spec/dependabot/bundler/file_fetcher_spec.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,48 @@
205205
end
206206
end
207207

208+
context "with a custom version file referenced via `ruby file:` option" do
209+
before do
210+
stub_request(:get, url + "?ref=sha")
211+
.with(headers: { "Authorization" => "token token" })
212+
.to_return(
213+
status: 200,
214+
body: fixture("github", "contents_ruby_with_custom_version_file.json"),
215+
headers: { "content-type" => "application/json" }
216+
)
217+
218+
stub_request(:get, url + "Gemfile?ref=sha")
219+
.with(headers: { "Authorization" => "token token" })
220+
.to_return(
221+
status: 200,
222+
body: fixture("github", "gemfile_with_ruby_file_option_content.json"),
223+
headers: { "content-type" => "application/json" }
224+
)
225+
226+
stub_request(:get, url + "Gemfile.lock?ref=sha")
227+
.with(headers: { "Authorization" => "token token" })
228+
.to_return(
229+
status: 200,
230+
body: fixture("github", "gemfile_lock_content.json"),
231+
headers: { "content-type" => "application/json" }
232+
)
233+
234+
stub_request(:get, url + "custom-ruby-version?ref=sha")
235+
.with(headers: { "Authorization" => "token token" })
236+
.to_return(
237+
status: 200,
238+
body: fixture("github", "custom_ruby_version_content.json"),
239+
headers: { "content-type" => "application/json" }
240+
)
241+
end
242+
243+
it "fetches the custom version file specified by `ruby file:` option" do
244+
expect(file_fetcher_instance.files.count).to eq(3)
245+
expect(file_fetcher_instance.files.map(&:name))
246+
.to include("custom-ruby-version")
247+
end
248+
end
249+
208250
context "with a gems.rb rather than a Gemfile" do
209251
before do
210252
stub_request(:get, url + "?ref=sha")

bundler/spec/dependabot/bundler/file_parser/file_preparer_spec.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@
5858
its(:content) { is_expected.to eq("ruby 2.2.0\n") }
5959
end
6060

61+
describe "the updated custom ruby version file specified by `ruby file:` option" do
62+
subject do
63+
prepared_dependency_files.find { |f| f.name == "custom-ruby-version" }
64+
end
65+
66+
let(:dependency_files) { bundler_project_dependency_files("ruby_file_option") }
67+
68+
its(:content) { is_expected.to eq("2.2.0\n") }
69+
end
70+
6171
describe "the updated .specification file" do
6272
subject do
6373
prepared_dependency_files.find { |f| f.name == "plugins/example/.specification" }

bundler/spec/dependabot/bundler/file_updater_spec.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,24 @@
547547
end
548548
end
549549

550+
context "when the Gemfile loads a custom ruby version file via `ruby file:` option" do
551+
let(:project_name) { "ruby_file_option" }
552+
let(:updater) do
553+
described_class.new(
554+
dependency_files: dependency_files,
555+
dependencies: [dependency],
556+
credentials: [{
557+
"type" => "git_source",
558+
"host" => "github.com"
559+
}]
560+
)
561+
end
562+
563+
it "locks the updated gem to the latest version" do
564+
expect(file.content).to include "business (1.5.0)"
565+
end
566+
end
567+
550568
context "when the Gemfile.lock didn't have a BUNDLED WITH line" do
551569
let(:project_name) { "no_bundled_with" }
552570

bundler/spec/dependabot/bundler/update_checker/latest_version_finder_spec.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@
227227
its([:version]) { is_expected.to eq(Dependabot::Bundler::Version.new("1.5.0")) }
228228
end
229229

230+
context "when the Gemfile loads a custom ruby version file via `ruby file:` option" do
231+
let(:dependency_files) { bundler_project_dependency_files("ruby_file_option") }
232+
233+
its([:version]) { is_expected.to eq(Dependabot::Bundler::Version.new("1.5.0")) }
234+
end
235+
230236
context "with a gemspec and a Gemfile" do
231237
let(:dependency_files) { bundler_project_dependency_files("gemfile_small_example") }
232238

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[
2+
{
3+
"name": "Gemfile",
4+
"path": "Gemfile",
5+
"sha": "88b4e0a1c8093fae2b4fa52534035f9f85ed0956",
6+
"size": 100,
7+
"url": "https://api.github.com/repos/gocardless/bump/contents/Gemfile?ref=master",
8+
"html_url": "https://github.com/gocardless/bump/blob/master/Gemfile",
9+
"git_url": "https://api.github.com/repos/gocardless/bump/git/blobs/88b4e0a1c8093fae2b4fa52534035f9f85ed0956",
10+
"download_url": "https://raw.githubusercontent.com/gocardless/bump/master/Gemfile",
11+
"type": "file",
12+
"_links": {
13+
"self": "https://api.github.com/repos/gocardless/bump/contents/Gemfile?ref=master",
14+
"git": "https://api.github.com/repos/gocardless/bump/git/blobs/88b4e0a1c8093fae2b4fa52534035f9f85ed0956",
15+
"html": "https://github.com/gocardless/bump/blob/master/Gemfile"
16+
}
17+
},
18+
{
19+
"name": "Gemfile.lock",
20+
"path": "Gemfile.lock",
21+
"sha": "d429264c8c2f0f306a422900c2f41123e07c31b4",
22+
"size": 100,
23+
"url": "https://api.github.com/repos/gocardless/bump/contents/Gemfile.lock?ref=master",
24+
"html_url": "https://github.com/gocardless/bump/blob/master/Gemfile.lock",
25+
"git_url": "https://api.github.com/repos/gocardless/bump/git/blobs/d429264c8c2f0f306a422900c2f41123e07c31b4",
26+
"download_url": "https://raw.githubusercontent.com/gocardless/bump/master/Gemfile.lock",
27+
"type": "file",
28+
"_links": {
29+
"self": "https://api.github.com/repos/gocardless/bump/contents/Gemfile.lock?ref=master",
30+
"git": "https://api.github.com/repos/gocardless/bump/git/blobs/d429264c8c2f0f306a422900c2f41123e07c31b4",
31+
"html": "https://github.com/gocardless/bump/blob/master/Gemfile.lock"
32+
}
33+
},
34+
{
35+
"name": "custom-ruby-version",
36+
"path": "custom-ruby-version",
37+
"sha": "005119baaa0653ca59d923010341d8341daa8c43",
38+
"size": 6,
39+
"url": "https://api.github.com/repos/gocardless/bump/contents/custom-ruby-version?ref=master",
40+
"html_url": "https://github.com/gocardless/bump/blob/master/custom-ruby-version",
41+
"git_url": "https://api.github.com/repos/gocardless/bump/git/blobs/005119baaa0653ca59d923010341d8341daa8c43",
42+
"download_url": "https://raw.githubusercontent.com/gocardless/bump/master/custom-ruby-version",
43+
"type": "file",
44+
"_links": {
45+
"self": "https://api.github.com/repos/gocardless/bump/contents/custom-ruby-version?ref=master",
46+
"git": "https://api.github.com/repos/gocardless/bump/git/blobs/005119baaa0653ca59d923010341d8341daa8c43",
47+
"html": "https://github.com/gocardless/bump/blob/master/custom-ruby-version"
48+
}
49+
}
50+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "custom-ruby-version",
3+
"path": "custom-ruby-version",
4+
"sha": "005119baaa0653ca59d923010341d8341daa8c43",
5+
"size": 6,
6+
"url": "https://api.github.com/repos/gocardless/bump/contents/custom-ruby-version?ref=master",
7+
"html_url": "https://github.com/gocardless/bump/blob/master/custom-ruby-version",
8+
"git_url": "https://api.github.com/repos/gocardless/bump/git/blobs/005119baaa0653ca59d923010341d8341daa8c43",
9+
"download_url": "https://raw.githubusercontent.com/gocardless/bump/master/custom-ruby-version",
10+
"type": "file",
11+
"content": "My4yLjAK\n",
12+
"encoding": "base64",
13+
"_links": {
14+
"self": "https://api.github.com/repos/gocardless/bump/contents/custom-ruby-version?ref=master",
15+
"git": "https://api.github.com/repos/gocardless/bump/git/blobs/005119baaa0653ca59d923010341d8341daa8c43",
16+
"html": "https://github.com/gocardless/bump/blob/master/custom-ruby-version"
17+
}
18+
}

0 commit comments

Comments
 (0)