class Dependabot::Cargo::FileFetcher

Public Class Methods

required_files_in?(filenames) click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 15
def self.required_files_in?(filenames)
  filenames.include?("Cargo.toml")
end
required_files_message() click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 19
def self.required_files_message
  "Repo must contain a Cargo.toml."
end

Private Instance Methods

cargo_lock() click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 283
def cargo_lock
  @cargo_lock ||= fetch_file_if_present("Cargo.lock")
end
cargo_toml() click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 279
def cargo_toml
  @cargo_toml ||= fetch_file_from_host("Cargo.toml")
end
expand_workspaces(path) click to toggle source

rubocop:enable Metrics/AbcSize rubocop:enable Metrics/PerceivedComplexity rubocop:enable Metrics/CyclomaticComplexity

# File lib/dependabot/cargo/file_fetcher.rb, line 262
def expand_workspaces(path)
  path = Pathname.new(path).cleanpath.to_path
  dir = directory.gsub(%r{(^/|/$)}, "")
  unglobbed_path = path.split("*").first.gsub(%r{(?<=/)[^/]*$}, "")

  repo_contents(dir: unglobbed_path, raise_errors: false).
    select { |file| file.type == "dir" }.
    map { |f| f.path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "") }.
    select { |filename| File.fnmatch?(path, filename) }
end
fetch_files() click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 25
def fetch_files
  fetched_files = []
  fetched_files << cargo_toml
  fetched_files << cargo_lock if cargo_lock
  fetched_files << rust_toolchain if rust_toolchain
  fetched_files += fetch_path_dependency_and_workspace_files
  fetched_files.uniq
end
fetch_path_dependency_and_workspace_files(files = nil) click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 34
def fetch_path_dependency_and_workspace_files(files = nil)
  fetched_files = files || [cargo_toml]

  fetched_files += path_dependency_files(fetched_files)
  fetched_files += fetched_files.flat_map { |f| workspace_files(f) }

  updated_files = fetched_files.reject(&:support_file?).uniq
  updated_files +=
    fetched_files.uniq.
    reject { |f| updated_files.map(&:name).include?(f.name) }

  return updated_files if updated_files == files

  fetch_path_dependency_and_workspace_files(updated_files)
end
fetch_path_dependency_files(file:, previously_fetched_files:) click to toggle source

rubocop:disable Metrics/PerceivedComplexity

# File lib/dependabot/cargo/file_fetcher.rb, line 102
def fetch_path_dependency_files(file:, previously_fetched_files:)
  current_dir = file.name.rpartition("/").first
  current_dir = nil if current_dir == ""
  unfetchable_required_path_deps = []

  path_dependency_files ||=
    path_dependency_paths_from_file(file).flat_map do |path|
      path = File.join(current_dir, path) unless current_dir.nil?
      path = Pathname.new(path).cleanpath.to_path

      next if previously_fetched_files.map(&:name).include?(path)
      next if file.name == path

      fetched_file = fetch_file_from_host(path, fetch_submodules: true).
                     tap { |f| f.support_file = true }
      previously_fetched_files << fetched_file
      grandchild_requirement_files =
        fetch_path_dependency_files(
          file: fetched_file,
          previously_fetched_files: previously_fetched_files
        )
      [fetched_file, *grandchild_requirement_files]
    rescue Dependabot::DependencyFileNotFound
      next unless required_path?(file, path)

      unfetchable_required_path_deps << path
    end.compact

  return path_dependency_files if unfetchable_required_path_deps.none?

  raise Dependabot::PathDependenciesNotReachable,
        unfetchable_required_path_deps
end
fetch_workspace_files(file:, previously_fetched_files:) click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 76
def fetch_workspace_files(file:, previously_fetched_files:)
  current_dir = file.name.rpartition("/").first
  current_dir = nil if current_dir == ""

  files = workspace_dependency_paths_from_file(file).flat_map do |path|
    path = File.join(current_dir, path) unless current_dir.nil?
    path = Pathname.new(path).cleanpath.to_path

    next if previously_fetched_files.map(&:name).include?(path)
    next if file.name == path

    fetched_file = fetch_file_from_host(path)
    previously_fetched_files << fetched_file
    grandchild_requirement_files =
      fetch_workspace_files(
        file: fetched_file,
        previously_fetched_files: previously_fetched_files
      )
    [fetched_file, *grandchild_requirement_files]
  end.compact

  files.each { |f| f.support_file = file != cargo_toml }
  files
end
parsed_file(file) click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 273
def parsed_file(file)
  TomlRB.parse(file.content)
rescue TomlRB::ParseError, TomlRB::ValueOverwriteError
  raise Dependabot::DependencyFileNotParseable, file.path
