class Dependabot::Bundler::FileUpdater::LockfileUpdater

Constants

GIT_DEPENDENCIES_SECTION
GIT_DEPENDENCY_DETAILS
LOCKFILE_ENDING

Attributes

credentials[R]
dependencies[R]
dependency_files[R]
options[R]
repo_contents_path[R]

Public Class Methods

new(dependencies:, dependency_files:, repo_contents_path: nil, credentials:, options:) click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 35
def initialize(dependencies:, dependency_files:,
               repo_contents_path: nil, credentials:, options:)
  @dependencies = dependencies
  @dependency_files = dependency_files
  @repo_contents_path = repo_contents_path
  @credentials = credentials
  @options = options
end

Public Instance Methods

gemspec_sources() click to toggle source

Can't be a constant because some of these don't exist in bundler 1.15, which Heroku uses, which causes an exception on boot.

# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 28
def gemspec_sources
  [
    ::Bundler::Source::Path,
    ::Bundler::Source::Gemspec
  ]
end
updated_lockfile_content() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 44
def updated_lockfile_content
  @updated_lockfile_content ||=
    begin
      updated_content = build_updated_lockfile

      raise "Expected content to change!" if lockfile.content == updated_content

      updated_content
    end
end

Private Instance Methods

build_updated_lockfile() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 60
def build_updated_lockfile
  base_dir = dependency_files.first.directory
  lockfile_body =
    SharedHelpers.in_a_temporary_repo_directory(
      base_dir,
      repo_contents_path
    ) do |tmp_dir|
      write_temporary_dependency_files

      NativeHelpers.run_bundler_subprocess(
        bundler_version: bundler_version,
        function: "update_lockfile",
        args: {
          gemfile_name: gemfile.name,
          lockfile_name: lockfile.name,
          dir: tmp_dir,
          credentials: credentials,
          dependencies: dependencies.map(&:to_h)
        }
      )
    end
  post_process_lockfile(lockfile_body)
rescue SharedHelpers::HelperSubprocessFailed => e
  raise unless ruby_lock_error?(e)

  @dont_lock_ruby_version = true
  retry
end
bundler_version() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 300
def bundler_version
  @bundler_version ||= Helpers.bundler_version(lockfile)
end
evaled_gemfiles() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 283
def evaled_gemfiles
  @evaled_gemfiles ||=
    dependency_files.
    reject { |f| f.name.end_with?(".gemspec") }.
    reject { |f| f.name.end_with?(".specification") }.
    reject { |f| f.name.end_with?(".lock") }.
    reject { |f| f.name.end_with?(".ruby-version") }.
    reject { |f| f.name == "Gemfile" }.
    reject { |f| f.name == "gems.rb" }.
    reject { |f| f.name == "gems.locked" }.
    reject(&:support_file?)
end
gemfile() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 267
def gemfile
  @gemfile ||= dependency_files.find { |f| f.name == "Gemfile" } ||
               dependency_files.find { |f| f.name == "gems.rb" }
end
imported_ruby_files() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 154
def imported_ruby_files
  dependency_files.
    select { |f| f.name.end_with?(".rb") }.
    reject { |f| f.name == "gems.rb" }
end
lockfile() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 272
def lockfile
  @lockfile ||=
    dependency_files.find { |f| f.name == "Gemfile.lock" } ||
    dependency_files.find { |f| f.name == "gems.locked" }
end
path_gemspecs() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 149
def path_gemspecs
  all = dependency_files.select { |f| f.name.end_with?(".gemspec") }
  all - top_level_gemspecs
end
post_process_lockfile(lockfile_body) click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 170
def post_process_lockfile(lockfile_body)
  lockfile_body = reorder_git_dependencies(lockfile_body)
  replace_lockfile_ending(lockfile_body)
end
prepared_gemfile_content(file) click to toggle source

rubocop:enable Metrics/PerceivedComplexity

# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 238
def prepared_gemfile_content(file)
  content =
    GemfileUpdater.new(
      dependencies: dependencies,
      gemfile: file
    ).updated_gemfile_content
  return content if @dont_lock_ruby_version

  top_level_gemspecs.each do |gs|
    content = RubyRequirementSetter.new(gemspec: gs).rewrite(content)
  end

  content
end
reorder_git_dependencies(lockfile_body) click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 175
def reorder_git_dependencies(lockfile_body)
  new_section = lockfile_body.match(GIT_DEPENDENCIES_SECTION)&.to_s
  old_section = lockfile.content.match(GIT_DEPENDENCIES_SECTION)&.to_s

  return lockfile_body unless new_section && old_section

  new_deps = new_section.scan(GIT_DEPENDENCY_DETAILS)
  old_deps = old_section.scan(GIT_DEPENDENCY_DETAILS)

  return lockfile_body unless new_deps.count == old_deps.count

  reordered_new_section = new_deps.sort_by do |new_dep_details|
    remote = new_dep_details.match(/remote: (?<remote>.*\n)/)[:remote]
    i = old_deps.index { |details| details.include?(remote) }

    # If this dependency isn't in the old lockfile then we can't rely
    # on that (presumably outdated) lockfile to do reordering.
    # Instead, we just return the default-ordered content just
    # generated.
    return lockfile_body unless i

    i
  end.join

  lockfile_body.gsub(new_section, reordered_new_section)
