class Dependabot::Cargo::UpdateChecker::FilePreparer

This class takes a set of dependency files and sanitizes them for use in UpdateCheckers::Rust::Cargo.

Attributes

dependency[R]
dependency_files[R]
latest_allowable_version[R]
replacement_git_pin[R]

Public Class Methods

new(dependency_files:, dependency:, unlock_requirement: true, replacement_git_pin: nil, latest_allowable_version: nil) click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 14
def initialize(dependency_files:, dependency:,
               unlock_requirement: true,
               replacement_git_pin: nil,
               latest_allowable_version: nil)
  @dependency_files         = dependency_files
  @dependency               = dependency
  @unlock_requirement       = unlock_requirement
  @replacement_git_pin      = replacement_git_pin
  @latest_allowable_version = latest_allowable_version
end

Public Instance Methods

prepared_dependency_files() click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 25
def prepared_dependency_files
  files = []
  files += manifest_files.map do |file|
    DependencyFile.new(
      name: file.name,
      content: manifest_content_for_update_check(file),
      directory: file.directory
    )
  end
  files << lockfile if lockfile
  files << toolchain if toolchain
  files
end

Private Instance Methods

dependency_names_for_type(parsed_manifest, type) click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 232
def dependency_names_for_type(parsed_manifest, type)
  names = []
  parsed_manifest.fetch(type, {}).each do |nm, req|
    next unless dependency.name == name_from_declaration(nm, req)

    names << nm
  end
  names
end
dependency_names_for_type_and_target(parsed_manifest, type, target) click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 242
def dependency_names_for_type_and_target(parsed_manifest, type, target)
  names = []
  (parsed_manifest.dig("target", target, type) || {}).each do |nm, req|
    next unless dependency.name == name_from_declaration(nm, req)

    names << nm
  end
  names
end
git_dependency?() click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 277
def git_dependency?
  GitCommitChecker.
    new(dependency: dependency, credentials: []).
    git_dependency?
end
git_dependency_version() click to toggle source

rubocop:enable Metrics/PerceivedComplexity

# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 222
def git_dependency_version
  return unless lockfile

  TomlRB.parse(lockfile.content).
    fetch("package", []).
    select { |p| p["name"] == dependency.name }.
    find { |p| p["source"].end_with?(dependency.version) }.
    fetch("version")
end
lockfile() click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 268
def lockfile
  @lockfile ||= dependency_files.find { |f| f.name == "Cargo.lock" }
end
lower_bound_version() click to toggle source

rubocop:disable Metrics/PerceivedComplexity

# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 201
def lower_bound_version
  @lower_bound_version ||=
    if git_dependency? && git_dependency_version
      git_dependency_version
    elsif !git_dependency? && dependency.version
      dependency.version
    else
      version_from_requirement =
        dependency.requirements.map { |r| r.fetch(:requirement) }.
        compact.
        flat_map { |req_str| Cargo::Requirement.new(req_str) }.
        flat_map(&:requirements).
        reject { |req_array| req_array.first.start_with?("<") }.
        map(&:last).
        max&.to_s

      version_from_requirement || 0
    end
end
manifest_content_for_update_check(file) click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 52
def manifest_content_for_update_check(file)
  content = file.content

  unless file.support_file?
    content = replace_version_constraint(content, file.name)
    content = replace_git_pin(content) if replace_git_pin?
  end

  content = replace_ssh_urls(content)

  content
end
manifest_files() click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 259
def manifest_files
  @manifest_files ||=
    dependency_files.select { |f| f.name.end_with?("Cargo.toml") }

  raise "No Cargo.toml!" if @manifest_files.none?

  @manifest_files
end
name_from_declaration(name, declaration) click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 252
def name_from_declaration(name, declaration)
  return name if declaration.is_a?(String)
  raise "Unexpected dependency declaration: #{declaration}" unless declaration.is_a?(Hash)

  declaration.fetch("package", name)
