dependabot-cargo 0.384.0 → 0.386.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.
- checksums.yaml +4 -4
- data/lib/dependabot/cargo/file_fetcher.rb +143 -81
- data/lib/dependabot/cargo/file_parser.rb +71 -31
- data/lib/dependabot/cargo/file_updater/lockfile_updater.rb +37 -15
- data/lib/dependabot/cargo/helpers.rb +14 -7
- data/lib/dependabot/cargo/metadata_finder.rb +25 -11
- data/lib/dependabot/cargo/package/package_details_fetcher.rb +28 -22
- data/lib/dependabot/cargo/update_checker/file_preparer.rb +107 -42
- data/lib/dependabot/cargo/update_checker/requirements_updater.rb +3 -3
- data/lib/dependabot/cargo/update_checker/version_resolver.rb +19 -12
- data/lib/dependabot/cargo/update_checker.rb +7 -7
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 342398b3a35a615f1b23ae103f2a61e3c11ab24fb92b2eb3f6227b68cdae6600
|
|
4
|
+
data.tar.gz: a087cc721a765e9b8fd424a6972e1f110895947e36048906832a6f7acc59baed
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 236e947dcc2a2a2674432ac934826740dd42591dd0568211059e11bc79d5f3875c70f63aedab2f03f5351170e3d2a9dd7537385e2c3fd240f5c02621a4624973
|
|
7
|
+
data.tar.gz: affa0255ca4605212a1a233cc636893532aa5b5743d94fd4a1fb1d75e70bc52ce36fb245c92cd19e5c13787906702137be32492cbc5b08327bb19875ff7ecfe0
|
|
@@ -28,7 +28,7 @@ module Dependabot
|
|
|
28
28
|
"Repo must contain a Cargo.toml."
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
sig { override.returns(T.nilable(T::Hash[Symbol, T.
|
|
31
|
+
sig { override.returns(T.nilable(T::Hash[Symbol, T.anything])) }
|
|
32
32
|
def ecosystem_versions
|
|
33
33
|
channel = if rust_toolchain
|
|
34
34
|
TomlRB.parse(T.must(rust_toolchain).content).dig("toolchain", "channel")
|
|
@@ -47,7 +47,7 @@ module Dependabot
|
|
|
47
47
|
def fetch_files
|
|
48
48
|
fetched_files = T.let([cargo_toml], T::Array[DependencyFile])
|
|
49
49
|
fetched_files << T.must(cargo_lock) if cargo_lock
|
|
50
|
-
fetched_files
|
|
50
|
+
fetched_files.concat(cargo_configs)
|
|
51
51
|
fetched_files << T.must(rust_toolchain) if rust_toolchain
|
|
52
52
|
fetched_files += fetch_path_dependency_and_workspace_files
|
|
53
53
|
parsed_manifest = parsed_file(cargo_toml)
|
|
@@ -180,12 +180,14 @@ module Dependabot
|
|
|
180
180
|
find_workspace_root(file)
|
|
181
181
|
end
|
|
182
182
|
|
|
183
|
-
sig { params(dependencies: T::Hash[
|
|
183
|
+
sig { params(dependencies: T::Hash[String, T.anything]).returns(T::Array[String]) }
|
|
184
184
|
def collect_path_dependencies_paths(dependencies)
|
|
185
185
|
dependencies.filter_map do |_, details|
|
|
186
|
-
|
|
186
|
+
details = toml_table(details)
|
|
187
|
+
path = T.cast(details&.fetch("path", nil), T.nilable(String))
|
|
188
|
+
next unless path
|
|
187
189
|
|
|
188
|
-
File.join(
|
|
190
|
+
File.join(path, "Cargo.toml").delete_prefix("/")
|
|
189
191
|
end
|
|
190
192
|
end
|
|
191
193
|
|
|
@@ -194,18 +196,19 @@ module Dependabot
|
|
|
194
196
|
def path_dependency_paths_from_file(file)
|
|
195
197
|
paths = T.let([], T::Array[String])
|
|
196
198
|
|
|
197
|
-
workspace = parsed_file(file).fetch("workspace", {})
|
|
199
|
+
workspace = toml_table_or_empty(parsed_file(file).fetch("workspace", {}))
|
|
198
200
|
Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
|
|
199
201
|
# Paths specified in dependency declaration
|
|
200
|
-
paths += collect_path_dependencies_paths(parsed_file(file).fetch(type, {}))
|
|
202
|
+
paths += collect_path_dependencies_paths(toml_table_or_empty(parsed_file(file).fetch(type, {})))
|
|
201
203
|
# Paths specified as workspace dependencies in workspace root
|
|
202
|
-
paths += collect_path_dependencies_paths(workspace.fetch(type, {}))
|
|
204
|
+
paths += collect_path_dependencies_paths(toml_table_or_empty(workspace.fetch(type, {})))
|
|
203
205
|
end
|
|
204
206
|
|
|
205
207
|
# Paths specified for target-specific dependencies
|
|
206
|
-
parsed_file(file).fetch("target", {}).each do |_, t_details|
|
|
208
|
+
toml_table_or_empty(parsed_file(file).fetch("target", {})).each do |_, t_details|
|
|
209
|
+
t_details = toml_table_or_empty(t_details)
|
|
207
210
|
Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
|
|
208
|
-
paths += collect_path_dependencies_paths(t_details.fetch(type, {}))
|
|
211
|
+
paths += collect_path_dependencies_paths(toml_table_or_empty(t_details.fetch(type, {})))
|
|
209
212
|
end
|
|
210
213
|
end
|
|
211
214
|
|
|
@@ -217,20 +220,25 @@ module Dependabot
|
|
|
217
220
|
paths = []
|
|
218
221
|
|
|
219
222
|
# Paths specified as replacements
|
|
220
|
-
parsed_file(file).fetch("replace", {}).each do |_, details|
|
|
221
|
-
|
|
223
|
+
toml_table_or_empty(parsed_file(file).fetch("replace", {})).each do |_, details|
|
|
224
|
+
details = toml_table(details)
|
|
225
|
+
path = T.cast(details&.fetch("path", nil), T.nilable(String))
|
|
226
|
+
next unless path
|
|
222
227
|
|
|
223
|
-
paths << File.join(
|
|
228
|
+
paths << File.join(path, "Cargo.toml")
|
|
224
229
|
end
|
|
225
230
|
|
|
226
231
|
# Paths specified as patches
|
|
227
|
-
parsed_file(file).fetch("patch", {}).each do |_, details|
|
|
228
|
-
|
|
232
|
+
toml_table_or_empty(parsed_file(file).fetch("patch", {})).each do |_, details|
|
|
233
|
+
details = toml_table(details)
|
|
234
|
+
next unless details
|
|
229
235
|
|
|
230
236
|
details.each do |_, dep_details|
|
|
231
|
-
|
|
237
|
+
dep_details = toml_table(dep_details)
|
|
238
|
+
path = T.cast(dep_details&.fetch("path", nil), T.nilable(String))
|
|
239
|
+
next unless path
|
|
232
240
|
|
|
233
|
-
paths << File.join(
|
|
241
|
+
paths << File.join(path, "Cargo.toml")
|
|
234
242
|
end
|
|
235
243
|
end
|
|
236
244
|
|
|
@@ -239,28 +247,31 @@ module Dependabot
|
|
|
239
247
|
|
|
240
248
|
# Check if this Cargo manifest uses workspace dependencies
|
|
241
249
|
# (e.g. dependency = { workspace = true }).
|
|
242
|
-
sig { params(parsed_manifest: T::Hash[
|
|
250
|
+
sig { params(parsed_manifest: T::Hash[String, T.anything]).returns(T::Boolean) }
|
|
243
251
|
def uses_workspace_dependencies?(parsed_manifest)
|
|
244
252
|
# Check regular dependencies
|
|
245
253
|
workspace_deps = Cargo::FileParser::DEPENDENCY_TYPES.any? do |type|
|
|
246
|
-
deps = parsed_manifest.fetch(type, {})
|
|
254
|
+
deps = toml_table_or_empty(parsed_manifest.fetch(type, {}))
|
|
247
255
|
deps.any? do |_, details|
|
|
248
|
-
|
|
256
|
+
details = toml_table(details)
|
|
257
|
+
next false unless details
|
|
249
258
|
|
|
250
|
-
details["workspace"] == true
|
|
259
|
+
T.cast(details["workspace"], T.nilable(Object)) == true
|
|
251
260
|
end
|
|
252
261
|
end
|
|
253
262
|
|
|
254
263
|
return true if workspace_deps
|
|
255
264
|
|
|
256
265
|
# Check target-specific dependencies
|
|
257
|
-
parsed_manifest.fetch("target", {}).any? do |_, target_details|
|
|
266
|
+
toml_table_or_empty(parsed_manifest.fetch("target", {})).any? do |_, target_details|
|
|
267
|
+
target_details = toml_table_or_empty(target_details)
|
|
258
268
|
Cargo::FileParser::DEPENDENCY_TYPES.any? do |type|
|
|
259
|
-
deps = target_details.fetch(type, {})
|
|
269
|
+
deps = toml_table_or_empty(target_details.fetch(type, {}))
|
|
260
270
|
deps.any? do |_, details|
|
|
261
|
-
|
|
271
|
+
details = toml_table(details)
|
|
272
|
+
next false unless details
|
|
262
273
|
|
|
263
|
-
details["workspace"] == true
|
|
274
|
+
T.cast(details["workspace"], T.nilable(Object)) == true
|
|
264
275
|
end
|
|
265
276
|
end
|
|
266
277
|
end
|
|
@@ -268,13 +279,14 @@ module Dependabot
|
|
|
268
279
|
|
|
269
280
|
# See if this Cargo manifest inherits any property from a workspace
|
|
270
281
|
# (e.g. edition = { workspace = true }).
|
|
271
|
-
sig { params(hash: T::Hash[
|
|
282
|
+
sig { params(hash: T::Hash[String, T.anything]).returns(T::Boolean) }
|
|
272
283
|
def workspace_member?(hash)
|
|
273
284
|
hash.each do |key, value|
|
|
285
|
+
value = T.cast(value, T.nilable(Object))
|
|
274
286
|
if key == "workspace" && value == true
|
|
275
287
|
return true
|
|
276
288
|
elsif value.is_a?(Hash)
|
|
277
|
-
return workspace_member?(value)
|
|
289
|
+
return workspace_member?(toml_table_or_empty(value))
|
|
278
290
|
end
|
|
279
291
|
end
|
|
280
292
|
false
|
|
@@ -289,7 +301,8 @@ module Dependabot
|
|
|
289
301
|
def find_workspace_root(workspace_member)
|
|
290
302
|
current_dir = workspace_member.name.rpartition("/").first
|
|
291
303
|
|
|
292
|
-
|
|
304
|
+
package = toml_table_or_empty(parsed_file(workspace_member)["package"])
|
|
305
|
+
workspace_root_dir = T.cast(package["workspace"], T.nilable(String))
|
|
293
306
|
unless workspace_root_dir.nil?
|
|
294
307
|
workspace_root = fetch_file_from_host(
|
|
295
308
|
File.join(current_dir, workspace_root_dir, "Cargo.toml"),
|
|
@@ -322,11 +335,10 @@ module Dependabot
|
|
|
322
335
|
|
|
323
336
|
sig { params(file: Dependabot::DependencyFile).returns(T::Array[String]) }
|
|
324
337
|
def workspace_dependency_paths_from_file(file)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
end
|
|
338
|
+
workspace = toml_table(parsed_file(file)["workspace"])
|
|
339
|
+
return path_dependency_paths_from_file(file) if workspace && !workspace.key?("members")
|
|
328
340
|
|
|
329
|
-
workspace_paths =
|
|
341
|
+
workspace_paths = T.cast(workspace&.fetch("members", nil), T.nilable(T::Array[String]))
|
|
330
342
|
return [] unless workspace_paths&.any?
|
|
331
343
|
|
|
332
344
|
# Expand any workspace paths that specify a `*`
|
|
@@ -335,9 +347,7 @@ module Dependabot
|
|
|
335
347
|
end
|
|
336
348
|
|
|
337
349
|
# Excluded paths, to be subtracted for the workspaces array
|
|
338
|
-
excluded_paths =
|
|
339
|
-
(parsed_file(file).dig("workspace", "excluded_paths") || []) +
|
|
340
|
-
(parsed_file(file).dig("workspace", "exclude") || [])
|
|
350
|
+
excluded_paths = workspace_excluded_paths(workspace)
|
|
341
351
|
|
|
342
352
|
(workspace_paths - excluded_paths).map do |path|
|
|
343
353
|
File.join(path, "Cargo.toml")
|
|
@@ -346,59 +356,40 @@ module Dependabot
|
|
|
346
356
|
|
|
347
357
|
# Check whether a path is required or not. It will not be required if
|
|
348
358
|
# an alternative source (i.e., a git source) is also specified
|
|
349
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
350
359
|
# rubocop:disable Metrics/PerceivedComplexity
|
|
351
|
-
# rubocop:disable Metrics/AbcSize
|
|
352
360
|
sig { params(file: Dependabot::DependencyFile, path: String).returns(T::Boolean) }
|
|
353
361
|
def required_path?(file, path)
|
|
354
362
|
# Paths specified in dependency declaration
|
|
355
363
|
Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
|
|
356
|
-
parsed_file(file).fetch(type, {}).each do |_, details|
|
|
357
|
-
|
|
358
|
-
next unless details["path"]
|
|
359
|
-
next unless path == File.join(details["path"], "Cargo.toml")
|
|
360
|
-
|
|
361
|
-
return true if details["git"].nil?
|
|
364
|
+
toml_table_or_empty(parsed_file(file).fetch(type, {})).each do |_, details|
|
|
365
|
+
return true if required_dependency_details?(details, path)
|
|
362
366
|
end
|
|
363
367
|
end
|
|
364
368
|
|
|
365
369
|
# Paths specified for target-specific dependencies
|
|
366
|
-
parsed_file(file).fetch("target", {}).each do |_, t_details|
|
|
370
|
+
toml_table_or_empty(parsed_file(file).fetch("target", {})).each do |_, t_details|
|
|
371
|
+
t_details = toml_table_or_empty(t_details)
|
|
367
372
|
Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
|
|
368
|
-
t_details.fetch(type, {}).each do |_, details|
|
|
369
|
-
|
|
370
|
-
next unless details["path"]
|
|
371
|
-
next unless path == File.join(details["path"], "Cargo.toml")
|
|
372
|
-
|
|
373
|
-
return true if details["git"].nil?
|
|
373
|
+
toml_table_or_empty(t_details.fetch(type, {})).each do |_, details|
|
|
374
|
+
return true if required_dependency_details?(details, path)
|
|
374
375
|
end
|
|
375
376
|
end
|
|
376
377
|
end
|
|
377
378
|
|
|
378
379
|
# Paths specified for workspace-wide dependencies
|
|
379
|
-
workspace = parsed_file(file).fetch("workspace", {})
|
|
380
|
-
workspace.fetch("dependencies", {}).each do |_, details|
|
|
381
|
-
|
|
382
|
-
next unless details["path"]
|
|
383
|
-
next unless path == File.join(details["path"], "Cargo.toml")
|
|
384
|
-
|
|
385
|
-
return true if details["git"].nil?
|
|
380
|
+
workspace = toml_table_or_empty(parsed_file(file).fetch("workspace", {}))
|
|
381
|
+
toml_table_or_empty(workspace.fetch("dependencies", {})).each do |_, details|
|
|
382
|
+
return true if required_dependency_details?(details, path)
|
|
386
383
|
end
|
|
387
384
|
|
|
388
385
|
# Paths specified as replacements
|
|
389
|
-
parsed_file(file).fetch("replace", {}).each do |_, details|
|
|
390
|
-
|
|
391
|
-
next unless details["path"]
|
|
392
|
-
next unless path == File.join(details["path"], "Cargo.toml")
|
|
393
|
-
|
|
394
|
-
return true if details["git"].nil?
|
|
386
|
+
toml_table_or_empty(parsed_file(file).fetch("replace", {})).each do |_, details|
|
|
387
|
+
return true if required_dependency_details?(details, path)
|
|
395
388
|
end
|
|
396
389
|
|
|
397
390
|
false
|
|
398
391
|
end
|
|
399
|
-
# rubocop:enable Metrics/AbcSize
|
|
400
392
|
# rubocop:enable Metrics/PerceivedComplexity
|
|
401
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
402
393
|
|
|
403
394
|
sig { params(path: String).returns(T::Array[String]) }
|
|
404
395
|
def expand_workspaces(path)
|
|
@@ -411,13 +402,44 @@ module Dependabot
|
|
|
411
402
|
.select { |filename| File.fnmatch?(path, filename) }
|
|
412
403
|
end
|
|
413
404
|
|
|
414
|
-
sig { params(file: Dependabot::DependencyFile).returns(T::Hash[
|
|
405
|
+
sig { params(file: Dependabot::DependencyFile).returns(T::Hash[String, T.anything]) }
|
|
415
406
|
def parsed_file(file)
|
|
416
407
|
TomlRB.parse(file.content)
|
|
417
408
|
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
|
|
418
409
|
raise Dependabot::DependencyFileNotParseable, file.path
|
|
419
410
|
end
|
|
420
411
|
|
|
412
|
+
sig { params(workspace: T.nilable(T::Hash[String, T.anything])).returns(T::Array[String]) }
|
|
413
|
+
def workspace_excluded_paths(workspace)
|
|
414
|
+
(T.cast(workspace&.fetch("excluded_paths", nil), T.nilable(T::Array[String])) || []) +
|
|
415
|
+
(T.cast(workspace&.fetch("exclude", nil), T.nilable(T::Array[String])) || [])
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
sig { params(details: T.anything, path: String).returns(T::Boolean) }
|
|
419
|
+
def required_dependency_details?(details, path)
|
|
420
|
+
details = toml_table(details)
|
|
421
|
+
dependency_path = T.cast(details&.fetch("path", nil), T.nilable(String))
|
|
422
|
+
return false unless details && dependency_path
|
|
423
|
+
return false unless path == File.join(dependency_path, "Cargo.toml")
|
|
424
|
+
|
|
425
|
+
T.cast(details["git"], T.nilable(Object)).nil?
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Returns the value as a TOML table (Hash) when it is one, otherwise nil.
|
|
429
|
+
# TOML dependency values are polymorphic (e.g. `foo = "1.0"` is a String
|
|
430
|
+
# while `foo = { version = "1.0" }` is a table), so callers must narrow at
|
|
431
|
+
# runtime rather than assert a type with T.cast (which would raise).
|
|
432
|
+
sig { params(value: T.anything).returns(T.nilable(T::Hash[String, T.anything])) }
|
|
433
|
+
def toml_table(value)
|
|
434
|
+
obj = T.cast(value, T.nilable(Object))
|
|
435
|
+
obj.is_a?(Hash) ? obj : nil
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
sig { params(value: T.anything).returns(T::Hash[String, T.anything]) }
|
|
439
|
+
def toml_table_or_empty(value)
|
|
440
|
+
toml_table(value) || {}
|
|
441
|
+
end
|
|
442
|
+
|
|
421
443
|
sig { returns(Dependabot::DependencyFile) }
|
|
422
444
|
def cargo_toml
|
|
423
445
|
@cargo_toml ||= T.let(fetch_file_from_host("Cargo.toml"), T.nilable(Dependabot::DependencyFile))
|
|
@@ -428,19 +450,53 @@ module Dependabot
|
|
|
428
450
|
@cargo_lock ||= T.let(fetch_file_if_present("Cargo.lock"), T.nilable(Dependabot::DependencyFile))
|
|
429
451
|
end
|
|
430
452
|
|
|
453
|
+
# Cargo merges configuration hierarchically: it reads `.cargo/config.toml`
|
|
454
|
+
# from the package directory *and* every ancestor directory up to the repo
|
|
455
|
+
# root, combining them. We therefore collect all of them so that registries
|
|
456
|
+
# (and other settings) defined higher up the tree are not dropped.
|
|
457
|
+
#
|
|
458
|
+
# The package-directory config keeps the canonical name `.cargo/config.toml`
|
|
459
|
+
# (single-config consumers rely on this). Ancestor configs keep their
|
|
460
|
+
# relative `../` paths so they are written to the correct location and can
|
|
461
|
+
# be merged by Cargo. When the package directory has no config of its own,
|
|
462
|
+
# the nearest ancestor is promoted to the canonical name to preserve the
|
|
463
|
+
# historical behaviour that downstream consumers depend on.
|
|
464
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
465
|
+
def cargo_configs
|
|
466
|
+
return T.must(@cargo_configs) if defined?(@cargo_configs)
|
|
467
|
+
|
|
468
|
+
configs = T.let([], T::Array[Dependabot::DependencyFile])
|
|
469
|
+
local = local_cargo_config
|
|
470
|
+
parents = parent_dir_cargo_configs
|
|
471
|
+
|
|
472
|
+
if local
|
|
473
|
+
configs << local
|
|
474
|
+
configs.concat(parents)
|
|
475
|
+
elsif parents.any?
|
|
476
|
+
effective = T.must(parents.first)
|
|
477
|
+
effective.name = ".cargo/config.toml"
|
|
478
|
+
configs << effective
|
|
479
|
+
configs.concat(parents.drop(1))
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
@cargo_configs = T.let(configs, T.nilable(T::Array[Dependabot::DependencyFile]))
|
|
483
|
+
configs
|
|
484
|
+
end
|
|
485
|
+
|
|
431
486
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
432
487
|
def cargo_config
|
|
433
|
-
|
|
488
|
+
cargo_configs.find { |f| f.name == ".cargo/config.toml" }
|
|
489
|
+
end
|
|
434
490
|
|
|
435
|
-
|
|
436
|
-
|
|
491
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
492
|
+
def local_cargo_config
|
|
493
|
+
return @local_cargo_config if defined?(@local_cargo_config)
|
|
494
|
+
|
|
495
|
+
@local_cargo_config = fetch_support_file(".cargo/config.toml")
|
|
496
|
+
@local_cargo_config ||= T.let(
|
|
437
497
|
fetch_support_file(".cargo/config")&.tap { |f| f.name = ".cargo/config.toml" },
|
|
438
498
|
T.nilable(Dependabot::DependencyFile)
|
|
439
499
|
)
|
|
440
|
-
@cargo_config ||= T.let(
|
|
441
|
-
fetch_cargo_config_from_parent_dirs,
|
|
442
|
-
T.nilable(Dependabot::DependencyFile)
|
|
443
|
-
)
|
|
444
500
|
end
|
|
445
501
|
|
|
446
502
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
|
@@ -470,22 +526,26 @@ module Dependabot
|
|
|
470
526
|
super.tap { |f| f.name = Pathname.new(f.name).cleanpath.to_s.gsub(%r{^/+}, "") }
|
|
471
527
|
end
|
|
472
528
|
|
|
473
|
-
sig { returns(T
|
|
474
|
-
def
|
|
475
|
-
return
|
|
529
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
530
|
+
def parent_dir_cargo_configs
|
|
531
|
+
return T.must(@parent_dir_cargo_configs) if defined?(@parent_dir_cargo_configs)
|
|
532
|
+
|
|
533
|
+
configs = T.let([], T::Array[Dependabot::DependencyFile])
|
|
534
|
+
@parent_dir_cargo_configs = T.let(configs, T.nilable(T::Array[Dependabot::DependencyFile]))
|
|
535
|
+
return configs if directory.empty?
|
|
476
536
|
|
|
477
537
|
# Count directory depth to determine how many levels to search up
|
|
478
538
|
depth = directory.split("/").count { |s| !s.empty? }
|
|
479
|
-
return
|
|
539
|
+
return configs if depth.zero?
|
|
480
540
|
|
|
481
|
-
#
|
|
541
|
+
# Collect a config from every ancestor directory that has one, nearest first
|
|
482
542
|
depth.times do |i|
|
|
483
543
|
parent_path = ([".."] * (i + 1)).join("/")
|
|
484
544
|
config = try_fetch_config_at_path(parent_path)
|
|
485
|
-
|
|
545
|
+
configs << config if config
|
|
486
546
|
end
|
|
487
547
|
|
|
488
|
-
|
|
548
|
+
configs
|
|
489
549
|
end
|
|
490
550
|
|
|
491
551
|
sig { params(parent_path: String).returns(T.nilable(Dependabot::DependencyFile)) }
|
|
@@ -499,7 +559,9 @@ module Dependabot
|
|
|
499
559
|
)
|
|
500
560
|
Dependabot.logger.debug("Successfully fetched config from: #{full_path}")
|
|
501
561
|
config.support_file = true
|
|
502
|
-
config
|
|
562
|
+
# Normalise `.cargo/config` to `.cargo/config.toml` while preserving the
|
|
563
|
+
# relative `../` path so the file is written to the right location.
|
|
564
|
+
config.name = Pathname.new(File.join(parent_path, ".cargo/config.toml")).cleanpath.to_s
|
|
503
565
|
return config
|
|
504
566
|
rescue Dependabot::DependencyFileNotFound
|
|
505
567
|
Dependabot.logger.debug("No config found at: #{full_path}")
|
|
@@ -105,9 +105,8 @@ module Dependabot
|
|
|
105
105
|
def check_rust_workspace_root
|
|
106
106
|
cargo_toml = dependency_files.find { |f| f.name == "Cargo.toml" }
|
|
107
107
|
workspace_root = parsed_file(T.must(cargo_toml))
|
|
108
|
-
return unless workspace_root.is_a?(Hash)
|
|
109
108
|
|
|
110
|
-
workspace_config =
|
|
109
|
+
workspace_config = T.cast(toml_table_or_empty(workspace_root["package"])["workspace"], T.nilable(String))
|
|
111
110
|
return unless workspace_config
|
|
112
111
|
|
|
113
112
|
msg = "This project is part of a Rust workspace but is not the " \
|
|
@@ -129,10 +128,10 @@ module Dependabot
|
|
|
129
128
|
|
|
130
129
|
manifest_files.each do |file|
|
|
131
130
|
parsed_content = parsed_file(file)
|
|
132
|
-
next unless parsed_content.is_a?(Hash)
|
|
133
131
|
|
|
134
132
|
DEPENDENCY_TYPES.each do |type|
|
|
135
|
-
parsed_content.fetch(type, {}).each do |name, requirement|
|
|
133
|
+
toml_table_or_empty(parsed_content.fetch(type, {})).each do |name, requirement|
|
|
134
|
+
requirement = dependency_declaration(requirement)
|
|
136
135
|
# Skip workspace-inherited dependencies (similar to pnpm catalog)
|
|
137
136
|
# Only skip if workspace is exactly boolean true
|
|
138
137
|
next if requirement.is_a?(Hash) && requirement["workspace"] == true
|
|
@@ -143,8 +142,10 @@ module Dependabot
|
|
|
143
142
|
dependency_set << build_dependency(name, requirement, type, file)
|
|
144
143
|
end
|
|
145
144
|
|
|
146
|
-
parsed_content.fetch("target", {}).each do |_, t_details|
|
|
147
|
-
t_details
|
|
145
|
+
toml_table_or_empty(parsed_content.fetch("target", {})).each do |_, t_details|
|
|
146
|
+
t_details = toml_table_or_empty(t_details)
|
|
147
|
+
toml_table_or_empty(t_details.fetch(type, {})).each do |name, requirement|
|
|
148
|
+
requirement = dependency_declaration(requirement)
|
|
148
149
|
# Skip workspace-inherited dependencies
|
|
149
150
|
# Only skip if workspace is exactly boolean true
|
|
150
151
|
next if requirement.is_a?(Hash) && requirement["workspace"] == true
|
|
@@ -158,8 +159,9 @@ module Dependabot
|
|
|
158
159
|
end
|
|
159
160
|
end
|
|
160
161
|
|
|
161
|
-
workspace = parsed_content.fetch("workspace", {})
|
|
162
|
-
workspace.fetch("dependencies", {}).each do |name, requirement|
|
|
162
|
+
workspace = toml_table_or_empty(parsed_content.fetch("workspace", {}))
|
|
163
|
+
toml_table_or_empty(workspace.fetch("dependencies", {})).each do |name, requirement|
|
|
164
|
+
requirement = dependency_declaration(requirement)
|
|
163
165
|
next unless name == name_from_declaration(name, requirement)
|
|
164
166
|
next if lockfile && !version_from_lockfile(name, requirement)
|
|
165
167
|
|
|
@@ -202,15 +204,14 @@ module Dependabot
|
|
|
202
204
|
return dependency_set unless lockfile
|
|
203
205
|
|
|
204
206
|
lockfile_content = parsed_file(T.must(lockfile))
|
|
205
|
-
return dependency_set unless lockfile_content.is_a?(Hash)
|
|
206
207
|
|
|
207
|
-
lockfile_content.fetch("package", []).each do |package_details|
|
|
208
|
-
next unless package_details["source"]
|
|
208
|
+
T.cast(lockfile_content.fetch("package", []), T::Array[T::Hash[String, T.anything]]).each do |package_details|
|
|
209
|
+
next unless T.cast(package_details["source"], T.nilable(String))
|
|
209
210
|
|
|
210
211
|
# TODO: This isn't quite right, as it will only give us one
|
|
211
212
|
# version of each dependency (when in fact there are many)
|
|
212
213
|
dependency_set << Dependency.new(
|
|
213
|
-
name: package_details["name"],
|
|
214
|
+
name: T.cast(package_details["name"], String),
|
|
214
215
|
version: version_from_lockfile_details(package_details),
|
|
215
216
|
package_manager: "cargo",
|
|
216
217
|
requirements: []
|
|
@@ -224,10 +225,9 @@ module Dependabot
|
|
|
224
225
|
def patched_dependencies
|
|
225
226
|
root_manifest = manifest_files.find { |f| f.name == "Cargo.toml" }
|
|
226
227
|
parsed_content = parsed_file(T.must(root_manifest))
|
|
227
|
-
return [] unless parsed_content.is_a?(Hash)
|
|
228
228
|
return [] unless parsed_content["patch"]
|
|
229
229
|
|
|
230
|
-
parsed_content["patch"].values.flat_map(
|
|
230
|
+
toml_table_or_empty(parsed_content["patch"]).values.flat_map { |patch| toml_table_or_empty(patch).keys }
|
|
231
231
|
end
|
|
232
232
|
|
|
233
233
|
sig { params(declaration: T.any(String, T::Hash[String, String])).returns(T.nilable(String)) }
|
|
@@ -332,10 +332,18 @@ module Dependabot
|
|
|
332
332
|
|
|
333
333
|
sig { params(key_name: String).returns(T.nilable(String)) }
|
|
334
334
|
def cargo_config_from_file(key_name)
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
# Cargo merges `.cargo/config.toml` hierarchically, with nearer configs
|
|
336
|
+
# taking precedence over ancestors. Search the package-directory config
|
|
337
|
+
# first, then each ancestor config, returning the first match.
|
|
338
|
+
cargo_config_files.each do |config_file|
|
|
339
|
+
value = key_name.split(".").reduce(parsed_file(config_file)) do |current, key|
|
|
340
|
+
toml_table_or_empty(current)[key]
|
|
341
|
+
end
|
|
342
|
+
value = T.cast(value, T.nilable(String))
|
|
343
|
+
return value if value
|
|
344
|
+
end
|
|
337
345
|
|
|
338
|
-
|
|
346
|
+
nil
|
|
339
347
|
end
|
|
340
348
|
|
|
341
349
|
sig { params(name: String, declaration: T.any(String, T::Hash[String, String])).returns(T.nilable(String)) }
|
|
@@ -343,29 +351,28 @@ module Dependabot
|
|
|
343
351
|
return unless lockfile
|
|
344
352
|
|
|
345
353
|
lockfile_content = parsed_file(T.must(lockfile))
|
|
346
|
-
return unless lockfile_content.is_a?(Hash)
|
|
347
354
|
|
|
348
355
|
candidate_packages =
|
|
349
|
-
lockfile_content
|
|
350
|
-
|
|
356
|
+
lockfile_packages(lockfile_content)
|
|
357
|
+
.select { |p| package_field(p, "name") == name }
|
|
351
358
|
|
|
352
359
|
if (req = requirement_from_declaration(declaration))
|
|
353
360
|
req = Cargo::Requirement.new(req)
|
|
354
361
|
|
|
355
362
|
candidate_packages =
|
|
356
363
|
candidate_packages
|
|
357
|
-
.select { |p| req.satisfied_by?(version_class.new(p
|
|
364
|
+
.select { |p| req.satisfied_by?(version_class.new(T.must(package_field(p, "version")))) }
|
|
358
365
|
end
|
|
359
366
|
|
|
360
367
|
candidate_packages =
|
|
361
368
|
candidate_packages
|
|
362
369
|
.select do |p|
|
|
363
|
-
git_req?(declaration) ^ !p
|
|
370
|
+
git_req?(declaration) ^ !package_field(p, "source")&.start_with?("git+")
|
|
364
371
|
end
|
|
365
372
|
|
|
366
373
|
package =
|
|
367
374
|
candidate_packages
|
|
368
|
-
.max_by { |p| version_class.new(p
|
|
375
|
+
.max_by { |p| version_class.new(T.must(package_field(p, "version"))) }
|
|
369
376
|
|
|
370
377
|
return unless package
|
|
371
378
|
|
|
@@ -387,11 +394,13 @@ module Dependabot
|
|
|
387
394
|
}
|
|
388
395
|
end
|
|
389
396
|
|
|
390
|
-
sig { params(package_details: T::Hash[String,
|
|
397
|
+
sig { params(package_details: T::Hash[String, T.anything]).returns(String) }
|
|
391
398
|
def version_from_lockfile_details(package_details)
|
|
392
|
-
|
|
399
|
+
source = T.cast(package_details["source"], T.nilable(String))
|
|
400
|
+
version = T.cast(package_details["version"], String)
|
|
401
|
+
return version unless source&.start_with?("git+")
|
|
393
402
|
|
|
394
|
-
T.must(
|
|
403
|
+
T.must(source.split("#").last)
|
|
395
404
|
end
|
|
396
405
|
|
|
397
406
|
sig { override.void }
|
|
@@ -399,14 +408,36 @@ module Dependabot
|
|
|
399
408
|
raise "No Cargo.toml!" unless get_original_file("Cargo.toml")
|
|
400
409
|
end
|
|
401
410
|
|
|
402
|
-
sig { params(file: DependencyFile).returns(T.
|
|
411
|
+
sig { params(file: DependencyFile).returns(T::Hash[String, T.anything]) }
|
|
403
412
|
def parsed_file(file)
|
|
404
|
-
@parsed_file ||= T.let({}, T.nilable(T::Hash[String, T.
|
|
413
|
+
@parsed_file ||= T.let({}, T.nilable(T::Hash[String, T::Hash[String, T.anything]]))
|
|
405
414
|
@parsed_file[file.name] ||= TomlRB.parse(file.content)
|
|
406
415
|
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
|
|
407
416
|
raise Dependabot::DependencyFileNotParseable, file.path
|
|
408
417
|
end
|
|
409
418
|
|
|
419
|
+
sig { params(lockfile_content: T::Hash[String, T.anything]).returns(T::Array[T::Hash[String, T.anything]]) }
|
|
420
|
+
def lockfile_packages(lockfile_content)
|
|
421
|
+
T.cast(lockfile_content.fetch("package", []), T::Array[T::Hash[String, T.anything]])
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
sig { params(package_details: T::Hash[String, T.anything], field: String).returns(T.nilable(String)) }
|
|
425
|
+
def package_field(package_details, field)
|
|
426
|
+
T.cast(package_details[field], T.nilable(String))
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
sig { params(value: T.anything).returns(T::Hash[String, T.anything]) }
|
|
430
|
+
def toml_table_or_empty(value)
|
|
431
|
+
(obj = T.cast(value, T.nilable(Object))).is_a?(Hash) ? obj : {}
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
sig { params(value: T.anything).returns(T.any(String, T::Hash[String, String])) }
|
|
435
|
+
def dependency_declaration(value)
|
|
436
|
+
return T.cast(value, String) if T.cast(value, T.nilable(Object)).is_a?(String)
|
|
437
|
+
|
|
438
|
+
T.cast(value, T::Hash[String, String])
|
|
439
|
+
end
|
|
440
|
+
|
|
410
441
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
411
442
|
def manifest_files
|
|
412
443
|
@manifest_files ||= T.let(
|
|
@@ -422,9 +453,18 @@ module Dependabot
|
|
|
422
453
|
@lockfile ||= T.let(get_original_file("Cargo.lock"), T.nilable(Dependabot::DependencyFile))
|
|
423
454
|
end
|
|
424
455
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
456
|
+
# All `.cargo/config.toml` files in the fetched set, ordered nearest-first
|
|
457
|
+
# (package directory config, then successive ancestors). Ancestor configs
|
|
458
|
+
# carry relative `../` names, so we order by `../` depth to reflect Cargo's
|
|
459
|
+
# nearest-wins precedence.
|
|
460
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
461
|
+
def cargo_config_files
|
|
462
|
+
@cargo_config_files ||= T.let(
|
|
463
|
+
dependency_files
|
|
464
|
+
.select { |f| f.name.end_with?(".cargo/config.toml") }
|
|
465
|
+
.sort_by { |f| f.name.scan("../").count },
|
|
466
|
+
T.nilable(T::Array[Dependabot::DependencyFile])
|
|
467
|
+
)
|
|
428
468
|
end
|
|
429
469
|
|
|
430
470
|
sig { returns(T.class_of(Dependabot::Version)) }
|