Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add Support for Swift Package Manager #7344

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ jobs:
- { path: python, name: python, ci_node_total: 2, ci_node_index: 1, ecosystem: pip }
- { path: python, name: python_slow, ci_node_total: 2, ci_node_index: 0, ecosystem: pip }
- { path: python, name: python_slow, ci_node_total: 2, ci_node_index: 1, ecosystem: pip }
- { path: swift, name: swift, ci_node_total: 2, ci_node_index: 0, ecosystem: swiftpm }
- { path: swift, name: swift, ci_node_total: 2, ci_node_index: 1, ecosystem: swiftpm }
- { path: terraform, name: terraform, ecosystem: terraform }

steps:
Expand Down Expand Up @@ -160,6 +162,13 @@ jobs:
- 'omnibus/**'
- 'python/**'
- '.github/workflows/ci.yml'
swift:
- Dockerfile.updater-core
- 'common/**'
- 'updater/Gemfil*'
- 'omnibus/**'
- 'swift/**'
- '.github/workflows/ci.yml'
terraform:
- Dockerfile.updater-core
- 'common/**'
Expand Down
1 change: 1 addition & 0 deletions Dockerfile.development
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ COPY --chown=dependabot:dependabot npm_and_yarn/Gemfile npm_and_yarn/dependabot-
COPY --chown=dependabot:dependabot nuget/Gemfile nuget/dependabot-nuget.gemspec ${CODE_DIR}/nuget/
COPY --chown=dependabot:dependabot python/Gemfile python/dependabot-python.gemspec ${CODE_DIR}/python/
COPY --chown=dependabot:dependabot pub/Gemfile pub/dependabot-pub.gemspec ${CODE_DIR}/pub/
COPY --chown=dependabot:dependabot swift/Gemfile swift/dependabot-swift.gemspec ${CODE_DIR}/swift/
COPY --chown=dependabot:dependabot terraform/Gemfile terraform/dependabot-terraform.gemspec ${CODE_DIR}/terraform/

# Prepare the updater project
Expand Down
3 changes: 2 additions & 1 deletion Dockerfile.updater-core
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,11 @@ COPY --chown=dependabot:dependabot npm_and_yarn/Gemfile npm_and_yarn/dependabot-
COPY --chown=dependabot:dependabot nuget/Gemfile nuget/dependabot-nuget.gemspec nuget/
COPY --chown=dependabot:dependabot pub/Gemfile pub/dependabot-pub.gemspec pub/
COPY --chown=dependabot:dependabot python/Gemfile python/dependabot-python.gemspec python/
COPY --chown=dependabot:dependabot swift/Gemfile swift/dependabot-swift.gemspec swift/
COPY --chown=dependabot:dependabot terraform/Gemfile terraform/dependabot-terraform.gemspec terraform/

# prevent having all the source in every ecosystem image
RUN for ecosystem in git_submodules terraform github_actions hex elm docker nuget maven gradle cargo composer go_modules python pub npm_and_yarn bundler; do \
RUN for ecosystem in git_submodules terraform github_actions hex elm docker nuget maven gradle cargo composer go_modules python pub npm_and_yarn bundler swift; do \
mkdir -p $ecosystem/lib/dependabot; \
touch $ecosystem/lib/dependabot/$ecosystem.rb; \
done
Expand Down
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ GEMSPECS = %w(
python/dependabot-python.gemspec
pub/dependabot-pub.gemspec
omnibus/dependabot-omnibus.gemspec
swift/dependabot-swift.gemspec
).freeze

def run_command(command)
Expand Down
3 changes: 3 additions & 0 deletions bin/dry-run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
# - docker
# - terraform
# - pub
# - Swift Package Manager

# rubocop:disable Style/GlobalVars

Expand Down Expand Up @@ -62,6 +63,7 @@
$LOAD_PATH << "./nuget/lib"
$LOAD_PATH << "./python/lib"
$LOAD_PATH << "./pub/lib"
$LOAD_PATH << "./swift/lib"
$LOAD_PATH << "./terraform/lib"

