Skip to content

Commit

Permalink
Implement FileUpdater for Swift
Browse files Browse the repository at this point in the history
  • Loading branch information
deivid-rodriguez committed Jul 19, 2023
1 parent b2501f0 commit 92704f0
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 2 deletions.
61 changes: 59 additions & 2 deletions swift/lib/dependabot/swift/file_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,73 @@

require "dependabot/file_updaters"
require "dependabot/file_updaters/base"
require "dependabot/swift/file_updater/lockfile_updater"
require "dependabot/swift/file_updater/manifest_updater"

module Dependabot
module Swift
class FileUpdater < Dependabot::FileUpdaters::Base
def self.updated_files_regex
raise NotImplementedError
[
/Package(@swift-\d(\.\d){0,2})?\.swift/,
/^Package\.resolved$/
]
end

def updated_dependency_files
raise NotImplementedError
updated_files = []

SharedHelpers.in_a_temporary_repo_directory(manifest.directory, repo_contents_path) do
updated_manifest = nil

if file_changed?(manifest)
updated_manifest = updated_file(file: manifest, content: updated_manifest_content)
updated_files << updated_manifest
end

updated_files << updated_file(file: lockfile, content: updated_lockfile_content(updated_manifest)) if lockfile
end

updated_files
end

private

def dependency
# For now we will be updating a single dependency.
# TODO: Revisit when/if implementing full unlocks
dependencies.first
end

def check_required_files
raise "A Package.swift file must be provided!" unless manifest
end

def updated_manifest_content
ManifestUpdater.new(
manifest.content,
old_requirements: dependency.previous_requirements,
new_requirements: dependency.requirements
).updated_manifest_content
end

def updated_lockfile_content(updated_manifest)
LockfileUpdater.new(
dependencies: dependencies,
manifest: updated_manifest || manifest,
repo_contents_path: repo_contents_path,
credentials: credentials
).updated_lockfile_content
end

def manifest
@manifest ||= get_original_file("Package.swift")
end

def lockfile
return @lockfile if defined?(@lockfile)

@lockfile = get_original_file("Package.resolved")
end
end
end
Expand Down
38 changes: 38 additions & 0 deletions swift/lib/dependabot/swift/file_updater/lockfile_updater.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

require "dependabot/file_updaters/base"
require "dependabot/shared_helpers"

module Dependabot
module Swift
class FileUpdater < Dependabot::FileUpdaters::Base
class LockfileUpdater
def initialize(dependencies:, manifest:, repo_contents_path:, credentials:)
@dependencies = dependencies
@manifest = manifest
@repo_contents_path = repo_contents_path
@credentials = credentials
end

def updated_lockfile_content
SharedHelpers.in_a_temporary_repo_directory(manifest.directory, repo_contents_path) do
File.write(manifest.name, manifest.content)

SharedHelpers.with_git_configured(credentials: credentials) do
SharedHelpers.run_shell_command(
"swift package update #{dependencies.map(&:name).join(' ')}",
fingerprint: "swift package update <dependency_name>"
)

File.read("Package.resolved")
end
end
end

private

attr_reader :dependencies, :manifest, :repo_contents_path, :credentials
end
end
end
end
37 changes: 37 additions & 0 deletions swift/lib/dependabot/swift/file_updater/manifest_updater.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

require "dependabot/file_updaters/base"
require "dependabot/swift/file_updater/requirement_replacer"

module Dependabot
module Swift
class FileUpdater < FileUpdaters::Base
class ManifestUpdater
def initialize(content, old_requirements:, new_requirements:)
@content = content
@old_requirements = old_requirements
@new_requirements = new_requirements
end

def updated_manifest_content
updated_content = content

old_requirements.zip(new_requirements).each do |old, new|
updated_content = RequirementReplacer.new(
content: updated_content,
declaration: old[:metadata][:declaration_string],
old_requirement: old[:metadata][:requirement_string],
new_requirement: new[:metadata][:requirement_string]
).updated_content
end

updated_content
end

private

attr_reader :content, :old_requirements, :new_requirements
end
end
end
end
28 changes: 28 additions & 0 deletions swift/lib/dependabot/swift/file_updater/requirement_replacer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

require "dependabot/file_updaters/base"

module Dependabot
module Swift
class FileUpdater < Dependabot::FileUpdaters::Base
class RequirementReplacer
def initialize(content:, declaration:, old_requirement:, new_requirement:)
@content = content
@declaration = declaration
@old_requirement = old_requirement
@new_requirement = new_requirement
end

def updated_content
content.gsub(declaration) do |match|
match.to_s.sub(old_requirement, new_requirement)
end
end

private

attr_reader :content, :declaration, :old_requirement, :new_requirement
end
end
end
end
96 changes: 96 additions & 0 deletions swift/spec/dependabot/swift/file_updater_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# frozen_string_literal: true

require "spec_helper"
require "dependabot/dependency"
require "dependabot/dependency_file"
require "dependabot/swift/file_updater"
require_common_spec "file_updaters/shared_examples_for_file_updaters"

RSpec.describe Dependabot::Swift::FileUpdater do
it_behaves_like "a dependency file updater"

subject(:updater) do
described_class.new(
dependency_files: files,
dependencies: dependencies,
credentials: credentials,
repo_contents_path: repo_contents_path
)
end

let(:project_name) { "Example" }
let(:repo_contents_path) { build_tmp_repo(project_name) }

let(:files) { project_dependency_files(project_name) }
let(:dependencies) { [] }
let(:credentials) do
[{ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" }]
end

describe "#updated_dependency_files" do
subject { updater.updated_dependency_files }

let(:dependencies) do
[
Dependabot::Dependency.new(
name: "reactiveswift",
version: "7.1.1",
previous_version: "7.1.0",
requirements: [{
requirement: "= 7.1.1",
groups: [],
file: "Package.swift",
source: {
type: "git",
url: "https://github.com/ReactiveCocoa/ReactiveSwift.git",
ref: "7.1.0",
branch: nil
},
metadata: {
requirement_string: "exact: \"7.1.1\""
}
}],
previous_requirements: [{
requirement: "= 7.1.0",
groups: [],
file: "Package.swift",
source: {
type: "git",
url: "https://github.com/ReactiveCocoa/ReactiveSwift.git",
ref: "7.1.0",
branch: nil
},
metadata: {
declaration_string:
".package(url: \"https://github.com/ReactiveCocoa/ReactiveSwift.git\",\n exact: \"7.1.0\")",
requirement_string: "exact: \"7.1.0\""
}
}],
package_manager: "swift"
)
]
end

it "updates the version in manifest and lockfile" do
manifest = subject.find { |file| file.name == "Package.swift" }

expect(manifest.content).to include(
".package(url: \"https://github.com/ReactiveCocoa/ReactiveSwift.git\",\n exact: \"7.1.1\")"
)

lockfile = subject.find { |file| file.name == "Package.resolved" }

expect(lockfile.content.gsub(/^ {4}/, "")).to include <<~RESOLVED
{
"identity" : "reactiveswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git",
"state" : {
"revision" : "40c465af19b993344e84355c00669ba2022ca3cd",
"version" : "7.1.1"
}
},
RESOLVED
end
end
end

0 comments on commit 92704f0

Please sign in to comment.