1414# https://doc.rust-lang.org/cargo/reference/manifest.html#the-workspace-section
1515module Dependabot
1616 module Cargo
17- class FileFetcher < Dependabot ::FileFetchers ::Base
17+ class FileFetcher < Dependabot ::FileFetchers ::Base # rubocop:disable Metrics/ClassLength
1818 extend T ::Sig
1919 extend T ::Helpers
2020
@@ -50,13 +50,11 @@ def fetch_files
5050 fetched_files << T . must ( cargo_config ) if cargo_config
5151 fetched_files << T . must ( rust_toolchain ) if rust_toolchain
5252 fetched_files += fetch_path_dependency_and_workspace_files
53- # If the main Cargo.toml uses workspace dependencies, ensure we have the workspace root
5453 parsed_manifest = parsed_file ( cargo_toml )
5554 if uses_workspace_dependencies? ( parsed_manifest ) || workspace_member? ( parsed_manifest )
5655 workspace_root = find_workspace_root ( cargo_toml )
5756 fetched_files << workspace_root if workspace_root && !fetched_files . include? ( workspace_root )
5857 end
59- # Filter excluded files from final collection
6058 fetched_files . reject do |file |
6159 Dependabot ::FileFiltering . should_exclude_path? ( file . name , "file from final collection" , @exclude_paths )
6260 end . uniq
@@ -70,20 +68,17 @@ def fetch_files
7068 def fetch_path_dependency_and_workspace_files ( files = nil )
7169 fetched_files = files || [ cargo_toml ]
7270 fetched_files += path_dependency_files ( fetched_files )
73- fetched_files += fetched_files . flat_map { |f | workspace_files ( f ) }
71+ @workspace_files ||= T . let ( { } , T . nilable ( T ::Hash [ String , T ::Array [ Dependabot ::DependencyFile ] ] ) )
72+ fetched_files += fetched_files . flat_map do |f |
73+ @workspace_files [ f . name ] ||= fetch_workspace_files ( file : f , previously_fetched_files : [ ] )
74+ end
7475 updated_files = fetched_files . reject ( &:support_file? ) . uniq
7576 updated_files += fetched_files . uniq . reject { |f | updated_files . map ( &:name ) . include? ( f . name ) }
7677 return updated_files if updated_files == files
7778
7879 fetch_path_dependency_and_workspace_files ( updated_files )
7980 end
8081
81- sig { params ( cargo_toml : Dependabot ::DependencyFile ) . returns ( T ::Array [ Dependabot ::DependencyFile ] ) }
82- def workspace_files ( cargo_toml )
83- @workspace_files ||= T . let ( { } , T . nilable ( T ::Hash [ String , T ::Array [ Dependabot ::DependencyFile ] ] ) )
84- @workspace_files [ cargo_toml . name ] ||= fetch_workspace_files ( file : cargo_toml , previously_fetched_files : [ ] )
85- end
86-
8782 sig { params ( fetched_files : T ::Array [ Dependabot ::DependencyFile ] ) . returns ( T ::Array [ Dependabot ::DependencyFile ] ) }
8883 def path_dependency_files ( fetched_files )
8984 @path_dependency_files ||= T . let ( { } , T . nilable ( T ::Hash [ String , T ::Array [ Dependabot ::DependencyFile ] ] ) )
@@ -442,15 +437,17 @@ def cargo_config
442437 fetch_support_file ( ".cargo/config" ) &.tap { |f | f . name = ".cargo/config.toml" } ,
443438 T . nilable ( Dependabot ::DependencyFile )
444439 )
440+ @cargo_config ||= T . let (
441+ fetch_cargo_config_from_parent_dirs ,
442+ T . nilable ( Dependabot ::DependencyFile )
443+ )
445444 end
446445
447446 sig { returns ( T . nilable ( Dependabot ::DependencyFile ) ) }
448447 def rust_toolchain
449448 return @rust_toolchain if defined? ( @rust_toolchain )
450449
451450 @rust_toolchain = fetch_support_file ( "rust-toolchain" )
452- # Per https://rust-lang.github.io/rustup/overrides.html the file can have a `.toml` extension,
453- # but the non-extension version is preferred. Renaming here to simplify finding it later in the code.
454451 @rust_toolchain ||= T . let (
455452 fetch_support_file ( "rust-toolchain.toml" ) &.tap { |f | f . name = "rust-toolchain" } ,
456453 T . nilable ( Dependabot ::DependencyFile )
@@ -459,9 +456,7 @@ def rust_toolchain
459456
460457 sig { override . params ( filename : T . any ( Pathname , String ) ) . returns ( Dependabot ::DependencyFile ) }
461458 def load_cloned_file_if_present ( filename )
462- file = super
463- file . name = Pathname . new ( file . name ) . cleanpath . to_s . gsub ( %r{^/+} , "" )
464- file
459+ super . tap { |f | f . name = Pathname . new ( f . name ) . cleanpath . to_s . gsub ( %r{^/+} , "" ) }
465460 end
466461
467462 sig do
@@ -472,9 +467,45 @@ def load_cloned_file_if_present(filename)
472467 ) . returns ( Dependabot ::DependencyFile )
473468 end
474469 def fetch_file_from_host ( filename , type : "file" , fetch_submodules : false )
475- file = super
476- file . name = Pathname . new ( file . name ) . cleanpath . to_s . gsub ( %r{^/+} , "" )
477- file
470+ super . tap { |f | f . name = Pathname . new ( f . name ) . cleanpath . to_s . gsub ( %r{^/+} , "" ) }
471+ end
472+
473+ sig { returns ( T . nilable ( Dependabot ::DependencyFile ) ) }
474+ def fetch_cargo_config_from_parent_dirs
475+ return nil if directory . empty?
476+
477+ # Count directory depth to determine how many levels to search up
478+ depth = directory . split ( "/" ) . count { |s | !s . empty? }
479+ return nil if depth . zero?
480+
481+ # Try each parent directory level
482+ depth . times do |i |
483+ parent_path = ( [ ".." ] * ( i + 1 ) ) . join ( "/" )
484+ config = try_fetch_config_at_path ( parent_path )
485+ return config if config
486+ end
487+
488+ nil
489+ end
490+
491+ sig { params ( parent_path : String ) . returns ( T . nilable ( Dependabot ::DependencyFile ) ) }
492+ def try_fetch_config_at_path ( parent_path )
493+ [ ".cargo/config.toml" , ".cargo/config" ] . each do |config_name |
494+ full_path = File . join ( parent_path , config_name )
495+ Dependabot . logger . debug ( "Attempting to fetch config from: #{ full_path } " )
496+ config = fetch_file_from_host (
497+ full_path ,
498+ fetch_submodules : false
499+ )
500+ Dependabot . logger . debug ( "Successfully fetched config from: #{ full_path } " )
501+ config . support_file = true
502+ config . name = ".cargo/config.toml"
503+ return config
504+ rescue Dependabot ::DependencyFileNotFound
505+ Dependabot . logger . debug ( "No config found at: #{ full_path } " )
506+ next
507+ end
508+ nil
478509 end
479510 end
480511 end
0 commit comments