require "bundler"
Expand Down Expand Up @@ -100,6 +102,7 @@
require "dependabot/nuget"
require "dependabot/python"
require "dependabot/pub"
require "dependabot/swift"
require "dependabot/terraform"

# GitHub credentials with write permission to the repo you want to update
Expand Down
1 change: 1 addition & 0 deletions omnibus/dependabot-omnibus.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "dependabot-nuget", Dependabot::VERSION
spec.add_dependency "dependabot-pub", Dependabot::VERSION
spec.add_dependency "dependabot-python", Dependabot::VERSION
spec.add_dependency "dependabot-swift", Dependabot::VERSION
spec.add_dependency "dependabot-terraform", Dependabot::VERSION

common_gemspec.development_dependencies.each do |dep|
Expand Down
1 change: 1 addition & 0 deletions omnibus/lib/dependabot/omnibus.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
require "dependabot/npm_and_yarn"
require "dependabot/bundler"
require "dependabot/pub"
require "dependabot/swift"
6 changes: 6 additions & 0 deletions swift/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/.bundle/
/.env
/tmp
/dependabot-*.gem
Gemfile.lock
helpers/install-dir
1 change: 1 addition & 0 deletions swift/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
inherit_from: ../omnibus/.rubocop.yml
7 changes: 7 additions & 0 deletions swift/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

source "https://rubygems.org"

gem "dependabot-common", path: "../common"

gemspec
23 changes: 23 additions & 0 deletions swift/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## `dependabot-swift`

Swift Package Manager support for [`dependabot-core`][core-repo].

### Running locally

1. Install native helpers
```
$ export DEPENDABOT_NATIVE_HELPERS_PATH=$PWD/helpers/install-dir
$ helpers/build
```

2. Install Ruby dependencies
```
$ bundle install
```

3. Run tests
```
$ bundle exec rspec spec
```

[core-repo]: https://github.com/dependabot/dependabot-core
45 changes: 45 additions & 0 deletions swift/dependabot-swift.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true

require "find"

Gem::Specification.new do |spec|
common_gemspec =
Bundler.load_gemspec_uncached("../common/dependabot-common.gemspec")

spec.name = "dependabot-terraform"
spec.summary = "Swift Package Manager support for dependabot"
spec.version = common_gemspec.version
spec.description = common_gemspec.description

spec.author = common_gemspec.author
spec.email = common_gemspec.email
spec.homepage = common_gemspec.homepage
spec.license = common_gemspec.license

spec.require_path = "lib"
spec.files = []

spec.required_ruby_version = common_gemspec.required_ruby_version
spec.required_rubygems_version = common_gemspec.required_ruby_version

spec.add_dependency "dependabot-common", Dependabot::VERSION

common_gemspec.development_dependencies.each do |dep|
spec.add_development_dependency dep.name, dep.requirement.to_s
end

next unless File.exist?("../.gitignore")

ignores = File.readlines("../.gitignore").grep(/\S+/).map(&:chomp)

next unless File.directory?("lib") && File.directory?("helpers")

prefix = "/" + File.basename(File.expand_path(__dir__)) + "/"
Find.find("lib", "helpers") do |path|
if ignores.any? { |i| File.fnmatch(i, prefix + path, File::FNM_DOTMATCH) }
Find.prune
else
spec.files << path unless File.directory?(path)
end
end
end
20 changes: 20 additions & 0 deletions swift/lib/dependabot/swift.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

# These all need to be required so the various classes can be registered in a
# lookup table of package manager names to concrete classes.
require "dependabot/swift/package"
require "dependabot/swift/file_fetcher"
require "dependabot/swift/file_parser"
require "dependabot/swift/update_checker"
require "dependabot/swift/file_updater"
require "dependabot/swift/metadata_finder"
require "dependabot/swift/requirement"
require "dependabot/swift/version"

require "dependabot/pull_request_creator/labeler"
Dependabot::PullRequestCreator::Labeler.
register_label_details("swift", name: "swift_package_manager", colour: "F05138")

require "dependabot/dependency"
Dependabot::Dependency.
register_production_check("swift", ->(_) { true })
50 changes: 50 additions & 0 deletions swift/lib/dependabot/swift/file_fetcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# frozen_string_literal: true

