Skip to content

Commit

Permalink
Implement UpdateChecker for Swift
Browse files Browse the repository at this point in the history
  • Loading branch information
deivid-rodriguez committed Jul 19, 2023
1 parent 92704f0 commit 3080a93
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 3 deletions.
31 changes: 30 additions & 1 deletion swift/lib/dependabot/swift/native_requirement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,26 @@

require "dependabot/utils"
require "dependabot/swift/requirement"
require "dependabot/swift/version"

module Dependabot
module Swift
class NativeRequirement
attr_reader :declaration

def self.map_requirements(requirements)
requirements.map do |requirement|
declaration = new(requirement[:metadata][:requirement_string])

new_declaration = yield(declaration)
new_requirement = new(new_declaration)

requirement.merge(
requirement: new_requirement.to_s,
metadata: { requirement_string: new_declaration }
)
end
end

def initialize(declaration)
@declaration = declaration

Expand All @@ -31,6 +44,22 @@ def to_s
requirement.to_s
end

def update_if_needed(version)
return declaration if requirement.satisfied_by?(version)

update(version)
end

def update(version)
if single_version_declaration?
declaration.sub(min, version.to_s)
elsif closed_range?
declaration.sub(max, version.to_s)
elsif range?
declaration.sub(max, bump_major(version.to_s))
end
end

private

def parse_declaration(declaration)
Expand Down
82 changes: 80 additions & 2 deletions swift/lib/dependabot/swift/update_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,103 @@

require "dependabot/update_checkers"
require "dependabot/update_checkers/base"
require "dependabot/git_commit_checker"
require "dependabot/swift/native_requirement"
require "dependabot/swift/file_updater/manifest_updater"

module Dependabot
module Swift
class UpdateChecker < Dependabot::UpdateCheckers::Base
require_relative "update_checker/requirements_updater"
require_relative "update_checker/version_resolver"

def latest_version
raise NotImplementedError
@latest_version ||= fetch_latest_version
end

def latest_resolvable_version
raise NotImplementedError
@latest_resolvable_version ||= fetch_latest_resolvable_version
end

def latest_resolvable_version_with_no_unlock
raise NotImplementedError
end

def updated_requirements
RequirementsUpdater.new(
requirements: old_requirements,
target_version: preferred_resolvable_version
).updated_requirements
end

private

def old_requirements
dependency.requirements
end

def fetch_latest_version
return unless git_commit_checker.pinned_ref_looks_like_version? && latest_version_tag

latest_version_tag.fetch(:version)
end

def fetch_latest_resolvable_version
Version.new(version_resolver.latest_resolvable_version)
end

def version_resolver
VersionResolver.new(
dependency: dependency,
manifest: prepared_manifest,
repo_contents_path: repo_contents_path,
credentials: credentials
)
end

def unlocked_requirements
NativeRequirement.map_requirements(old_requirements) do |_old_requirement|
"\"#{dependency.version}\"...\"#{latest_version}\""
end
end

def prepared_manifest
DependencyFile.new(
name: manifest.name,
content: FileUpdater::ManifestUpdater.new(
manifest.content,
old_requirements: old_requirements,
new_requirements: unlocked_requirements
).updated_manifest_content
)
end

def manifest
dependency_files.find { |file| file.name == "Package.swift" }
end

def latest_version_resolvable_with_full_unlock?
# Full unlock checks aren't implemented for Swift (yet)
false
end

def updated_dependencies_after_full_unlock
raise NotImplementedError
end

def git_commit_checker
@git_commit_checker ||= Dependabot::GitCommitChecker.new(
dependency: dependency,
credentials: credentials,
ignored_versions: ignored_versions,
raise_on_ignored: raise_on_ignored,
consider_version_branches_pinned: true
)
end

def latest_version_tag
git_commit_checker.local_tag_for_latest_version
end
end
end
end
Expand Down
31 changes: 31 additions & 0 deletions swift/lib/dependabot/swift/update_checker/requirements_updater.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require "dependabot/update_checkers/base"
require "dependabot/swift/native_requirement"
require "dependabot/swift/version"

module Dependabot
module Swift
class UpdateChecker < Dependabot::UpdateCheckers::Base
class RequirementsUpdater
def initialize(requirements:, target_version:)
@requirements = requirements

return unless target_version && Version.correct?(target_version)

@target_version = Version.new(target_version)
end

def updated_requirements
NativeRequirement.map_requirements(requirements) do |requirement|
requirement.update_if_needed(target_version)
end
end

private

attr_reader :requirements, :target_version
end
end
end
end
54 changes: 54 additions & 0 deletions swift/lib/dependabot/swift/update_checker/version_resolver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require "dependabot/update_checkers/base"
require "dependabot/swift/file_parser/dependency_parser"
require "dependabot/swift/file_updater/lockfile_updater"

