class Dependabot::Cargo::FileUpdater::LockfileUpdater
Constants
- LOCKFILE_CHECKSUM_REGEX
- LOCKFILE_ENTRY_REGEX
Attributes
credentials[R]
dependencies[R]
dependency_files[R]
Public Class Methods
new(dependencies:, dependency_files:, credentials:)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 21 def initialize(dependencies:, dependency_files:, credentials:) @dependencies = dependencies @dependency_files = dependency_files @credentials = credentials end
Public Instance Methods
updated_lockfile_content()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 27 def updated_lockfile_content base_directory = dependency_files.first.directory SharedHelpers.in_a_temporary_directory(base_directory) do write_temporary_dependency_files SharedHelpers.with_git_configured(credentials: credentials) do # Shell out to Cargo, which handles everything for us, and does # so without doing an install (so it's fast). run_shell_command("cargo update -p #{dependency_spec}") end updated_lockfile = File.read("Cargo.lock") updated_lockfile = post_process_lockfile(updated_lockfile) next updated_lockfile if updated_lockfile.include?(desired_lockfile_content) raise "Failed to update #{dependency.name}!" end rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e retry if better_specification_needed?(e) handle_cargo_error(e) end
Private Instance Methods
better_specification_needed?(error)
click to toggle source
rubocop:disable Metrics/PerceivedComplexity rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/AbcSize
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 70 def better_specification_needed?(error) return false if @custom_specification return false unless error.message.match?(/specification .* is ambigu/) spec_options = error.message.gsub(/.*following:\n/m, ""). lines.map(&:strip) ver = if git_dependency? && git_previous_version git_previous_version else dependency.version end if spec_options.count { |s| s.end_with?(ver) } == 1 @custom_specification = spec_options.find { |s| s.end_with?(ver) } return true elsif spec_options.count { |s| s.end_with?(ver) } > 1 spec_options.select! { |s| s.end_with?(ver) } end if git_dependency? && git_source_url && spec_options.count { |s| s.include?(git_source_url) } >= 1 spec_options.select! { |s| s.include?(git_source_url) } end @custom_specification = spec_options.first true end
dependency()
click to toggle source
Currently, there will only be a single updated dependency
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 55 def dependency dependencies.first end
dependency_spec()
click to toggle source
rubocop:enable Metrics/AbcSize rubocop:enable Metrics/CyclomaticComplexity rubocop:enable Metrics/PerceivedComplexity
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 102 def dependency_spec return @custom_specification if @custom_specification spec = dependency.name if git_dependency? spec += ":#{git_previous_version}" if git_previous_version elsif dependency.previous_version spec += ":#{dependency.previous_version}" spec = "https://github.com/rust-lang/crates.io-index#" + spec end spec end
desired_lockfile_content()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 131 def desired_lockfile_content return dependency.version if git_dependency? %(name = "#{dependency.name}"\nversion = "#{dependency.version}") end
dummy_app_content()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 336 def dummy_app_content %{fn main() {\nprintln!("Hello, world!");\n}} end
git_dependency?()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 340 def git_dependency? GitCommitChecker.new( dependency: dependency, credentials: credentials ).git_dependency? end
git_previous_version()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 117 def git_previous_version TomlRB.parse(lockfile.content). fetch("package", []). select { |p| p["name"] == dependency.name }. find { |p| p["source"].end_with?(dependency.previous_version) }. fetch("version") end
git_source_url()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 125 def git_source_url dependency.previous_requirements. find { |r| r.dig(:source, :type) == "git" }&. dig(:source, :url) end
git_ssh_requirements_to_swap()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 284 def git_ssh_requirements_to_swap return @git_ssh_requirements_to_swap if @git_ssh_requirements_to_swap @git_ssh_requirements_to_swap = {} [*manifest_files, *path_dependency_files].each do |manifest| parsed_manifest = TomlRB.parse(manifest.content) Cargo::FileParser::DEPENDENCY_TYPES.each do |type| (parsed_manifest[type] || {}).each do |_, details| next unless details.is_a?(Hash) next unless details["git"]&.match?(%r{ssh://git@(.*?)/}) @git_ssh_requirements_to_swap[details["git"]] = details["git"].gsub(%r{ssh://git@(.*?)/}, 'https://\1/') end end end @git_ssh_requirements_to_swap end
handle_cargo_error(error)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 59 def handle_cargo_error(error) raise unless error.message.include?("failed to select a version") || error.message.include?("no matching version") raise if error.message.include?("`#{dependency.name} ") raise Dependabot::DependencyFileNotResolvable, error.message end
lockfile()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 361 def lockfile @lockfile ||= dependency_files.find { |f| f.name == "Cargo.lock" } end
manifest_files()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 347 def manifest_files @manifest_files ||= dependency_files. select { |f| f.name.end_with?("Cargo.toml") }. reject(&:support_file?) end
path_dependency_files()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 354 def path_dependency_files @path_dependency_files ||= dependency_files. select { |f| f.name.end_with?("Cargo.toml") }. select(&:support_file?) end
pin_target_specific_dependencies!(parsed_manifest)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 237 def pin_target_specific_dependencies!(parsed_manifest) parsed_manifest.fetch("target", {}).each do |target, t_details| Cargo::FileParser::DEPENDENCY_TYPES.each do |type| t_details.fetch(type, {}).each do |name, requirement| next unless name == dependency.name updated_req = "=#{dependency.version}" if requirement.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
pin_version(content)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 217 def pin_version(content) parsed_manifest = TomlRB.parse(content) Cargo::FileParser::DEPENDENCY_TYPES.each do |type| next unless (req = parsed_manifest.dig(type, dependency.name)) updated_req = "=#{dependency.version}" if req.is_a?(Hash) parsed_manifest[type][dependency.name]["version"] = updated_req else parsed_manifest[type][dependency.name] = updated_req end end pin_target_specific_dependencies!(parsed_manifest) TomlRB.dump(parsed_manifest) end
post_process_lockfile(content)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 275 def post_process_lockfile(content) git_ssh_requirements_to_swap.each do |ssh_url, https_url| content = content.gsub(https_url, ssh_url) content = remove_duplicate_lockfile_entries(content) end content end
prepared_manifest_content(file)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 195 def prepared_manifest_content(file) content = updated_manifest_content(file) content = pin_version(content) unless git_dependency? content = replace_ssh_urls(content) content = remove_binary_specifications(content) content = remove_default_run_specification(content) content end
prepared_path_dependency_content(file)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 204 def prepared_path_dependency_content(file) content = file.content.dup content = replace_ssh_urls(content) content end
remove_binary_specifications(content)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 263 def remove_binary_specifications(content) parsed_manifest = TomlRB.parse(content) parsed_manifest.delete("bin") TomlRB.dump(parsed_manifest) end
remove_default_run_specification(content)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 269 def remove_default_run_specification(content) parsed_manifest = TomlRB.parse(content) parsed_manifest["package"].delete("default-run") if parsed_manifest.dig("package", "default-run") TomlRB.dump(parsed_manifest) end
remove_duplicate_lockfile_entries(lockfile_content)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 306 def remove_duplicate_lockfile_entries(lockfile_content) # Loop through the lockfile entries looking for duplicates. Replace # any that are found lockfile_entries = [] lockfile_content.scan(LOCKFILE_ENTRY_REGEX) do lockfile_entries << Regexp.last_match.to_s end lockfile_entries. select { |e| lockfile_entries.count(e) > 1 }.uniq. each do |entry| (lockfile_entries.count(entry) - 1). times { lockfile_content = lockfile_content.sub(entry, "") } end # Loop through the lockfile checksums looking for duplicates. Replace # any that are found lockfile_checksums = [] lockfile_content.scan(LOCKFILE_CHECKSUM_REGEX) do lockfile_checksums << Regexp.last_match.to_s end lockfile_checksums. select { |e| lockfile_checksums.count(e) > 1 }.uniq. each do |cs| (lockfile_checksums.count(cs) - 1). times { lockfile_content = lockfile_content.sub("\n#{cs}", "") } end lockfile_content end
replace_ssh_urls(content)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 256 def replace_ssh_urls(content) git_ssh_requirements_to_swap.each do |ssh_url, https_url| content = content.gsub(ssh_url, https_url) end content end
run_shell_command(command)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 137 def run_shell_command(command) start = Time.now command = SharedHelpers.escape_command(command) stdout, process = Open3.capture2e(command) time_taken = Time.now - start # Raise an error with the output from the shell session if Cargo # returns a non-zero status return if process.success? raise SharedHelpers::HelperSubprocessFailed.new( message: stdout, error_context: { command: command, time_taken: time_taken, process_exit_value: process.to_s } ) end
toolchain()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 365 def toolchain @toolchain ||= dependency_files.find { |f| f.name == "rust-toolchain" } end
updated_manifest_content(file)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 210 def updated_manifest_content(file) ManifestUpdater.new( dependencies: dependencies, manifest: file ).updated_manifest_content end
virtual_manifest?(file)
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 370 def virtual_manifest?(file) !file.content.include?("[package]") end
write_temporary_dependency_files()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 157 def write_temporary_dependency_files write_temporary_manifest_files write_temporary_path_dependency_files File.write(lockfile.name, lockfile.content) File.write(toolchain.name, toolchain.content) if toolchain end
write_temporary_manifest_files()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 165 def write_temporary_manifest_files manifest_files.each do |file| path = file.name dir = Pathname.new(path).dirname FileUtils.mkdir_p(Pathname.new(path).dirname) File.write(file.name, prepared_manifest_content(file)) next if virtual_manifest?(file) File.write(File.join(dir, "build.rs"), dummy_app_content) FileUtils.mkdir_p(File.join(dir, "src")) File.write(File.join(dir, "src/lib.rs"), dummy_app_content) File.write(File.join(dir, "src/main.rs"), dummy_app_content) end end
write_temporary_path_dependency_files()
click to toggle source
# File lib/dependabot/cargo/file_updater/lockfile_updater.rb, line 182 def write_temporary_path_dependency_files path_dependency_files.each do |file| path = file.name dir = Pathname.new(path).dirname FileUtils.mkdir_p(Pathname.new(path).dirname) File.write(file.name, prepared_path_dependency_content(file)) FileUtils.mkdir_p(File.join(dir, "src")) File.write(File.join(dir, "src/lib.rs"), dummy_app_content) File.write(File.join(dir, "src/main.rs"), dummy_app_content) end end