dependabot-bazel 0.344.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,358 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/dependency"
5
+ require "dependabot/dependency_file"
6
+ require "dependabot/file_parsers"
7
+ require "dependabot/file_parsers/base"
8
+ require "dependabot/file_parsers/base/dependency_set"
9
+ require "dependabot/bazel/version"
10
+ require "dependabot/errors"
11
+
12
+ module Dependabot
13
+ module Bazel
14
+ class FileParser < Dependabot::FileParsers::Base
15
+ extend T::Sig
16
+
17
+ require_relative "file_parser/starlark_parser"
18
+
19
+ REPOSITORY_REFERENCE = %r{@([^/]+)}
20
+
21
+ DEPS_REGEX = /deps\s*=\s*\[\s*([^\]]+)\]/mx
22
+
23
+ GITHUB_ARCHIVE_PATTERN = %r{
24
+ github\.com/[^/]+/[^/]+/archive/ # GitHub archive path
25
+ (?:v?([^/]+)) # Capture version (with optional 'v' prefix)
26
+ \.(?:tar\.gz|tar\.bz2|zip)$ # Archive extension
27
+ }x
28
+
29
+ GITHUB_RELEASE_PATTERN = %r{
30
+ github\.com/[^/]+/[^/]+/releases/download/ # GitHub releases path
31
+ (?:v?([^/]+))/ # Capture version (with optional 'v' prefix)
32
+ }x
33
+
34
+ GENERIC_VERSION_PATTERN = %r{
35
+ /(?:v?([0-9]+(?:\.[0-9]+)*(?:[+-][^/]*)?)) # Capture semantic version
36
+ (?:\.tar\.gz|\.zip|$) # Optional archive extension or end
37
+ }x
38
+
39
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
40
+ def parse
41
+ dependency_set = DependencySet.new
42
+ dependency_set += module_dependencies
43
+ dependency_set += workspace_dependencies
44
+ dependency_set += build_dependencies
45
+
46
+ dependencies = dependency_set.dependencies
47
+
48
+ dependencies.uniq { |dep| [dep.name, dep.version] }
49
+ end
50
+
51
+ private
52
+
53
+ sig { returns(DependencySet) }
54
+ def module_dependencies
55
+ dependency_set = DependencySet.new
56
+
57
+ module_files.each do |file|
58
+ dependency_set += parse_module_file(file)
59
+ end
60
+
61
+ dependency_set
62
+ end
63
+
64
+ sig { returns(DependencySet) }
65
+ def workspace_dependencies
66
+ dependency_set = DependencySet.new
67
+
68
+ workspace_files.each do |file|
69
+ dependency_set += parse_workspace_file(file)
70
+ end
71
+
72
+ dependency_set
73
+ end
74
+
75
+ sig { returns(DependencySet) }
76
+ def build_dependencies
77
+ dependency_set = DependencySet.new
78
+
79
+ build_files.each do |file|
80
+ dependency_set += parse_build_file(file)
81
+ end
82
+
83
+ dependency_set
84
+ end
85
+
86
+ sig { override.void }
87
+ def check_required_files
88
+ return if module_files.any? || workspace_files.any?
89
+
90
+ raise Dependabot::DependencyFileNotFound, "No MODULE.bazel or WORKSPACE file found!"
91
+ end
92
+
93
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
94
+ def module_files
95
+ @module_files ||= T.let(
96
+ dependency_files.select { |f| f.name.end_with?("MODULE.bazel") },
97
+ T.nilable(T::Array[Dependabot::DependencyFile])
98
+ )
99
+ end
100
+
101
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
102
+ def workspace_files
103
+ @workspace_files ||= T.let(
104
+ dependency_files.select { |f| f.name == "WORKSPACE" || f.name.end_with?("WORKSPACE.bazel") },
105
+ T.nilable(T::Array[Dependabot::DependencyFile])
106
+ )
107
+ end
108
+
109
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
110
+ def build_files
111
+ @build_files ||= T.let(
112
+ dependency_files.select { |f| f.name == "BUILD" || f.name.end_with?("BUILD.bazel") },
113
+ T.nilable(T::Array[Dependabot::DependencyFile])
114
+ )
115
+ end
116
+
117
+ sig { returns(T.nilable(String)) }
118
+ def bazel_version
119
+ bazelversion_file = dependency_files.find { |f| f.name == ".bazelversion" }
120
+ bazelversion_file&.content&.strip
121
+ end
122
+
123
+ sig { params(file: Dependabot::DependencyFile).returns(DependencySet) }
124
+ def parse_module_file(file)
125
+ dependency_set = DependencySet.new
126
+ content = file.content
127
+ return dependency_set unless content
128
+
129
+ parser = StarlarkParser.new(content)
130
+ function_calls = parser.parse_function_calls
131
+
132
+ function_calls.each do |func_call|
133
+ next unless func_call.name == "bazel_dep"
134
+
135
+ name = func_call.arguments["name"]
136
+ version = func_call.arguments["version"]
137
+
138
+ # Ensure name and version are strings
139
+ next unless name.is_a?(String) && version.is_a?(String)
140
+
141
+ dependency_set << Dependabot::Dependency.new(
142
+ name: name,
143
+ version: version,
144
+ requirements: [
145
+ {
146
+ file: file.name,
147
+ requirement: version,
148
+ groups: [],
149
+ source: nil
150
+ }
151
+ ],
152
+ package_manager: "bazel"
153
+ )
154
+ end
155
+
156
+ dependency_set
157
+ end
158
+
159
+ sig { params(file: Dependabot::DependencyFile).returns(DependencySet) }
160
+ def parse_workspace_file(file)
161
+ dependency_set = DependencySet.new
162
+ content = file.content
163
+ return dependency_set unless content
164
+
165
+ parser = StarlarkParser.new(content)
166
+ function_calls = parser.parse_function_calls
167
+
168
+ function_calls.each do |func_call|
169
+ dependency = case func_call.name
170
+ when "http_archive"
171
+ parse_http_archive_dependency(func_call, file)
172
+ when "git_repository"
173
+ parse_git_repository_dependency(func_call, file)
174
+ end
175
+
176
+ dependency_set << dependency if dependency
177
+ end
178
+
179
+ dependency_set
180
+ end
181
+
182
+ sig do
183
+ params(
184
+ func_call: StarlarkParser::FunctionCall,
185
+ file: Dependabot::DependencyFile
186
+ ).returns(T.nilable(Dependabot::Dependency))
187
+ end
188
+ def parse_http_archive_dependency(func_call, file)
189
+ name = func_call.arguments["name"]
190
+ urls = func_call.arguments["urls"]
191
+
192
+ return nil unless name.is_a?(String)
193
+
194
+ url = urls.is_a?(Array) ? urls.first : urls
195
+ return nil unless url.is_a?(String)
196
+
197
+ version = extract_version_from_url(url)
198
+ return nil unless version
199
+
200
+ Dependabot::Dependency.new(
201
+ name: name,
202
+ version: version,
203
+ requirements: [
204
+ {
205
+ file: file.name,
206
+ requirement: version,
207
+ groups: [],
208
+ source: { type: "http_archive", url: url }
209
+ }
210
+ ],
211
+ package_manager: "bazel"
212
+ )
213
+ end
214
+
215
+ sig do
216
+ params(
217
+ func_call: StarlarkParser::FunctionCall,
218
+ file: Dependabot::DependencyFile
219
+ ).returns(T.nilable(Dependabot::Dependency))
220
+ end
221
+ def parse_git_repository_dependency(func_call, file)
222
+ name = func_call.arguments["name"]
223
+ tag = func_call.arguments["tag"]
224
+ commit = func_call.arguments["commit"]
225
+
226
+ return nil unless name.is_a?(String)
227
+
228
+ version = tag || commit
229
+ return nil unless version.is_a?(String)
230
+
231
+ Dependabot::Dependency.new(
232
+ name: name,
233
+ version: version,
234
+ requirements: [
235
+ {
236
+ file: file.name,
237
+ requirement: version,
238
+ groups: [],
239
+ source: { type: "git_repository", tag: tag, commit: commit }
240
+ }
241
+ ],
242
+ package_manager: "bazel"
243
+ )
244
+ end
245
+
246
+ sig { params(file: Dependabot::DependencyFile).returns(DependencySet) }
247
+ def parse_build_file(file)
248
+ dependency_set = DependencySet.new
249
+ content = file.content
250
+ return dependency_set unless content
251
+
252
+ parser = StarlarkParser.new(content)
253
+ function_calls = parser.parse_function_calls
254
+
255
+ dependency_set += parse_load_statements(function_calls, file)
256
+ dependency_set += parse_dependency_references(content, file)
257
+
258
+ dependency_set
259
+ end
260
+
261
+ sig do
262
+ params(
263
+ function_calls: T::Array[StarlarkParser::FunctionCall],
264
+ file: Dependabot::DependencyFile
265
+ ).returns(DependencySet)
266
+ end
267
+ def parse_load_statements(function_calls, file)
268
+ dependency_set = DependencySet.new
269
+
270
+ function_calls.each do |func_call|
271
+ next unless func_call.name == "load"
272
+
273
+ first_arg = func_call.positional_arguments.first
274
+ next unless first_arg.is_a?(String)
275
+
276
+ match = first_arg.match(%r{^@([^/]+)})
277
+ next unless match
278
+
279
+ repo_name = match[1]
280
+ next unless repo_name
281
+ next if repo_name == "bazel_tools"
282
+
283
+ dependency_set << Dependabot::Dependency.new(
284
+ name: repo_name,
285
+ version: nil,
286
+ requirements: [
287
+ {
288
+ file: file.name,
289
+ requirement: nil,
290
+ groups: ["load_references"],
291
+ source: { type: "load_statement" }
292
+ }
293
+ ],
294
+ package_manager: "bazel"
295
+ )
296
+ end
297
+
298
+ dependency_set
299
+ end
300
+
301
+ sig do
302
+ params(
303
+ content: String,
304
+ file: Dependabot::DependencyFile
305
+ ).returns(DependencySet)
306
+ end
307
+ def parse_dependency_references(content, file)
308
+ dependency_set = DependencySet.new
309
+
310
+ content.scan(DEPS_REGEX) do |deps_content|
311
+ deps_content_str = deps_content[0]
312
+
313
+ deps_content_str.scan(REPOSITORY_REFERENCE) do |repo_name|
314
+ repo_name = repo_name[0]
315
+ next if repo_name == "bazel_tools"
316
+
317
+ dependency_set << Dependabot::Dependency.new(
318
+ name: repo_name,
319
+ version: nil,
320
+ requirements: [
321
+ {
322
+ file: file.name,
323
+ requirement: nil,
324
+ groups: ["deps"],
325
+ source: { type: "dependency_reference" }
326
+ }
327
+ ],
328
+ package_manager: "bazel"
329
+ )
330
+ end
331
+ end
332
+
333
+ dependency_set
334
+ end
335
+
336
+ sig { params(url: String).returns(T.nilable(String)) }
337
+ def extract_version_from_url(url)
338
+ version_patterns.each do |pattern|
339
+ match = url.match(pattern)
340
+ return match[1] if match
341
+ end
342
+
343
+ nil
344
+ end
345
+
346
+ sig { returns(T::Array[Regexp]) }
347
+ def version_patterns
348
+ [
349
+ GITHUB_ARCHIVE_PATTERN,
350
+ GITHUB_RELEASE_PATTERN,
351
+ GENERIC_VERSION_PATTERN
352
+ ]
353
+ end
354
+ end
355
+ end
356
+ end
357
+
358
+ Dependabot::FileParsers.register("bazel", Dependabot::Bazel::FileParser)
@@ -0,0 +1,99 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/bazel/file_updater"
6
+
7
+ module Dependabot
8
+ module Bazel
9
+ class FileUpdater < Dependabot::FileUpdaters::Base
10
+ class BzlmodFileUpdater
11
+ extend T::Sig
12
+
13
+ sig do
14
+ params(
15
+ dependency_files: T::Array[Dependabot::DependencyFile],
16
+ dependencies: T::Array[Dependabot::Dependency],
17
+ credentials: T::Array[Dependabot::Credential]
18
+ ).void
19
+ end
20
+ def initialize(dependency_files:, dependencies:, credentials:)
21
+ @dependency_files = dependency_files
22
+ @dependencies = dependencies
23
+ @credentials = credentials
24
+ end
25
+
26
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
27
+ def updated_module_files
28
+ module_files.filter_map do |file|
29
+ updated_content = update_file_content(file)
30
+ next if updated_content == T.must(file.content)
31
+
32
+ file.dup.tap { |f| f.content = updated_content }
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
39
+ attr_reader :dependency_files
40
+
41
+ sig { returns(T::Array[Dependabot::Dependency]) }
42
+ attr_reader :dependencies
43
+
44
+ sig { returns(T::Array[Dependabot::Credential]) }
45
+ attr_reader :credentials
46
+
47
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
48
+ def module_files
49
+ @module_files ||= T.let(
50
+ dependency_files.select { |f| f.name.end_with?("MODULE.bazel") },
51
+ T.nilable(T::Array[Dependabot::DependencyFile])
52
+ )
53
+ end
54
+
55
+ sig { params(file: Dependabot::DependencyFile).returns(String) }
56
+ def update_file_content(file)
57
+ content = T.must(file.content).dup
58
+
59
+ dependencies.each do |dependency|
60
+ content = update_dependency_in_content(content, dependency)
61
+ end
62
+
63
+ content
64
+ end
65
+
66
+ sig { params(content: String, dependency: Dependabot::Dependency).returns(String) }
67
+ def update_dependency_in_content(content, dependency)
68
+ return content unless dependency.package_manager == "bazel"
69
+
70
+ update_bazel_dep_version(content, dependency)
71
+ end
72
+
73
+ sig { params(content: String, dependency: Dependabot::Dependency).returns(String) }
74
+ def update_bazel_dep_version(content, dependency)
75
+ new_version = dependency.version
76
+ return content unless new_version
77
+
78
+ escaped_name = Regexp.escape(dependency.name)
79
+
80
+ bazel_dep_pattern = /bazel_dep\s*\(([^)]+?)\)/mx
81
+
82
+ content.gsub(bazel_dep_pattern) do |match|
83
+ function_content = T.must(Regexp.last_match(1))
84
+
85
+ if /name\s*=\s*["']#{escaped_name}["']/.match?(function_content)
86
+ updated_function_content = function_content.gsub(
87
+ /version\s*=\s*["'][^"']*["']/,
88
+ "version = \"#{new_version}\""
89
+ )
90
+ "bazel_dep(#{updated_function_content})"
91
+ else
92
+ match
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,78 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/bazel/file_updater"
6
+
7
+ module Dependabot
8
+ module Bazel
9
+ class FileUpdater < Dependabot::FileUpdaters::Base
10
+ class DeclarationParser
11
+ extend T::Sig
12
+
13
+ sig { params(declaration_text: String).returns(T::Hash[Symbol, String]) }
14
+ def self.parse_bazel_dep(declaration_text)
15
+ attributes = {}
16
+
17
+ name_match = declaration_text.match(/name\s*=\s*["']([^"']+)["']/)
18
+ attributes[:name] = name_match[1] if name_match
19
+
20
+ version_match = declaration_text.match(/version\s*=\s*["']([^"']+)["']/)
21
+ attributes[:version] = version_match[1] if version_match
22
+
23
+ dev_dep_match = declaration_text.match(/dev_dependency\s*=\s*(True|False)/)
24
+ attributes[:dev_dependency] = dev_dep_match[1] if dev_dep_match
25
+
26
+ repo_name_match = declaration_text.match(/repo_name\s*=\s*["']([^"']+)["']/)
27
+ attributes[:repo_name] = repo_name_match[1] if repo_name_match
28
+
29
+ attributes
30
+ end
31
+
32
+ sig { params(declaration_text: String).returns(T::Hash[Symbol, String]) }
33
+ def self.parse_http_archive(declaration_text)
34
+ attributes = {}
35
+
36
+ name_match = declaration_text.match(/name\s*=\s*["']([^"']+)["']/)
37
+ attributes[:name] = name_match[1] if name_match
38
+
39
+ url_match = declaration_text.match(/url\s*=\s*["']([^"']+)["']/)
40
+ attributes[:url] = url_match[1] if url_match
41
+
42
+ urls_match = declaration_text.match(/urls\s*=\s*\[(.*?)\]/m)
43
+ attributes[:urls] = urls_match[1] if urls_match
44
+
45
+ sha256_match = declaration_text.match(/sha256\s*=\s*["']([^"']+)["']/)
46
+ attributes[:sha256] = sha256_match[1] if sha256_match
47
+
48
+ strip_prefix_match = declaration_text.match(/strip_prefix\s*=\s*["']([^"']+)["']/)
49
+ attributes[:strip_prefix] = strip_prefix_match[1] if strip_prefix_match
50
+
51
+ attributes
52
+ end
53
+
54
+ sig { params(declaration_text: String).returns(T::Hash[Symbol, String]) }
55
+ def self.parse_git_repository(declaration_text)
56
+ attributes = {}
57
+
58
+ name_match = declaration_text.match(/name\s*=\s*["']([^"']+)["']/)
59
+ attributes[:name] = name_match[1] if name_match
60
+
61
+ remote_match = declaration_text.match(/remote\s*=\s*["']([^"']+)["']/)
62
+ attributes[:remote] = remote_match[1] if remote_match
63
+
64
+ tag_match = declaration_text.match(/tag\s*=\s*["']([^"']+)["']/)
65
+ attributes[:tag] = tag_match[1] if tag_match
66
+
67
+ commit_match = declaration_text.match(/commit\s*=\s*["']([^"']+)["']/)
68
+ attributes[:commit] = commit_match[1] if commit_match
69
+
70
+ branch_match = declaration_text.match(/branch\s*=\s*["']([^"']+)["']/)
71
+ attributes[:branch] = branch_match[1] if branch_match
72
+
73
+ attributes
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end