end
path_dependency_files(fetched_files) click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 59
def path_dependency_files(fetched_files)
  @path_dependency_files ||= {}
  fetched_path_dependency_files = []
  fetched_files.each do |file|
    @path_dependency_files[file.name] ||=
      fetch_path_dependency_files(
        file: file,
        previously_fetched_files: fetched_files +
                                  fetched_path_dependency_files
      )

    fetched_path_dependency_files += @path_dependency_files[file.name]
  end

  fetched_path_dependency_files
end
path_dependency_paths_from_file(file) click to toggle source

rubocop:enable Metrics/PerceivedComplexity

# File lib/dependabot/cargo/file_fetcher.rb, line 138
def path_dependency_paths_from_file(file)
  paths = []

  # Paths specified in dependency declaration
  Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
    parsed_file(file).fetch(type, {}).each do |_, details|
      next unless details.is_a?(Hash)
      next unless details["path"]

      paths << File.join(details["path"], "Cargo.toml")
    end
  end

  # Paths specified for target-specific dependencies
  parsed_file(file).fetch("target", {}).each do |_, t_details|
    Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
      t_details.fetch(type, {}).each do |_, details|
        next unless details.is_a?(Hash)
        next unless details["path"]

        paths << File.join(details["path"], "Cargo.toml")
      end
    end
  end

  paths += replacement_path_dependency_paths_from_file(file)
  paths
end
replacement_path_dependency_paths_from_file(file) click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 167
def replacement_path_dependency_paths_from_file(file)
  paths = []

  # Paths specified as replacements
  parsed_file(file).fetch("replace", {}).each do |_, details|
    next unless details.is_a?(Hash)
    next unless details["path"]

    paths << File.join(details["path"], "Cargo.toml")
  end

  # Paths specified as patches
  parsed_file(file).fetch("patch", {}).each do |_, details|
    next unless details.is_a?(Hash)

    details.each do |_, dep_details|
      next unless dep_details.is_a?(Hash)
      next unless dep_details["path"]

      paths << File.join(dep_details["path"], "Cargo.toml")
    end
  end

  paths
end
required_path?(file, path) click to toggle source

Check whether a path is required or not. It will not be required if an alternative source (i.e., a git source) is also specified rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/PerceivedComplexity rubocop:disable Metrics/AbcSize

# File lib/dependabot/cargo/file_fetcher.rb, line 222
def required_path?(file, path)
  # Paths specified in dependency declaration
  Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
    parsed_file(file).fetch(type, {}).each do |_, details|
      next unless details.is_a?(Hash)
      next unless details["path"]
      next unless path == File.join(details["path"], "Cargo.toml")

      return true if details["git"].nil?
    end
  end

  # Paths specified for target-specific dependencies
  parsed_file(file).fetch("target", {}).each do |_, t_details|
    Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
      t_details.fetch(type, {}).each do |_, details|
        next unless details.is_a?(Hash)
        next unless details["path"]
        next unless path == File.join(details["path"], "Cargo.toml")

        return true if details["git"].nil?
      end
    end
  end

  # Paths specified as replacements
  parsed_file(file).fetch("replace", {}).each do |_, details|
    next unless details.is_a?(Hash)
    next unless details["path"]
    next unless path == File.join(details["path"], "Cargo.toml")

    return true if details["git"].nil?
  end

  false
end
rust_toolchain() click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 287
def rust_toolchain
  @rust_toolchain ||= fetch_file_if_present("rust-toolchain")&.
                      tap { |f| f.support_file = true }
end
workspace_dependency_paths_from_file(file) click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 193
def workspace_dependency_paths_from_file(file)
  if parsed_file(file)["workspace"] &&
     !parsed_file(file)["workspace"].key?("members")
    return path_dependency_paths_from_file(file)
  end

  workspace_paths = parsed_file(file).dig("workspace", "members")
  return [] unless workspace_paths&.any?

  # Expand any workspace paths that specify a `*`
  workspace_paths = workspace_paths.flat_map do |path|
    path.include?("*") ? expand_workspaces(path) : [path]
  end

  # Excluded paths, to be subtracted for the workspaces array
  excluded_paths =
    (parsed_file(file).dig("workspace", "excluded_paths") || []) +
    (parsed_file(file).dig("workspace", "exclude") || [])

  (workspace_paths - excluded_paths).map do |path|
    File.join(path, "Cargo.toml")
  end
end
workspace_files(cargo_toml) click to toggle source
# File lib/dependabot/cargo/file_fetcher.rb, line 50
def workspace_files(cargo_toml)
  @workspace_files ||= {}
  @workspace_files[cargo_toml.name] ||=
    fetch_workspace_files(
      file: cargo_toml,
      previously_fetched_files: []
    )
end