require "dependabot/file_fetchers"
require "dependabot/file_fetchers/base"
require "dependabot/swift/package"

module Dependabot
module Swift
class FileFetcher < Dependabot::FileFetchers::Base
def self.required_files_in?(filenames)
filenames.include?("Package.swift")
end

def self.required_files_message
"Repo must contain a Package.swift configuration file."
end

private

def fetch_files
fetched_files = []
fetched_files << package_manifest if package_manifest
fetched_files << package_resolved if package_resolved

check_required_files_present

return fetched_files if fetched_files.any?
end

def package_manifest
@package_manifest ||= fetch_file_from_host("Package.swift")
end

def package_resolved
@package_resolved ||= fetch_file_from_host("Package.resolved")
end

def check_required_files_present
return if package_manifest

path = Pathname.new(File.join(directory, "Package.swift")).
cleanpath.to_path
raise Dependabot::DependencyFileNotFound, path
end
end
end
end

Dependabot::FileFetchers.
register("swift", Dependabot::Swift::FileFetcher)
115 changes: 115 additions & 0 deletions swift/lib/dependabot/swift/file_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# frozen_string_literal: true

require "cgi"
require "excon"
require "nokogiri"
require "open3"
require "yaml"
require "shellwords"

require "dependabot/dependency"
require "dependabot/file_parsers"
require "dependabot/file_parsers/base"
require "dependabot/git_commit_checker"
require "dependabot/shared_helpers"
require "dependabot/errors"

module Dependabot
module Swift
class FileParser < Dependabot::FileParsers::Base
require "dependabot/file_parsers/base/dependency_set"

def parse
dependency_set = DependencySet.new

pins.each do |pin|
url = pin["repositoryURL"]
id = Dependabot::Swift::Package::Identifier.new(url)

requirements = parse_declaration(declarations.find { |d| d["url"] == url })

if requirements.empty? && (version = pin.dig("state", "version"))
requirements << Gem::Requirement.new(version.to_s)
end

dependency_set << Dependency.new(
name: id.normalized,
version: version,
package_manager: "swift",
requirements: requirements.map do |requirement|
{
requirement: requirement.to_s,
groups: ["dependencies"],
file: package_manifest_file.name,
source: {
type: "repository",
url: url
}
}
end
)
end

dependency_set.dependencies
end

private

def check_required_files
raise "No Package.swift!" unless package_manifest_file
raise "No Package.resolved!" unless package_resolved_file
end

def declarations
dump_package(package_manifest_file)["dependencies"]
end

def pins
JSON.parse(package_resolved_file.content).dig("object", "pins")
end

def dump_package(manifest)
SharedHelpers.in_a_temporary_directory do |path|
File.write(manifest.name, manifest.content)

command = Shellwords.join([
"swift", "package", "dump-package",
"--skip-update",
"--package-path", path
])

env = {}
stdout, _stderr, _status = Open3.capture3(env, command)
# handle_parser_error(path, stderr) unless status.success?
JSON.parse(stdout)
end
end

def package_manifest_file
# TODO: Select version-specific manifest
@package_manifest_file ||= get_original_file("Package.swift")
end

def package_resolved_file
@package_resolved_file ||= get_original_file("Package.resolved")
end

def parse_declaration(declaration)
requirements = []
declaration&.dig("requirement", "range")&.each do |range|
if (lower_bound = range["lowerBound"])
requirements << Gem::Requirement.new(">= #{lower_bound}")
end

if (upper_bound = range["upperBound"])
requirements << Gem::Requirement.new("< #{upper_bound}")
end
end
requirements
end
end
end
end

Dependabot::FileParsers.
register("swift", Dependabot::Swift::FileParser)
25 changes: 25 additions & 0 deletions swift/lib/dependabot/swift/file_updater.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

require "dependabot/file_updaters"
require "dependabot/file_updaters/base"
require "dependabot/errors"

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

def updated_dependency_files
raise NotImplementedError
end
end
end
end

Dependabot::FileUpdaters.
register("swift", Dependabot::Swift::FileUpdater)
Loading