module Dependabot
module Swift
class UpdateChecker < Dependabot::UpdateCheckers::Base
class VersionResolver
def initialize(dependency:, manifest:, repo_contents_path:, credentials:)
@dependency = dependency
@manifest = manifest
@credentials = credentials
@repo_contents_path = repo_contents_path
end

def latest_resolvable_version
@latest_resolvable_version ||= fetch_latest_resolvable_version
end

private

def fetch_latest_resolvable_version
updated_lockfile_content = FileUpdater::LockfileUpdater.new(
dependencies: [dependency],
manifest: manifest,
repo_contents_path: repo_contents_path,
credentials: credentials
).updated_lockfile_content

lockfile = DependencyFile.new(
name: "Package.resolved",
content: updated_lockfile_content
)

dependency_parser(manifest, lockfile).parse.find do |parsed_dep|
parsed_dep.name == dependency.name
end.version
end

def dependency_parser(manifest, lockfile)
FileParser::DependencyParser.new(
dependency_files: [manifest, lockfile],
repo_contents_path: repo_contents_path,
credentials: credentials
)
end

attr_reader :dependency, :manifest, :repo_contents_path, :credentials
end
end
end
end
132 changes: 132 additions & 0 deletions swift/spec/dependabot/swift/update_checker_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# frozen_string_literal: true

require "spec_helper"
require "dependabot/dependency"
require "dependabot/swift/file_parser"
require "dependabot/swift/update_checker"
require_common_spec "update_checkers/shared_examples_for_update_checkers"

RSpec.describe Dependabot::Swift::UpdateChecker do
it_behaves_like "an update checker"

let(:checker) do
described_class.new(
dependency: dependency,
dependency_files: dependency_files,
repo_contents_path: repo_contents_path,
credentials: github_credentials,
security_advisories: security_advisories,
ignored_versions: ignored_versions,
raise_on_ignored: raise_on_ignored
)
end
let(:project_name) { "ReactiveCocoa" }
let(:repo_contents_path) { build_tmp_repo(project_name, path: "projects") }
let(:dependency_files) { project_dependency_files(project_name) }
let(:security_advisories) { [] }
let(:ignored_versions) { [] }
let(:raise_on_ignored) { false }

let(:dependencies) do
file_parser.parse
end

let(:file_parser) do
Dependabot::Swift::FileParser.new(
dependency_files: dependency_files,
repo_contents_path: repo_contents_path,
source: nil
)
end

let(:dependency) { dependencies.find { |dep| dep.name == name } }

let(:stub_upload_pack) do
stub_request(:get, "#{url}.git/info/refs?service=git-upload-pack").
to_return(
status: 200,
body: fixture("git", "upload_packs", upload_pack_fixture),
headers: {
"content-type" => "application/x-git-upload-pack-advertisement"
}
)
end

context "with an up to date dependency" do
let(:name) { "reactiveswift" }
let(:url) { "https://github.com/ReactiveCocoa/ReactiveSwift" }
let(:upload_pack_fixture) { "reactive-swift" }

before { stub_upload_pack }

describe "#can_update?" do
subject { checker.can_update?(requirements_to_unlock: :own) }

it { is_expected.to be_falsey }
end

describe "#latest_version" do
subject { checker.latest_version }

it { is_expected.to eq(dependency.version) }
end

describe "#latest_resolvable_version" do
subject { checker.latest_resolvable_version }

it { is_expected.to eq(dependency.version) }
end
end

context "with a dependency that needs only lockfile changes to get updated" do
let(:name) { "quick" }
let(:url) { "https://github.com/Quick/Quick" }
let(:upload_pack_fixture) { "quick" }

before { stub_upload_pack }

describe "#can_update?" do
subject { checker.can_update?(requirements_to_unlock: :own) }

it { is_expected.to be_truthy }
end

describe "#latest_version" do
subject { checker.latest_version }

it { is_expected.to eq("7.0.2") }
end

describe "#latest_resolvable_version" do
subject { checker.latest_resolvable_version }

it { is_expected.to eq("7.0.2") }
end
end

context "with a dependency that needs manifest changes to get updated" do
let(:name) { "nimble" }
let(:url) { "https://github.com/Quick/Nimble" }
let(:upload_pack_fixture) { "nimble" }

before { stub_upload_pack }

describe "#can_update?" do
subject { checker.can_update?(requirements_to_unlock: :own) }

it { is_expected.to be_truthy }
end

describe "#latest_version" do
subject { checker.latest_version }

it { is_expected.to eq("12.0.1") }
end

describe "#latest_resolvable_version" do
subject { checker.latest_resolvable_version }

it { is_expected.to eq("12.0.1") }
end
end
end
Binary file added swift/spec/fixtures/git/upload_packs/nimble
Binary file not shown.
Binary file added swift/spec/fixtures/git/upload_packs/quick
Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 3080a93

Please sign in to comment.