end
replace_lockfile_ending(lockfile_body) click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 202
def replace_lockfile_ending(lockfile_body)
  # Re-add the old `BUNDLED WITH` version (and remove the RUBY VERSION
  # if it wasn't previously present in the lockfile)
  lockfile_body.gsub(
    LOCKFILE_ENDING,
    lockfile.content.match(LOCKFILE_ENDING)&.[](:ending) || "\n"
  )
end
replacement_version_for_gemspec(gemspec_content) click to toggle source

rubocop:disable Metrics/PerceivedComplexity

# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 220
def replacement_version_for_gemspec(gemspec_content)
  return "0.0.1" unless lockfile

  gemspec_specs =
    ::Bundler::LockfileParser.new(sanitized_lockfile_body).specs.
    select { |s| gemspec_sources.include?(s.source.class) }

  gem_name =
    GemspecDependencyNameFinder.new(gemspec_content: gemspec_content).
    dependency_name

  return gemspec_specs.first&.version || "0.0.1" unless gem_name

  spec = gemspec_specs.find { |s| s.name == gem_name }
  spec&.version || gemspec_specs.first&.version || "0.0.1"
end
ruby_lock_error?(error) click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 89
def ruby_lock_error?(error)
  return false unless error.error_class == "Bundler::VersionConflict"
  return false unless error.message.include?(" for gem \"ruby\0\"")
  return false if @dont_lock_ruby_version

  dependency_files.any? { |f| f.name.end_with?(".gemspec") }
end
ruby_version_file() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 166
def ruby_version_file
  dependency_files.find { |f| f.name == ".ruby-version" }
end
sanitized_gemspec_content(gemspec_content) click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 211
def sanitized_gemspec_content(gemspec_content)
  new_version = replacement_version_for_gemspec(gemspec_content)

  GemspecSanitizer.
    new(replacement_version: new_version).
    rewrite(gemspec_content)
end
sanitized_lockfile_body() click to toggle source

TODO: Stop sanitizing the lockfile once we have bundler 2 installed

# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 279
def sanitized_lockfile_body
  lockfile.content.gsub(LOCKFILE_ENDING, "")
end
specification_files() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 296
def specification_files
  dependency_files.select { |f| f.name.end_with?(".specification") }
end
top_level_gemspecs() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 160
def top_level_gemspecs
  dependency_files.
    select { |file| file.name.end_with?(".gemspec") }.
    reject(&:support_file?)
end
updated_gemfile_content(file) click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 253
def updated_gemfile_content(file)
  GemfileUpdater.new(
    dependencies: dependencies,
    gemfile: file
  ).updated_gemfile_content
end
updated_gemspec_content(gemspec) click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 260
def updated_gemspec_content(gemspec)
  GemspecUpdater.new(
    dependencies: dependencies,
    gemspec: gemspec
  ).updated_gemspec_content
end
write_imported_ruby_files() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 141
def write_imported_ruby_files
  imported_ruby_files.each do |file|
    path = file.name
    FileUtils.mkdir_p(Pathname.new(path).dirname)
    File.write(path, file.content)
  end
end
write_path_gemspecs() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 127
def write_path_gemspecs
  path_gemspecs.each do |file|
    path = file.name
    FileUtils.mkdir_p(Pathname.new(path).dirname)
    File.write(path, sanitized_gemspec_content(file.content))
  end

  specification_files.each do |file|
    path = file.name
    FileUtils.mkdir_p(Pathname.new(path).dirname)
    File.write(path, file.content)
  end
end
write_ruby_version_file() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 119
def write_ruby_version_file
  return unless ruby_version_file

  path = ruby_version_file.name
  FileUtils.mkdir_p(Pathname.new(path).dirname)
  File.write(path, ruby_version_file.content)
end
write_temporary_dependency_files() click to toggle source
# File lib/dependabot/bundler/file_updater/lockfile_updater.rb, line 97
def write_temporary_dependency_files
  File.write(gemfile.name, prepared_gemfile_content(gemfile))
  File.write(lockfile.name, sanitized_lockfile_body)

  top_level_gemspecs.each do |gemspec|
    path = gemspec.name
    FileUtils.mkdir_p(Pathname.new(path).dirname)
    updated_content = updated_gemspec_content(gemspec)
    File.write(path, sanitized_gemspec_content(updated_content))
  end

  write_ruby_version_file
  write_path_gemspecs
  write_imported_ruby_files

  evaled_gemfiles.each do |file|
    path = file.name
    FileUtils.mkdir_p(Pathname.new(path).dirname)
    File.write(path, updated_gemfile_content(file))
  end
end