end
replace_git_pin(content) click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 114
def replace_git_pin(content)
  parsed_manifest = TomlRB.parse(content)

  Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
    dependency_names_for_type(parsed_manifest, type).each do |name|
      req = parsed_manifest.dig(type, name)
      next unless req.is_a?(Hash)
      next unless [req["tag"], req["rev"]].compact.uniq.count == 1

      parsed_manifest[type][name]["tag"] = replacement_git_pin if req["tag"]

      parsed_manifest[type][name]["rev"] = replacement_git_pin if req["rev"]
    end
  end

  replace_git_pin_on_target_specific_deps!(parsed_manifest)

  TomlRB.dump(parsed_manifest)
end
replace_git_pin?() click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 48
def replace_git_pin?
  !replacement_git_pin.nil?
end
replace_git_pin_on_target_specific_deps!(parsed_manifest) click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 134
def replace_git_pin_on_target_specific_deps!(parsed_manifest)
  parsed_manifest.fetch("target", {}).each do |target, _|
    Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
      dependency_names = dependency_names_for_type_and_target(
        parsed_manifest,
        type,
        target
      )

      dependency_names.each do |name|
        req = parsed_manifest.dig("target", target, type, name)
        next unless req.is_a?(Hash)
        next unless [req["tag"], req["rev"]].compact.uniq.count == 1

        if req["tag"]
          parsed_manifest["target"][target][type][name]["tag"] =
            replacement_git_pin
        end

        if req["rev"]
          parsed_manifest["target"][target][type][name]["rev"] =
            replacement_git_pin
        end
      end
    end
  end
end
replace_req_on_target_specific_deps!(parsed_manifest, filename) click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 89
def replace_req_on_target_specific_deps!(parsed_manifest, filename)
  parsed_manifest.fetch("target", {}).each do |target, _|
    Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
      dependency_names = dependency_names_for_type_and_target(
        parsed_manifest,
        type,
        target
      )

      dependency_names.each do |name|
        req = parsed_manifest.dig("target", target, type, name)

        updated_req = temporary_requirement_for_resolution(filename)

        if req.is_a?(Hash)
          parsed_manifest["target"][target][type][name]["version"] =
            updated_req
        else
          parsed_manifest["target"][target][type][name] = updated_req
        end
      end
    end
  end
end
replace_ssh_urls(content) click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 162
def replace_ssh_urls(content)
  parsed_manifest = TomlRB.parse(content)

  Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
    (parsed_manifest[type] || {}).each do |_, details|
      next unless details.is_a?(Hash)
      next unless details["git"]

      details["git"] = details["git"].
                       gsub(%r{ssh://git@(.*?)/}, 'https://\1/')
    end
  end

  TomlRB.dump(parsed_manifest)
end
replace_version_constraint(content, filename) click to toggle source

NOTE: We don't need to care about formatting in this method, since we're only using the manifest to find the latest resolvable version

# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 67
def replace_version_constraint(content, filename)
  parsed_manifest = TomlRB.parse(content)

  Cargo::FileParser::DEPENDENCY_TYPES.each do |type|
    dependency_names_for_type(parsed_manifest, type).each do |name|
      req = parsed_manifest.dig(type, name)

      updated_req = temporary_requirement_for_resolution(filename)

      if req.is_a?(Hash)
        parsed_manifest[type][name]["version"] = updated_req
      else
        parsed_manifest[type][name] = updated_req
      end
    end
  end

  replace_req_on_target_specific_deps!(parsed_manifest, filename)

  TomlRB.dump(parsed_manifest)
end
temporary_requirement_for_resolution(filename) click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 178
def temporary_requirement_for_resolution(filename)
  original_req = dependency.requirements.
                 find { |r| r.fetch(:file) == filename }&.
                 fetch(:requirement)

  lower_bound_req =
    if original_req && !unlock_requirement?
      original_req
    else
      ">= #{lower_bound_version}"
    end

  unless latest_allowable_version &&
         Cargo::Version.correct?(latest_allowable_version) &&
         Cargo::Version.new(latest_allowable_version) >=
         Cargo::Version.new(lower_bound_version)
    return lower_bound_req
  end

  lower_bound_req + ", <= #{latest_allowable_version}"
end
toolchain() click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 272
def toolchain
  @toolchain ||=
    dependency_files.find { |f| f.name == "rust-toolchain" }
end
unlock_requirement?() click to toggle source
# File lib/dependabot/cargo/update_checker/file_preparer.rb, line 44
def unlock_requirement?
  @unlock_requirement
end