From 2cda919efede9e01ab26043655f3ec4a780649f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 27 Jun 2023 17:44:53 +0200 Subject: [PATCH 1/7] Initial skeleton for Swift Co-authored-by: Mattt Co-authored-by: Randall Wood <297232+rhwood@users.noreply.github.com> Co-authored-by: Tim <0xtimc@gmail.com> --- .github/labeler.yml | 3 ++ .github/workflows/ci.yml | 9 ++++ .github/workflows/images-branch.yml | 1 + .github/workflows/images-latest.yml | 1 + .github/workflows/smoke.yml | 7 +++ Dockerfile.development | 1 + Dockerfile.updater-core | 3 +- Rakefile | 1 + bin/docker-dev-shell | 6 +++ bin/dry-run.rb | 3 ++ common/lib/dependabot/config/file.rb | 1 + omnibus/Gemfile | 1 + omnibus/dependabot-omnibus.gemspec | 1 + omnibus/lib/dependabot/omnibus.rb | 1 + script/dependabot | 1 + swift/.gitignore | 5 +++ swift/.rubocop.yml | 1 + swift/Dockerfile | 44 +++++++++++++++++++ swift/Gemfile | 7 +++ swift/README.md | 18 ++++++++ swift/dependabot-swift.gemspec | 35 +++++++++++++++ swift/lib/dependabot/swift.rb | 22 ++++++++++ swift/lib/dependabot/swift/file_fetcher.rb | 27 ++++++++++++ swift/lib/dependabot/swift/file_parser.rb | 23 ++++++++++ swift/lib/dependabot/swift/file_updater.rb | 21 +++++++++ swift/lib/dependabot/swift/metadata_finder.rb | 19 ++++++++ swift/lib/dependabot/swift/requirement.rb | 13 ++++++ swift/lib/dependabot/swift/update_checker.rb | 29 ++++++++++++ swift/lib/dependabot/swift/version.rb | 11 +++++ swift/script/ci-test | 6 +++ swift/spec/dependabot/swift_spec.rb | 9 ++++ swift/spec/spec_helper.rb | 11 +++++ updater/Gemfile | 1 + updater/Gemfile.lock | 7 +++ updater/lib/dependabot/setup.rb | 4 +- updater/lib/dependabot/updater.rb | 1 + 36 files changed, 352 insertions(+), 2 deletions(-) create mode 100644 swift/.gitignore create mode 100644 swift/.rubocop.yml create mode 100644 swift/Dockerfile create mode 100644 swift/Gemfile create mode 100644 swift/README.md create mode 100644 swift/dependabot-swift.gemspec create mode 100644 swift/lib/dependabot/swift.rb create mode 100644 swift/lib/dependabot/swift/file_fetcher.rb create mode 100644 swift/lib/dependabot/swift/file_parser.rb create mode 100644 swift/lib/dependabot/swift/file_updater.rb create mode 100644 swift/lib/dependabot/swift/metadata_finder.rb create mode 100644 swift/lib/dependabot/swift/requirement.rb create mode 100644 swift/lib/dependabot/swift/update_checker.rb create mode 100644 swift/lib/dependabot/swift/version.rb create mode 100755 swift/script/ci-test create mode 100644 swift/spec/dependabot/swift_spec.rb create mode 100644 swift/spec/spec_helper.rb diff --git a/.github/labeler.yml b/.github/labeler.yml index 1584a17af32..cb9d03e280d 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -45,3 +45,6 @@ "L: terraform": - terraform/**/* + +"L: swift": + - swift/**/* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c23cc61a83..113635ce30c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,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: swift } + - { path: swift, name: swift, ci_node_total: 2, ci_node_index: 1, ecosystem: swift } - { path: terraform, name: terraform, ecosystem: terraform } steps: @@ -164,6 +166,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/**' diff --git a/.github/workflows/images-branch.yml b/.github/workflows/images-branch.yml index 794fee44e2a..08e85c2613d 100644 --- a/.github/workflows/images-branch.yml +++ b/.github/workflows/images-branch.yml @@ -40,6 +40,7 @@ jobs: - { name: nuget, ecosystem: nuget } - { name: pub, ecosystem: pub } - { name: python, ecosystem: pip } + - { name: swift, ecosystem: swift } - { name: terraform, ecosystem: terraform } permissions: contents: read diff --git a/.github/workflows/images-latest.yml b/.github/workflows/images-latest.yml index e011a6c095d..fbf5e2f14cd 100644 --- a/.github/workflows/images-latest.yml +++ b/.github/workflows/images-latest.yml @@ -47,6 +47,7 @@ jobs: - { name: nuget, ecosystem: nuget } - { name: pub, ecosystem: pub } - { name: python, ecosystem: pip } + - { name: swift, ecosystem: swift } - { name: terraform, ecosystem: terraform } env: COMMIT_SHA: ${{ github.sha }} diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index e63b6bccb27..8de532f24cc 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -54,6 +54,7 @@ jobs: - { path: python, name: pipenv, ecosystem: pip} - { path: python, name: pip-compile, ecosystem: pip } - { path: python, name: poetry, ecosystem: pip } + - { path: swift, name: swift, ecosystem: swift } - { path: terraform, name: terraform, ecosystem: terraform } steps: - uses: actions/checkout@v3 @@ -212,6 +213,12 @@ jobs: - 'common/**' - 'updater/**' - 'git_submodules/**' + swift: + - .github/workflows/smoke.yml + - Dockerfile.updater-core + - 'common/**' + - 'updater/**' + - 'swift/**' terraform: - .github/workflows/smoke.yml - Dockerfile.updater-core diff --git a/Dockerfile.development b/Dockerfile.development index d22d8693536..ded93d760be 100644 --- a/Dockerfile.development +++ b/Dockerfile.development @@ -44,6 +44,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 diff --git a/Dockerfile.updater-core b/Dockerfile.updater-core index c8c0d1d1bae..34afd17b51d 100644 --- a/Dockerfile.updater-core +++ b/Dockerfile.updater-core @@ -79,10 +79,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 diff --git a/Rakefile b/Rakefile index 0a16d248c5e..5d668dbdeb3 100644 --- a/Rakefile +++ b/Rakefile @@ -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) diff --git a/bin/docker-dev-shell b/bin/docker-dev-shell index 6a889d56e49..7742b8685d8 100755 --- a/bin/docker-dev-shell +++ b/bin/docker-dev-shell @@ -226,6 +226,12 @@ docker run --rm -ti \ -v "$(pwd)/python/lib:$CODE_DIR/python/lib" \ -v "$(pwd)/python/script:$CODE_DIR/python/script" \ -v "$(pwd)/python/spec:$CODE_DIR/python/spec" \ + -v "$(pwd)/swift/.rubocop.yml:$CODE_DIR/swift/.rubocop.yml" \ + -v "$(pwd)/swift/Gemfile:$CODE_DIR/swift/Gemfile" \ + -v "$(pwd)/swift/dependabot-swift.gemspec:$CODE_DIR/swift/dependabot-swift.gemspec" \ + -v "$(pwd)/swift/lib:$CODE_DIR/swift/lib" \ + -v "$(pwd)/swift/script:$CODE_DIR/swift/script" \ + -v "$(pwd)/swift/spec:$CODE_DIR/swift/spec" \ -v "$(pwd)/terraform/.rubocop.yml:$CODE_DIR/terraform/.rubocop.yml" \ -v "$(pwd)/terraform/Gemfile:$CODE_DIR/terraform/Gemfile" \ -v "$(pwd)/terraform/dependabot-terraform.gemspec:$CODE_DIR/terraform/dependabot-terraform.gemspec" \ diff --git a/bin/dry-run.rb b/bin/dry-run.rb index 7020df2dbce..2cb5d6b4c73 100755 --- a/bin/dry-run.rb +++ b/bin/dry-run.rb @@ -33,6 +33,7 @@ # - docker # - terraform # - pub +# - swift # rubocop:disable Style/GlobalVars @@ -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" @@ -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 diff --git a/common/lib/dependabot/config/file.rb b/common/lib/dependabot/config/file.rb index e5d446c1374..3f4f2d80e91 100644 --- a/common/lib/dependabot/config/file.rb +++ b/common/lib/dependabot/config/file.rb @@ -53,6 +53,7 @@ def self.parse(config) "npm" => "npm_and_yarn", "pip" => "pip", "pub" => "pub", + "swift" => "swift", "terraform" => "terraform" }.freeze diff --git a/omnibus/Gemfile b/omnibus/Gemfile index a330f0516b0..9ce89ed4d71 100644 --- a/omnibus/Gemfile +++ b/omnibus/Gemfile @@ -18,6 +18,7 @@ gemspec path: "../npm_and_yarn" gemspec path: "../nuget" gemspec path: "../pub" gemspec path: "../python" +gemspec path: "../swift" gemspec path: "../terraform" # Visual Studio Code integration diff --git a/omnibus/dependabot-omnibus.gemspec b/omnibus/dependabot-omnibus.gemspec index 79760d579b5..3fdda0368e7 100644 --- a/omnibus/dependabot-omnibus.gemspec +++ b/omnibus/dependabot-omnibus.gemspec @@ -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| diff --git a/omnibus/lib/dependabot/omnibus.rb b/omnibus/lib/dependabot/omnibus.rb index 215a08bc449..58b29d37c92 100644 --- a/omnibus/lib/dependabot/omnibus.rb +++ b/omnibus/lib/dependabot/omnibus.rb @@ -16,3 +16,4 @@ require "dependabot/npm_and_yarn" require "dependabot/bundler" require "dependabot/pub" +require "dependabot/swift" diff --git a/script/dependabot b/script/dependabot index 2b857442f3d..df0dc7e100a 100755 --- a/script/dependabot +++ b/script/dependabot @@ -21,5 +21,6 @@ dependabot \ -v "$(pwd)"/nuget:/home/dependabot/nuget \ -v "$(pwd)"/pub:/home/dependabot/pub \ -v "$(pwd)"/python:/home/dependabot/python \ + -v "$(pwd)"/swift:/home/dependabot/swift \ -v "$(pwd)"/terraform:/home/dependabot/terraform \ "$@" diff --git a/swift/.gitignore b/swift/.gitignore new file mode 100644 index 00000000000..06845bd78c0 --- /dev/null +++ b/swift/.gitignore @@ -0,0 +1,5 @@ +/.bundle/ +/.env +/tmp +/dependabot-*.gem +Gemfile.lock diff --git a/swift/.rubocop.yml b/swift/.rubocop.yml new file mode 100644 index 00000000000..a9f364a37fa --- /dev/null +++ b/swift/.rubocop.yml @@ -0,0 +1 @@ +inherit_from: ../omnibus/.rubocop.yml diff --git a/swift/Dockerfile b/swift/Dockerfile new file mode 100644 index 00000000000..0bf0fbbe975 --- /dev/null +++ b/swift/Dockerfile @@ -0,0 +1,44 @@ +FROM ghcr.io/dependabot/dependabot-updater-core +ARG TARGETARCH + +ENV PATH="${PATH}:/opt/swift/usr/bin" + +# OS dependencies +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + binutils \ + libc6-dev \ + libcurl4 \ + libedit2 \ + libgcc-9-dev \ + libpython2.7 \ + libsqlite3-0 \ + libstdc++-9-dev \ + libxml2 \ + libz3-dev \ + pkg-config \ + tzdata \ + uuid-dev \ + && rm -rf /var/lib/apt/lists/* + +USER dependabot + +# https://www.swift.org/download/ +ARG SWIFT_VERSION=5.8.1 +ARG SWIFT_UBUNTU_VERSION=ubuntu20.04 + +RUN if [ "$TARGETARCH" = "arm64" ]; then SWIFT_UBUNTU_VERSION="${SWIFT_UBUNTU_VERSION}-aarch64"; fi \ + && SWIFT_SHORT_UBUNTU_VERSION=$(echo $SWIFT_UBUNTU_VERSION | tr -d .) \ + && SWIFT_TARBALL="swift-${SWIFT_VERSION}-RELEASE-${SWIFT_UBUNTU_VERSION}.tar.gz" \ + && DOWNLOAD_URL=https://download.swift.org/swift-${SWIFT_VERSION}-release/${SWIFT_SHORT_UBUNTU_VERSION}/swift-${SWIFT_VERSION}-RELEASE/${SWIFT_TARBALL} \ + && curl --connect-timeout 15 --retry 5 "${DOWNLOAD_URL}" > "/tmp/${SWIFT_TARBALL}" \ + && curl --connect-timeout 15 --retry 5 "${DOWNLOAD_URL}.sig" > "/tmp/${SWIFT_TARBALL}.sig" \ + && sh -c 'curl --connect-timeout 15 --retry 5 https://www.swift.org/keys/all-keys.asc | gpg --import -' \ + && gpg --keyserver hkp://keyserver.ubuntu.com --refresh-keys Swift \ + && gpg --verify /tmp/${SWIFT_TARBALL}.sig \ + && mkdir /opt/swift \ + && tar -C /opt/swift -xzf /tmp/${SWIFT_TARBALL} --strip-components 1 + +COPY --chown=dependabot:dependabot swift $DEPENDABOT_HOME/swift +COPY --chown=dependabot:dependabot common $DEPENDABOT_HOME/common +COPY --chown=dependabot:dependabot updater $DEPENDABOT_HOME/dependabot-updater diff --git a/swift/Gemfile b/swift/Gemfile new file mode 100644 index 00000000000..430599a9e1b --- /dev/null +++ b/swift/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "dependabot-common", path: "../common" + +gemspec diff --git a/swift/README.md b/swift/README.md new file mode 100644 index 00000000000..2b3a2f13f31 --- /dev/null +++ b/swift/README.md @@ -0,0 +1,18 @@ +## `dependabot-swift` + +Swift Package Manager support for [`dependabot-core`][core-repo]. + +### Running locally + +1. Start a development shell + + ``` + $ bin/docker-dev-shell swift + ``` + +2. Run tests + ``` + [dependabot-core-dev] ~/dependabot-core $ cd swift && rspec + ``` + +[core-repo]: https://github.com/dependabot/dependabot-core diff --git a/swift/dependabot-swift.gemspec b/swift/dependabot-swift.gemspec new file mode 100644 index 00000000000..29f02441cf3 --- /dev/null +++ b/swift/dependabot-swift.gemspec @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +Gem::Specification.new do |spec| + common_gemspec = + Bundler.load_gemspec_uncached("../common/dependabot-common.gemspec") + + spec.name = "dependabot-swift" + spec.summary = "Provides Dependabot support for Swift" + spec.description = "Dependabot-Swift provides support for bumping Swift packages via Dependabot. " \ + "If you want support for multiple package managers, you probably want the meta-gem " \ + "dependabot-omnibus." + + spec.author = common_gemspec.author + spec.email = common_gemspec.email + spec.homepage = common_gemspec.homepage + spec.license = common_gemspec.license + + spec.metadata = { + "bug_tracker_uri" => common_gemspec.metadata["bug_tracker_uri"], + "changelog_uri" => common_gemspec.metadata["changelog_uri"] + } + + spec.version = common_gemspec.version + spec.required_ruby_version = common_gemspec.required_ruby_version + spec.required_rubygems_version = common_gemspec.required_ruby_version + + spec.require_path = "lib" + spec.files = Dir["lib/**/*"] + + spec.add_dependency "dependabot-common", Dependabot::VERSION + + common_gemspec.development_dependencies.each do |dep| + spec.add_development_dependency dep.name, *dep.requirement.as_list + end +end diff --git a/swift/lib/dependabot/swift.rb b/swift/lib/dependabot/swift.rb new file mode 100644 index 00000000000..4a84352f197 --- /dev/null +++ b/swift/lib/dependabot/swift.rb @@ -0,0 +1,22 @@ +# 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/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 }) + +require "dependabot/utils" +Dependabot::Utils.register_always_clone("swift") diff --git a/swift/lib/dependabot/swift/file_fetcher.rb b/swift/lib/dependabot/swift/file_fetcher.rb new file mode 100644 index 00000000000..60ba0d8937c --- /dev/null +++ b/swift/lib/dependabot/swift/file_fetcher.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "dependabot/file_fetchers" +require "dependabot/file_fetchers/base" + +module Dependabot + module Swift + class FileFetcher < Dependabot::FileFetchers::Base + def self.required_files_in?(filenames) + raise NotImplementedError + end + + def self.required_files_message + raise NotImplementedError + end + + private + + def fetch_files + raise NotImplementedError + end + end + end +end + +Dependabot::FileFetchers. + register("swift", Dependabot::Swift::FileFetcher) diff --git a/swift/lib/dependabot/swift/file_parser.rb b/swift/lib/dependabot/swift/file_parser.rb new file mode 100644 index 00000000000..da78439d5c2 --- /dev/null +++ b/swift/lib/dependabot/swift/file_parser.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "dependabot/file_parsers" +require "dependabot/file_parsers/base" + +module Dependabot + module Swift + class FileParser < Dependabot::FileParsers::Base + def parse + raise NotImplementedError + end + + private + + def check_required_files + raise NotImplementedError + end + end + end +end + +Dependabot::FileParsers. + register("swift", Dependabot::Swift::FileParser) diff --git a/swift/lib/dependabot/swift/file_updater.rb b/swift/lib/dependabot/swift/file_updater.rb new file mode 100644 index 00000000000..2055d2125a9 --- /dev/null +++ b/swift/lib/dependabot/swift/file_updater.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "dependabot/file_updaters" +require "dependabot/file_updaters/base" + +module Dependabot + module Swift + class FileUpdater < Dependabot::FileUpdaters::Base + def self.updated_files_regex + raise NotImplementedError + end + + def updated_dependency_files + raise NotImplementedError + end + end + end +end + +Dependabot::FileUpdaters. + register("swift", Dependabot::Swift::FileUpdater) diff --git a/swift/lib/dependabot/swift/metadata_finder.rb b/swift/lib/dependabot/swift/metadata_finder.rb new file mode 100644 index 00000000000..86dd09efa62 --- /dev/null +++ b/swift/lib/dependabot/swift/metadata_finder.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "dependabot/metadata_finders" +require "dependabot/metadata_finders/base" + +module Dependabot + module Swift + class MetadataFinder < Dependabot::MetadataFinders::Base + private + + def look_up_source + raise NotImplementedError + end + end + end +end + +Dependabot::MetadataFinders. + register("swift", Dependabot::Swift::MetadataFinder) diff --git a/swift/lib/dependabot/swift/requirement.rb b/swift/lib/dependabot/swift/requirement.rb new file mode 100644 index 00000000000..371f342a385 --- /dev/null +++ b/swift/lib/dependabot/swift/requirement.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "dependabot/utils" + +module Dependabot + module Swift + class Requirement < Gem::Requirement + end + end +end + +Dependabot::Utils. + register_requirement_class("swift", Dependabot::Swift::Requirement) diff --git a/swift/lib/dependabot/swift/update_checker.rb b/swift/lib/dependabot/swift/update_checker.rb new file mode 100644 index 00000000000..372af9909d4 --- /dev/null +++ b/swift/lib/dependabot/swift/update_checker.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "dependabot/update_checkers" +require "dependabot/update_checkers/base" + +module Dependabot + module Swift + class UpdateChecker < Dependabot::UpdateCheckers::Base + def latest_version + raise NotImplementedError + end + + def latest_resolvable_version + raise NotImplementedError + end + + def latest_resolvable_version_with_no_unlock + raise NotImplementedError + end + + def updated_requirements + raise NotImplementedError + end + end + end +end + +Dependabot::UpdateCheckers. + register("swift", Dependabot::Swift::UpdateChecker) diff --git a/swift/lib/dependabot/swift/version.rb b/swift/lib/dependabot/swift/version.rb new file mode 100644 index 00000000000..c3ef4520587 --- /dev/null +++ b/swift/lib/dependabot/swift/version.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Dependabot + module Swift + class Version < Dependabot::Version + end + end +end + +Dependabot::Utils. + register_version_class("swift", Dependabot::Swift::Version) diff --git a/swift/script/ci-test b/swift/script/ci-test new file mode 100755 index 00000000000..8ba165bea47 --- /dev/null +++ b/swift/script/ci-test @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +bundle install +bundle exec parallel_test spec/ -n "$CI_NODE_TOTAL" --only-group "$CI_NODE_INDEX" --group-by filesize --type rspec diff --git a/swift/spec/dependabot/swift_spec.rb b/swift/spec/dependabot/swift_spec.rb new file mode 100644 index 00000000000..76b6458a32d --- /dev/null +++ b/swift/spec/dependabot/swift_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/swift" +require_common_spec "shared_examples_for_autoloading" + +RSpec.describe Dependabot::Swift do + it_behaves_like "it registers the required classes", "swift" +end diff --git a/swift/spec/spec_helper.rb b/swift/spec/spec_helper.rb new file mode 100644 index 00000000000..6dcb854b37c --- /dev/null +++ b/swift/spec/spec_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +def common_dir + @common_dir ||= Gem::Specification.find_by_name("dependabot-common").gem_dir +end + +def require_common_spec(path) + require "#{common_dir}/spec/dependabot/#{path}" +end + +require "#{common_dir}/spec/spec_helper.rb" diff --git a/updater/Gemfile b/updater/Gemfile index 10af28f837c..75714e611bb 100644 --- a/updater/Gemfile +++ b/updater/Gemfile @@ -18,6 +18,7 @@ gem "dependabot-npm_and_yarn", path: "../npm_and_yarn" gem "dependabot-nuget", path: "../nuget" gem "dependabot-pub", path: "../pub" gem "dependabot-python", path: "../python" +gem "dependabot-swift", path: "../swift" gem "dependabot-terraform", path: "../terraform" gem "http", "~> 5.1" diff --git a/updater/Gemfile.lock b/updater/Gemfile.lock index 26ae6d29c52..b5e32a45d13 100644 --- a/updater/Gemfile.lock +++ b/updater/Gemfile.lock @@ -109,6 +109,12 @@ PATH dependabot-python (0.221.0) dependabot-common (= 0.221.0) +PATH + remote: ../swift + specs: + dependabot-swift (0.221.0) + dependabot-common (= 0.221.0) + PATH remote: ../terraform specs: @@ -309,6 +315,7 @@ DEPENDENCIES dependabot-nuget! dependabot-pub! dependabot-python! + dependabot-swift! dependabot-terraform! http (~> 5.1) licensed (~> 4.3) diff --git a/updater/lib/dependabot/setup.rb b/updater/lib/dependabot/setup.rb index d9fa805ad97..d9395bffaf9 100644 --- a/updater/lib/dependabot/setup.rb +++ b/updater/lib/dependabot/setup.rb @@ -34,7 +34,8 @@ go_modules| npm_and_yarn| bundler| - pub + pub| + swift )}x config.processors += [ExceptionSanitizer] @@ -59,3 +60,4 @@ require "dependabot/npm_and_yarn" require "dependabot/bundler" require "dependabot/pub" +require "dependabot/swift" diff --git a/updater/lib/dependabot/updater.rb b/updater/lib/dependabot/updater.rb index 30830842bfd..846d6463051 100644 --- a/updater/lib/dependabot/updater.rb +++ b/updater/lib/dependabot/updater.rb @@ -27,6 +27,7 @@ require "dependabot/npm_and_yarn" require "dependabot/bundler" require "dependabot/pub" +require "dependabot/swift" # Updater components require "dependabot/updater/error_handler" From 533c58d1c667141eae1eed12f5691df2edd123c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 27 Jun 2023 17:47:01 +0200 Subject: [PATCH 2/7] Implement FileFetcher for Swift --- swift/lib/dependabot/swift/file_fetcher.rb | 29 ++++++++-- .../dependabot/swift/file_fetcher_spec.rb | 53 +++++++++++++++++++ .../projects/manifest-only/Package.swift | 10 ++++ .../projects/standard/Package.resolved | 41 ++++++++++++++ .../fixtures/projects/standard/Package.swift | 10 ++++ 5 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 swift/spec/dependabot/swift/file_fetcher_spec.rb create mode 100644 swift/spec/fixtures/projects/manifest-only/Package.swift create mode 100644 swift/spec/fixtures/projects/standard/Package.resolved create mode 100644 swift/spec/fixtures/projects/standard/Package.swift diff --git a/swift/lib/dependabot/swift/file_fetcher.rb b/swift/lib/dependabot/swift/file_fetcher.rb index 60ba0d8937c..8d548f9cda0 100644 --- a/swift/lib/dependabot/swift/file_fetcher.rb +++ b/swift/lib/dependabot/swift/file_fetcher.rb @@ -7,17 +7,40 @@ module Dependabot module Swift class FileFetcher < Dependabot::FileFetchers::Base def self.required_files_in?(filenames) - raise NotImplementedError + filenames.include?("Package.swift") end def self.required_files_message - raise NotImplementedError + "Repo must contain a Package.swift configuration file." end private def fetch_files - raise NotImplementedError + check_required_files_present + + fetched_files = [] + fetched_files << package_manifest + fetched_files << package_resolved if package_resolved + fetched_files + end + + def package_manifest + @package_manifest ||= fetch_file_from_host("Package.swift") + end + + def package_resolved + return @package_resolved if defined?(@package_resolved) + + @package_resolved = fetch_file_if_present("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 diff --git a/swift/spec/dependabot/swift/file_fetcher_spec.rb b/swift/spec/dependabot/swift/file_fetcher_spec.rb new file mode 100644 index 00000000000..6aeecb35188 --- /dev/null +++ b/swift/spec/dependabot/swift/file_fetcher_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/swift/file_fetcher" +require_common_spec "file_fetchers/shared_examples_for_file_fetchers" + +RSpec.describe Dependabot::Swift::FileFetcher do + it_behaves_like "a dependency file fetcher" + + let(:source) do + Dependabot::Source.new( + provider: "github", + repo: "mona/swift-example", + directory: directory + ) + end + + let(:file_fetcher_instance) do + described_class.new(source: source, credentials: [], repo_contents_path: repo_contents_path) + end + + let(:repo_contents_path) { build_tmp_repo(project_name) } + + context "with Package.swift and Package.resolved" do + let(:project_name) { "standard" } + let(:directory) { "/" } + + it "fetches the manifest and resolved files" do + expect(file_fetcher_instance.files.map(&:name)). + to match_array(%w(Package.swift Package.resolved)) + end + end + + context "with Package.swift and Package.resolved" do + let(:project_name) { "manifest-only" } + let(:directory) { "/" } + + it "fetches the manifest and resolved files" do + expect(file_fetcher_instance.files.map(&:name)). + to match_array(%w(Package.swift)) + end + end + + context "with a directory that doesn't exist" do + let(:project_name) { "standard" } + let(:directory) { "/nonexistent" } + + it "raises a helpful error" do + expect { file_fetcher_instance.files }. + to raise_error(Dependabot::DependencyFileNotFound) + end + end +end diff --git a/swift/spec/fixtures/projects/manifest-only/Package.swift b/swift/spec/fixtures/projects/manifest-only/Package.swift new file mode 100644 index 00000000000..f13e4bc1179 --- /dev/null +++ b/swift/spec/fixtures/projects/manifest-only/Package.swift @@ -0,0 +1,10 @@ +// swift-tools-version:5.8.0 + +import PackageDescription + +let package = Package( + name: "tuist", + dependencies: [ + .package(url: "https://github.com/apple/swift-nio-http2.git", exact: "1.19.2") + ] +) diff --git a/swift/spec/fixtures/projects/standard/Package.resolved b/swift/spec/fixtures/projects/standard/Package.resolved new file mode 100644 index 00000000000..206dd3e4ede --- /dev/null +++ b/swift/spec/fixtures/projects/standard/Package.resolved @@ -0,0 +1,41 @@ +{ + "pins" : [ + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", + "version" : "1.1.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "937e904258d22af6e447a0b72c0bc67583ef64a2", + "version" : "1.0.4" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "6213ba7a06febe8fef60563a4a7d26a4085783cf", + "version" : "2.54.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "000ca94f9de92c95b9ac85d44600b7b0fe25a3e5", + "version" : "1.19.2" + } + } + ], + "version" : 2 +} diff --git a/swift/spec/fixtures/projects/standard/Package.swift b/swift/spec/fixtures/projects/standard/Package.swift new file mode 100644 index 00000000000..f13e4bc1179 --- /dev/null +++ b/swift/spec/fixtures/projects/standard/Package.swift @@ -0,0 +1,10 @@ +// swift-tools-version:5.8.0 + +import PackageDescription + +let package = Package( + name: "tuist", + dependencies: [ + .package(url: "https://github.com/apple/swift-nio-http2.git", exact: "1.19.2") + ] +) From 9aaa7adcb59306f677ada0e9a9d4ba56aba45b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 28 Jun 2023 15:14:08 +0200 Subject: [PATCH 3/7] Implement swift requirement parsing --- .../dependabot/swift/native_requirement.rb | 130 ++++++++++++++++++ swift/lib/dependabot/swift/requirement.rb | 16 +++ .../swift/native_requirement_spec.rb | 39 ++++++ 3 files changed, 185 insertions(+) create mode 100644 swift/lib/dependabot/swift/native_requirement.rb create mode 100644 swift/spec/dependabot/swift/native_requirement_spec.rb diff --git a/swift/lib/dependabot/swift/native_requirement.rb b/swift/lib/dependabot/swift/native_requirement.rb new file mode 100644 index 00000000000..3ed6e6344a6 --- /dev/null +++ b/swift/lib/dependabot/swift/native_requirement.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "dependabot/utils" +require "dependabot/swift/requirement" +require "dependabot/swift/version" + +module Dependabot + module Swift + class NativeRequirement + attr_reader :declaration + + def initialize(declaration) + @declaration = declaration + + min, max = parse_declaration(declaration) + + constraint = if min == max + ["= #{min}"] + elsif closed_range? + [">= #{min}", "<= #{max}"] + else + [">= #{min}", "< #{max}"] + end + + @min = min + @max = max + @requirement = Requirement.new(constraint) + end + + def to_s + requirement.to_s + end + + private + + def parse_declaration(declaration) + if up_to_next_major? + min = declaration.gsub(/\Afrom\s*:\s*"(\S+)"\s*\z/, '\1') + max = bump_major(min) + elsif up_to_next_major_deprecated? + min = declaration.gsub(/\A\.upToNextMajor\s*\(\s*from\s*:\s*"(\S+)"\s*\)\z/, '\1') + max = bump_major(min) + elsif up_to_next_minor_deprecated? + min = declaration.gsub(/\A\.upToNextMinor\s*\(\s*from\s*:\s*"(\S+)"\s*\)\z/, '\1') + max = bump_minor(min) + elsif closed_range? + min, max = parse_range("...") + elsif range? + min, max = parse_range("..<") + elsif exact_version? + min = declaration.gsub(/\Aexact\s*:\s*"(\S+)"\s*\z/, '\1') + max = min + elsif exact_version_deprecated? + min = declaration.gsub(/\A\.exact\s*\(\s*"(\S+)"\s*\)\z/, '\1') + max = min + else + raise "Unsupported constraint: #{declaration}" + end + + [min, max] + end + + def parse_range(separator) + declaration.split(separator).map { |str| unquote(str) } + end + + def single_version_declaration? + up_to_next_major? || up_to_next_major_deprecated? || up_to_next_minor? || + exact_version? || exact_version_deprecated? + end + + def bump_major(str) + transform_version(str) do |s, i| + i.zero? ? s.to_i + 1 : 0 + end + end + + def bump_minor(str) + transform_version(str) do |s, i| + if i.zero? + s + else + (i == 1 ? s.to_i + 1 : 0) + end + end + end + + def transform_version(str, &block) + str.split(".").map.with_index(&block).join(".") + end + + def up_to_next_major? + declaration.start_with?("from") + end + + def up_to_next_major_deprecated? + declaration.start_with?(".upToNextMajor") + end + + def up_to_next_minor_deprecated? + declaration.start_with?(".upToNextMinor") + end + + def exact_version? + declaration.start_with?("exact") + end + + def exact_version_deprecated? + declaration.start_with?(".exact") + end + + def closed_range? + declaration.include?("...") + end + + def range? + declaration.include?("..<") + end + + attr_reader :min, :max, :requirement + + def unquote(declaration) + declaration[1..-2] + end + end + end +end + +Dependabot::Utils. + register_requirement_class("swift", Dependabot::Swift::Requirement) diff --git a/swift/lib/dependabot/swift/requirement.rb b/swift/lib/dependabot/swift/requirement.rb index 371f342a385..aa9e20c46d9 100644 --- a/swift/lib/dependabot/swift/requirement.rb +++ b/swift/lib/dependabot/swift/requirement.rb @@ -5,6 +5,22 @@ module Dependabot module Swift class Requirement < Gem::Requirement + # For consistency with other languages, we define a requirements array. + # Swift doesn't have an `OR` separator for requirements, so it + # always contains a single element. + def self.requirements_array(requirement_string) + [new(requirement_string)] + end + + # Patches Gem::Requirement to make it accept requirement strings like + # "~> 4.2.5, >= 4.2.5.1" without first needing to split them. + def initialize(*requirements) + requirements = requirements.flatten.flat_map do |req_string| + req_string.split(",").map(&:strip) + end + + super(requirements) + end end end end diff --git a/swift/spec/dependabot/swift/native_requirement_spec.rb b/swift/spec/dependabot/swift/native_requirement_spec.rb new file mode 100644 index 00000000000..744ef3da85b --- /dev/null +++ b/swift/spec/dependabot/swift/native_requirement_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/swift/native_requirement" + +RSpec.describe Dependabot::Swift::NativeRequirement do + RSpec::Matchers.define :parse_as do |requirement| + match do |declaration| + described_class.new(declaration).to_s == requirement + end + end + + describe ".new" do + it "parses different ways of declaring requirements" do + expect('from: "1.0.0"').to parse_as(">= 1.0.0, < 2.0.0") + expect('from : "1.0.0"').to parse_as(">= 1.0.0, < 2.0.0") + + expect('exact: "1.0.0"').to parse_as("= 1.0.0") + expect('exact : "1.0.0"').to parse_as("= 1.0.0") + + expect('.upToNextMajor(from: "1.0.0")').to parse_as(">= 1.0.0, < 2.0.0") + expect('.upToNextMajor (from: "1.0.0")').to parse_as(">= 1.0.0, < 2.0.0") + expect('.upToNextMajor( from: "1.0.0" )').to parse_as(">= 1.0.0, < 2.0.0") + expect('.upToNextMajor (from : "1.0.0")').to parse_as(">= 1.0.0, < 2.0.0") + + expect('.upToNextMinor(from: "1.0.0")').to parse_as(">= 1.0.0, < 1.1.0") + expect('.upToNextMinor (from: "1.0.0")').to parse_as(">= 1.0.0, < 1.1.0") + expect('.upToNextMinor( from: "1.0.0" )').to parse_as(">= 1.0.0, < 1.1.0") + expect('.upToNextMinor (from : "1.0.0")').to parse_as(">= 1.0.0, < 1.1.0") + + expect('.exact("1.0.0")').to parse_as("= 1.0.0") + expect('.exact ("1.0.0")').to parse_as("= 1.0.0") + expect('.exact( "1.0.0" )').to parse_as("= 1.0.0") + + expect('"1.0.0"..<"2.0.0"').to parse_as(">= 1.0.0, < 2.0.0") + expect('"1.0.0"..."2.0.0"').to parse_as(">= 1.0.0, <= 2.0.0") + end + end +end From c9e14c9c7158181e8e2ed41df10e248988295931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 27 Jun 2023 17:48:45 +0200 Subject: [PATCH 4/7] Implement FileParser for Swift --- swift/lib/dependabot/swift/file_parser.rb | 41 +++- .../swift/file_parser/dependency_parser.rb | 72 +++++++ .../swift/file_parser/manifest_parser.rb | 47 +++++ .../spec/dependabot/swift/file_parser_spec.rb | 199 ++++++++++++++++++ .../.build/checkouts/Nimble/Package.resolved | 25 +++ .../.build/checkouts/Nimble/Package.swift | 21 ++ .../checkouts/Nimble/Sources/Nimble/DSL.swift | 1 + .../.build/checkouts/Quick/Package.resolved | 32 +++ .../.build/checkouts/Quick/Package.swift | 64 ++++++ .../Quick/Sources/Quick/QuickMain.swift | 1 + .../swift-argument-parser/Package.swift | 70 ++++++ .../Sources/ArgumentParser/Parsing/Name.swift | 1 + .../ArgumentParserToolInfo/ToolInfo.swift | 1 + .../swift-benchmark/Package.resolved | 16 ++ .../checkouts/swift-benchmark/Package.swift | 46 ++++ .../Sources/Benchmark/Benchmark.swift | 1 + .../swift-case-paths/Package.resolved | 43 ++++ .../checkouts/swift-case-paths/Package.swift | 49 +++++ .../swift-case-paths/Package@swift-5.1.swift | 22 ++ .../Sources/CasePaths/CasePaths.swift | 1 + .../swift-custom-dump/Package.resolved | 16 ++ .../checkouts/swift-custom-dump/Package.swift | 36 ++++ .../Sources/CustomDump/Dump.swift | 1 + .../checkouts/swift-docc-plugin/Package.swift | 21 ++ .../HelpInformation.swift | 1 + .../xctest-dynamic-overlay/Package.resolved | 16 ++ .../xctest-dynamic-overlay/Package.swift | 30 +++ .../XCTestDynamicOverlay/XCTFail.swift | 1 + .../.build/workspace-state.json | 145 +++++++++++++ .../Example-Deprecated/Package.resolved | 76 +++++++ .../projects/Example-Deprecated/Package.swift | 25 +++ .../checkouts/ReactiveSwift/Package.resolved | 43 ++++ .../checkouts/ReactiveSwift/Package.swift | 17 ++ .../ReactiveSwift/Sources/Reactive.swift | 1 + .../combine-schedulers/Package.resolved | 16 ++ .../combine-schedulers/Package.swift | 30 +++ .../Sources/CombineSchedulers/SwiftUI.swift | 1 + .../swift-argument-parser/Package.swift | 31 +++ .../Sources/ArgumentParser/Parsing/Name.swift | 1 + .../ArgumentParserToolInfo/ToolInfo.swift | 1 + .../swift-benchmark/Package.resolved | 16 ++ .../checkouts/swift-benchmark/Package.swift | 34 +++ .../Sources/Benchmark/Benchmark.swift | 1 + .../swift-case-paths/Package.resolved | 43 ++++ .../checkouts/swift-case-paths/Package.swift | 38 ++++ .../swift-case-paths/Package@swift-5.1.swift | 18 ++ .../Sources/CasePaths/CasePath.swift | 1 + .../swift-custom-dump/Package.resolved | 16 ++ .../checkouts/swift-custom-dump/Package.swift | 30 +++ .../Sources/CustomDump/Dump.swift | 1 + .../checkouts/swift-docc-plugin/Package.swift | 21 ++ .../SwiftDocCPluginUtilities/Arguments.swift | 1 + .../xctest-dynamic-overlay/Package.resolved | 16 ++ .../xctest-dynamic-overlay/Package.swift | 26 +++ .../XCTestDynamicOverlay/XCTFail.swift | 1 + .../Example/.build/workspace-state.json | 145 +++++++++++++ .../projects/Example/Package.resolved | 76 +++++++ .../fixtures/projects/Example/Package.swift | 23 ++ 58 files changed, 1767 insertions(+), 2 deletions(-) create mode 100644 swift/lib/dependabot/swift/file_parser/dependency_parser.rb create mode 100644 swift/lib/dependabot/swift/file_parser/manifest_parser.rb create mode 100644 swift/spec/dependabot/swift/file_parser_spec.rb create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Package.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Sources/Nimble/DSL.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Package.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Sources/Quick/QuickMain.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Package.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Sources/ArgumentParser/Parsing/Name.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Sources/ArgumentParserToolInfo/ToolInfo.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Package.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Sources/Benchmark/Benchmark.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package@swift-5.1.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Sources/CasePaths/CasePaths.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Package.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Sources/CustomDump/Dump.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-docc-plugin/Package.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-docc-plugin/Sources/SwiftDocCPluginUtilities/HelpInformation.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Package.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Sources/XCTestDynamicOverlay/XCTFail.swift create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/.build/workspace-state.json create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example-Deprecated/Package.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Package.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Sources/Reactive.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Package.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Sources/CombineSchedulers/SwiftUI.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Package.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Sources/ArgumentParser/Parsing/Name.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Sources/ArgumentParserToolInfo/ToolInfo.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Package.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Sources/Benchmark/Benchmark.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package@swift-5.1.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Sources/CasePaths/CasePath.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Package.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Sources/CustomDump/Dump.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-docc-plugin/Package.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/swift-docc-plugin/Sources/SwiftDocCPluginUtilities/Arguments.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Package.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Sources/XCTestDynamicOverlay/XCTFail.swift create mode 100644 swift/spec/fixtures/projects/Example/.build/workspace-state.json create mode 100644 swift/spec/fixtures/projects/Example/Package.resolved create mode 100644 swift/spec/fixtures/projects/Example/Package.swift diff --git a/swift/lib/dependabot/swift/file_parser.rb b/swift/lib/dependabot/swift/file_parser.rb index da78439d5c2..53ab668502c 100644 --- a/swift/lib/dependabot/swift/file_parser.rb +++ b/swift/lib/dependabot/swift/file_parser.rb @@ -1,19 +1,56 @@ # frozen_string_literal: true +require "dependabot/dependency" require "dependabot/file_parsers" require "dependabot/file_parsers/base" +require "dependabot/swift/file_parser/dependency_parser" +require "dependabot/swift/file_parser/manifest_parser" module Dependabot module Swift class FileParser < Dependabot::FileParsers::Base + require "dependabot/file_parsers/base/dependency_set" + def parse - raise NotImplementedError + dependency_set = DependencySet.new + + dependency_parser.parse.map do |dep| + if dep.top_level? + source = dep.requirements.first[:source] + + requirements = ManifestParser.new(package_manifest_file, source: source).requirements + + dependency_set << Dependency.new( + name: dep.name, + version: dep.version, + package_manager: dep.package_manager, + requirements: requirements + ) + else + dependency_set << dep + end + end + + dependency_set.dependencies end private + def dependency_parser + DependencyParser.new( + dependency_files: dependency_files, + repo_contents_path: repo_contents_path, + credentials: credentials + ) + end + def check_required_files - raise NotImplementedError + raise "No Package.swift!" unless package_manifest_file + end + + def package_manifest_file + # TODO: Select version-specific manifest + @package_manifest_file ||= get_original_file("Package.swift") end end end diff --git a/swift/lib/dependabot/swift/file_parser/dependency_parser.rb b/swift/lib/dependabot/swift/file_parser/dependency_parser.rb new file mode 100644 index 00000000000..1dd6a28ccc8 --- /dev/null +++ b/swift/lib/dependabot/swift/file_parser/dependency_parser.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "dependabot/file_parsers/base" +require "dependabot/shared_helpers" +require "dependabot/dependency" +require "json" + +module Dependabot + module Swift + class FileParser < Dependabot::FileParsers::Base + class DependencyParser + def initialize(dependency_files:, repo_contents_path:, credentials:) + @dependency_files = dependency_files + @repo_contents_path = repo_contents_path + @credentials = credentials + end + + def parse + SharedHelpers.in_a_temporary_repo_directory(dependency_files.first.directory, repo_contents_path) do + write_temporary_dependency_files + + SharedHelpers.with_git_configured(credentials: credentials) do + subdependencies(formatted_deps) + end + end + end + + private + + def write_temporary_dependency_files + dependency_files.each do |file| + File.write(file.name, file.content) + end + end + + def formatted_deps + deps = SharedHelpers.run_shell_command( + "swift package show-dependencies --format json", + stderr_to_stdout: false + ) + + JSON.parse(deps) + end + + def subdependencies(data, level: 0) + data["dependencies"].flat_map { |root| all_dependencies(root, level: level) } + end + + def all_dependencies(data, level: 0) + name = data["identity"] + url = data["url"] + version = data["version"] + + source = { type: "git", url: url, ref: version, branch: nil } + args = { name: name, version: version, package_manager: "swift", requirements: [] } + + if level.zero? + args[:requirements] << { requirement: nil, groups: ["dependencies"], file: nil, source: source } + else + args[:subdependency_metadata] = [{ source: source }] + end + + dep = Dependency.new(**args) if data["version"] != "unspecified" + + [dep, *subdependencies(data, level: level + 1)].compact + end + + attr_reader :dependency_files, :repo_contents_path, :credentials + end + end + end +end diff --git a/swift/lib/dependabot/swift/file_parser/manifest_parser.rb b/swift/lib/dependabot/swift/file_parser/manifest_parser.rb new file mode 100644 index 00000000000..b8703284d66 --- /dev/null +++ b/swift/lib/dependabot/swift/file_parser/manifest_parser.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "dependabot/file_parsers/base" +require "dependabot/swift/native_requirement" + +module Dependabot + module Swift + class FileParser < Dependabot::FileParsers::Base + class ManifestParser + DEPENDENCY = /(?\.package\(\s*(?:name: "[^"]+",\s*)?url: "(?[^"]+)",\s*(?.*)\))/ + + def initialize(manifest, source:) + @manifest = manifest + @source = source + end + + def requirements + found = manifest.content.scan(DEPENDENCY).find do |_declaration, url, requirement| + # TODO: Support pinning to specific revisions + next if requirement.start_with?("branch:", ".branch(", "revision:", ".revision(") + + url == source[:url] + end + + return [] unless found + + declaration = found.first + requirement = NativeRequirement.new(found.last) + + [ + { + requirement: requirement.to_s, + groups: ["dependencies"], + file: manifest.name, + source: source, + metadata: { declaration_string: declaration, requirement_string: requirement.declaration } + } + ] + end + + private + + attr_reader :manifest, :source + end + end + end +end diff --git a/swift/spec/dependabot/swift/file_parser_spec.rb b/swift/spec/dependabot/swift/file_parser_spec.rb new file mode 100644 index 00000000000..0299f5af2c3 --- /dev/null +++ b/swift/spec/dependabot/swift/file_parser_spec.rb @@ -0,0 +1,199 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/dependency_file" +require "dependabot/source" +require "dependabot/swift/file_parser" +require_common_spec "file_parsers/shared_examples_for_file_parsers" + +RSpec.describe Dependabot::Swift::FileParser do + it_behaves_like "a dependency file parser" + + let(:parser) do + described_class.new(dependency_files: files, source: source, repo_contents_path: repo_contents_path) + end + + let(:source) do + Dependabot::Source.new( + provider: "github", + repo: "mona/Example", + directory: "/" + ) + end + + let(:files) do + [ + package_manifest_file, + package_resolved_file + ] + end + + let(:repo_contents_path) { build_tmp_repo(project_name, path: "projects") } + + let(:package_manifest_file) do + Dependabot::DependencyFile.new( + name: "Package.swift", + content: fixture("projects", project_name, "Package.swift") + ) + end + + let(:package_resolved_file) do + Dependabot::DependencyFile.new( + name: "Package.resolved", + content: fixture("projects", project_name, "Package.resolved") + ) + end + + let(:dependencies) { parser.parse } + + shared_examples_for "parse" do + it "parses dependencies fine" do + expectations.each.with_index do |expected, index| + url = expected[:url] + version = expected[:version] + name = expected[:name] + source = { type: "git", url: url, ref: version, branch: nil } + + dependency = dependencies[index] + + expect(dependency).to be_a(Dependabot::Dependency) + expect(dependency.name).to eq(name) + expect(dependency.version).to eq(version) + + if expected[:requirement] + expect(dependency.requirements).to eq([ + { + requirement: expected[:requirement], + groups: ["dependencies"], + file: "Package.swift", + source: source, + metadata: { + declaration_string: expected[:declaration_string], + requirement_string: expected[:requirement_string] + } + } + ]) + else # subdependency + expect(dependency.subdependency_metadata).to eq([ + { + source: source + } + ]) + end + end + end + end + + context "with supported declarations" do + let(:project_name) { "Example" } + + let(:expectations) do + [ + { + name: "reactiveswift", + url: "https://github.com/ReactiveCocoa/ReactiveSwift.git", + version: "7.1.0", + requirement: "= 7.1.0", + declaration_string: + ".package(url: \"https://github.com/ReactiveCocoa/ReactiveSwift.git\",\n exact: \"7.1.0\")", + requirement_string: "exact: \"7.1.0\"" + }, + { + name: "swift-docc-plugin", + url: "https://github.com/apple/swift-docc-plugin", + version: "1.0.0", + requirement: ">= 1.0.0, < 2.0.0", + declaration_string: + ".package(\n url: \"https://github.com/apple/swift-docc-plugin\",\n from: \"1.0.0\")", + requirement_string: "from: \"1.0.0\"" + }, + { + name: "swift-benchmark", + url: "https://github.com/google/swift-benchmark", + version: "0.1.1", + requirement: ">= 0.1.0, < 0.1.2", + declaration_string: ".package(url: \"https://github.com/google/swift-benchmark\", \"0.1.0\"..<\"0.1.2\")", + requirement_string: "\"0.1.0\"..<\"0.1.2\"" + }, + { + name: "swift-argument-parser", + url: "https://github.com/apple/swift-argument-parser", + version: "0.5.0" + }, + { + name: "combine-schedulers", + url: "https://github.com/pointfreeco/combine-schedulers", + version: "0.10.0", + requirement: ">= 0.9.2, <= 0.10.0", + declaration_string: + ".package(url: \"https://github.com/pointfreeco/combine-schedulers\", \"0.9.2\"...\"0.10.0\")", + requirement_string: "\"0.9.2\"...\"0.10.0\"" + }, + { + name: "xctest-dynamic-overlay", + url: "https://github.com/pointfreeco/xctest-dynamic-overlay", + version: "0.8.5" + } + ] + end + + it_behaves_like "parse" + end + + context "with deprecated declarations" do + let(:project_name) { "Example-Deprecated" } + + let(:expectations) do + [ + { + name: "quick", + url: "https://github.com/Quick/Quick.git", + version: "7.0.2", + requirement: ">= 7.0.0, < 8.0.0", + declaration_string: + ".package(url: \"https://github.com/Quick/Quick.git\",\n .upToNextMajor(from: \"7.0.0\"))", + requirement_string: ".upToNextMajor(from: \"7.0.0\")" + }, + { + name: "nimble", + url: "https://github.com/Quick/Nimble.git", + version: "9.0.1", + requirement: ">= 9.0.0, < 9.1.0", + declaration_string: + ".package(url: \"https://github.com/Quick/Nimble.git\",\n .upToNextMinor(from: \"9.0.0\"))", + requirement_string: ".upToNextMinor(from: \"9.0.0\")" + }, + { + name: "swift-docc-plugin", + url: "https://github.com/apple/swift-docc-plugin", + version: "1.0.0", + requirement: "= 1.0.0", + declaration_string: + ".package(\n url: \"https://github.com/apple/swift-docc-plugin\",\n .exact(\"1.0.0\"))", + requirement_string: ".exact(\"1.0.0\")" + }, + { + name: "swift-benchmark", + url: "https://github.com/google/swift-benchmark", + version: "0.1.1", + requirement: ">= 0.1.0, < 0.1.2", + declaration_string: + ".package(name: \"foo\", url: \"https://github.com/google/swift-benchmark\", \"0.1.0\"..<\"0.1.2\")", + requirement_string: "\"0.1.0\"..<\"0.1.2\"" + }, + { + name: "swift-argument-parser", + url: "https://github.com/apple/swift-argument-parser", + version: "0.5.0" + }, + { + name: "xctest-dynamic-overlay", + url: "https://github.com/pointfreeco/xctest-dynamic-overlay", + version: "0.8.5" + } + ] + end + + it_behaves_like "parse" + end +end diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Package.resolved b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Package.resolved new file mode 100644 index 00000000000..e79774ff50e --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "CwlCatchException", + "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", + "state": { + "branch": null, + "revision": "f809deb30dc5c9d9b78c872e553261a61177721a", + "version": "2.0.0" + } + }, + { + "package": "CwlPreconditionTesting", + "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state": { + "branch": null, + "revision": "02b7a39a99c4da27abe03cab2053a9034379639f", + "version": "2.0.0" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Package.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Package.swift new file mode 100644 index 00000000000..105cc93fd9e --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Package.swift @@ -0,0 +1,21 @@ +// swift-tools-version:5.2 +import PackageDescription + +let package = Package( + name: "Nimble", + platforms: [ + .macOS(.v10_10), .iOS(.v9), .tvOS(.v9) + ], + products: [ + .library(name: "Nimble", targets: ["Nimble"]), + ], + dependencies: [ + .package(url: "https://github.com/mattgallagher/CwlPreconditionTesting.git", .upToNextMajor(from: "2.0.0")), + ], + targets: [ + .target( + name: "Nimble" + ) + ], + swiftLanguageVersions: [.v5] +) diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Sources/Nimble/DSL.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Sources/Nimble/DSL.swift new file mode 100644 index 00000000000..3ea5f9e7517 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Nimble/Sources/Nimble/DSL.swift @@ -0,0 +1 @@ +/// Placeholder diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Package.resolved b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Package.resolved new file mode 100644 index 00000000000..7178b7aa1b5 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Package.resolved @@ -0,0 +1,32 @@ +{ + "pins" : [ + { + "identity" : "cwlcatchexception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlCatchException.git", + "state" : { + "revision" : "f809deb30dc5c9d9b78c872e553261a61177721a", + "version" : "2.0.0" + } + }, + { + "identity" : "cwlpreconditiontesting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state" : { + "revision" : "c21f7bab5ca8eee0a9998bbd17ca1d0eb45d4688", + "version" : "2.1.0" + } + }, + { + "identity" : "nimble", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Nimble.git", + "state" : { + "revision" : "ecade0f20e58e55ba3e5f110b701dad88fd40170", + "version" : "12.0.0" + } + } + ], + "version" : 2 +} diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Package.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Package.swift new file mode 100644 index 00000000000..d18103cc558 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Package.swift @@ -0,0 +1,64 @@ +// swift-tools-version:5.7 + +import PackageDescription + +let package = Package( + name: "Quick", + platforms: [ + .macOS(.v10_15), .iOS(.v13), .tvOS(.v13) + ], + products: [ + .library(name: "Quick", targets: ["Quick"]), + ], + dependencies: [ + .package(url: "https://github.com/Quick/Nimble.git", from: "12.0.0"), + ], + targets: { + var targets: [Target] = [ + .testTarget( + name: "QuickTests", + dependencies: [ "Quick", "Nimble" ], + exclude: [ + "QuickAfterSuiteTests/AfterSuiteTests+ObjC.m", + "QuickFocusedTests/FocusedTests+ObjC.m", + "QuickTests/FunctionalTests/ObjC", + "QuickTests/Helpers/QCKSpecRunner.h", + "QuickTests/Helpers/QCKSpecRunner.m", + "QuickTests/Helpers/QuickTestsBridgingHeader.h", + "QuickTests/QuickConfigurationTests.m", + "QuickFocusedTests/Info.plist", + "QuickTests/Info.plist", + "QuickAfterSuiteTests/Info.plist", + ] + ), + .testTarget( + name: "QuickIssue853RegressionTests", + dependencies: [ "Quick" ] + ), + ] +#if os(macOS) + targets.append(contentsOf: [ + .target(name: "QuickObjCRuntime", dependencies: []), + .target( + name: "Quick", + dependencies: [ "QuickObjCRuntime" ], + exclude: [ + "Info.plist", + ] + ), + ]) +#else + targets.append(contentsOf: [ + .target( + name: "Quick", + dependencies: [], + exclude: [ + "Info.plist" + ] + ), + ]) +#endif + return targets + }(), + swiftLanguageVersions: [.v5] +) diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Sources/Quick/QuickMain.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Sources/Quick/QuickMain.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/Quick/Sources/Quick/QuickMain.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Package.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Package.swift new file mode 100644 index 00000000000..e1b21874cb1 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Package.swift @@ -0,0 +1,70 @@ +// swift-tools-version:5.2 +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import PackageDescription + +var package = Package( + name: "swift-argument-parser", + products: [ + .library( + name: "ArgumentParser", + targets: ["ArgumentParser"]), + ], + dependencies: [], + targets: [ + .target( + name: "ArgumentParser", + dependencies: ["ArgumentParserToolInfo"]), + .target( + name: "ArgumentParserTestHelpers", + dependencies: ["ArgumentParser", "ArgumentParserToolInfo"]), + .target( + name: "ArgumentParserToolInfo", + dependencies: []), + + .target( + name: "roll", + dependencies: ["ArgumentParser"], + path: "Examples/roll"), + .target( + name: "math", + dependencies: ["ArgumentParser"], + path: "Examples/math"), + .target( + name: "repeat", + dependencies: ["ArgumentParser"], + path: "Examples/repeat"), + + .target( + name: "changelog-authors", + dependencies: ["ArgumentParser"], + path: "Tools/changelog-authors"), + + .testTarget( + name: "ArgumentParserEndToEndTests", + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"]), + .testTarget( + name: "ArgumentParserUnitTests", + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"]), + .testTarget( + name: "ArgumentParserExampleTests", + dependencies: ["ArgumentParserTestHelpers"]), + ] +) + +#if swift(>=5.2) +// Skip if < 5.2 to avoid issue with nested type synthesized 'CodingKeys' +package.targets.append( + .testTarget( + name: "ArgumentParserPackageManagerTests", + dependencies: ["ArgumentParser", "ArgumentParserTestHelpers"])) +#endif diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Sources/ArgumentParser/Parsing/Name.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Sources/ArgumentParser/Parsing/Name.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Sources/ArgumentParser/Parsing/Name.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Sources/ArgumentParserToolInfo/ToolInfo.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Sources/ArgumentParserToolInfo/ToolInfo.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-argument-parser/Sources/ArgumentParserToolInfo/ToolInfo.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Package.resolved b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Package.resolved new file mode 100644 index 00000000000..4520bbb27f0 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", + "version": "0.5.0" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Package.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Package.swift new file mode 100644 index 00000000000..fa8bd50e5de --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Package.swift @@ -0,0 +1,46 @@ +// swift-tools-version:5.1 + +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import PackageDescription + +let package = Package( + name: "Benchmark", + products: [ + .library( + name: "Benchmark", + targets: ["Benchmark"]) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-argument-parser", from: "0.5.0"), + ], + targets: [ + .target( + name: "Benchmark", + dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")]), + .target( + name: "BenchmarkMinimalExample", + dependencies: ["Benchmark"]), + .target( + name: "BenchmarkSuiteExample", + dependencies: ["Benchmark"]), + .target( + name: "BenchmarkSuiteExampleMain", + dependencies: ["Benchmark", "BenchmarkSuiteExample"]), + .testTarget( + name: "BenchmarkTests", + dependencies: ["Benchmark", "BenchmarkSuiteExample"]), + ] +) diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Sources/Benchmark/Benchmark.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Sources/Benchmark/Benchmark.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-benchmark/Sources/Benchmark/Benchmark.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package.resolved b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package.resolved new file mode 100644 index 00000000000..6a606789d35 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package.resolved @@ -0,0 +1,43 @@ +{ + "object": { + "pins": [ + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "986d191f94cec88f6350056da59c2e59e83d1229", + "version": "0.4.3" + } + }, + { + "package": "Benchmark", + "repositoryURL": "https://github.com/google/swift-benchmark", + "state": { + "branch": null, + "revision": "8e0ef8bb7482ab97dcd2cd1d6855bd38921c345d", + "version": "0.1.0" + } + }, + { + "package": "SwiftDocCPlugin", + "repositoryURL": "https://github.com/apple/swift-docc-plugin", + "state": { + "branch": null, + "revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "version": "1.0.0" + } + }, + { + "package": "xctest-dynamic-overlay", + "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay.git", + "state": { + "branch": null, + "revision": "a9daebf0bf65981fd159c885d504481a65a75f02", + "version": "0.8.0" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package.swift new file mode 100644 index 00000000000..5c0a7fecf90 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package.swift @@ -0,0 +1,49 @@ +// swift-tools-version:5.5 + +import PackageDescription + +let package = Package( + name: "swift-case-paths", + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), + ], + products: [ + .library( + name: "CasePaths", + targets: ["CasePaths"] + ) + ], + dependencies: [ + .package(name: "Benchmark", url: "https://github.com/google/swift-benchmark", from: "0.1.0"), + .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.8.0"), + ], + targets: [ + .target( + name: "CasePaths", + dependencies: [ + .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") + ] + ), + .testTarget( + name: "CasePathsTests", + dependencies: ["CasePaths"] + ), + .executableTarget( + name: "swift-case-paths-benchmark", + dependencies: [ + "CasePaths", + .product(name: "Benchmark", package: "Benchmark"), + ] + ), + ] +) + +#if swift(>=5.6) + // Add the documentation compiler plugin if possible + package.dependencies.append( + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") + ) +#endif diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package@swift-5.1.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package@swift-5.1.swift new file mode 100644 index 00000000000..996ce04f8c4 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Package@swift-5.1.swift @@ -0,0 +1,22 @@ +// swift-tools-version:5.1 + +import PackageDescription + +let package = Package( + name: "swift-case-paths", + products: [ + .library( + name: "CasePaths", + targets: ["CasePaths"] + ) + ], + targets: [ + .target( + name: "CasePaths" + ), + .testTarget( + name: "CasePathsTests", + dependencies: ["CasePaths"] + ), + ] +) diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Sources/CasePaths/CasePaths.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Sources/CasePaths/CasePaths.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-case-paths/Sources/CasePaths/CasePaths.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Package.resolved b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Package.resolved new file mode 100644 index 00000000000..76b883171b2 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "xctest-dynamic-overlay", + "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state": { + "branch": null, + "revision": "30314f1ece684dd60679d598a9b89107557b67d9", + "version": "0.4.1" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Package.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Package.swift new file mode 100644 index 00000000000..b1aa1b2555d --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version:5.1 + +import PackageDescription + +let package = Package( + name: "swift-custom-dump", + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), + ], + products: [ + .library( + name: "CustomDump", + targets: ["CustomDump"] + ) + ], + dependencies: [ + .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.2.0") + ], + targets: [ + .target( + name: "CustomDump", + dependencies: [ + .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") + ] + ), + .testTarget( + name: "CustomDumpTests", + dependencies: [ + "CustomDump" + ] + ), + ] +) diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Sources/CustomDump/Dump.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Sources/CustomDump/Dump.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-custom-dump/Sources/CustomDump/Dump.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-docc-plugin/Package.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-docc-plugin/Package.swift new file mode 100644 index 00000000000..551d02074d9 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-docc-plugin/Package.swift @@ -0,0 +1,21 @@ +// swift-tools-version:5.6 +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors + +import PackageDescription + +let package = Package( + name: "SwiftDocCPlugin", + platforms: [ + .macOS("10.15.4"), + ], + targets: [ + .target(name: "SwiftDocCPluginUtilities") + ] +) diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-docc-plugin/Sources/SwiftDocCPluginUtilities/HelpInformation.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-docc-plugin/Sources/SwiftDocCPluginUtilities/HelpInformation.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/swift-docc-plugin/Sources/SwiftDocCPluginUtilities/HelpInformation.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Package.resolved b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Package.resolved new file mode 100644 index 00000000000..3b6d58b80c8 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "SwiftDocCPlugin", + "repositoryURL": "https://github.com/apple/swift-docc-plugin", + "state": { + "branch": null, + "revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "version": "1.0.0" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Package.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Package.swift new file mode 100644 index 00000000000..33a9349d2f6 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.5 + +import PackageDescription + +let package = Package( + name: "xctest-dynamic-overlay", + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), + ], + products: [ + .library(name: "XCTestDynamicOverlay", targets: ["XCTestDynamicOverlay"]) + ], + targets: [ + .target(name: "XCTestDynamicOverlay"), + .testTarget( + name: "XCTestDynamicOverlayTests", + dependencies: ["XCTestDynamicOverlay"] + ), + ] +) + +#if swift(>=5.6) + // Add the documentation compiler plugin if possible + package.dependencies.append( + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") + ) +#endif diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Sources/XCTestDynamicOverlay/XCTFail.swift b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Sources/XCTestDynamicOverlay/XCTFail.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/checkouts/xctest-dynamic-overlay/Sources/XCTestDynamicOverlay/XCTFail.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example-Deprecated/.build/workspace-state.json b/swift/spec/fixtures/projects/Example-Deprecated/.build/workspace-state.json new file mode 100644 index 00000000000..b1f7de0beee --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/.build/workspace-state.json @@ -0,0 +1,145 @@ +{ + "object" : { + "artifacts" : [ + + ], + "dependencies" : [ + { + "basedOn" : null, + "packageRef" : { + "identity" : "nimble", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Nimble.git", + "name" : "Nimble" + }, + "state" : { + "checkoutState" : { + "revision" : "7a54aaf19a8ef16f67787c260fda81ead7ba4d67", + "version" : "9.0.1" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "Nimble" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "quick", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Quick.git", + "name" : "Quick" + }, + "state" : { + "checkoutState" : { + "revision" : "91132c0fe9a98e76f3d7381a608685aa41770706", + "version" : "7.0.2" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "Quick" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "name" : "swift-argument-parser" + }, + "state" : { + "checkoutState" : { + "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", + "version" : "0.5.0" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "swift-argument-parser" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "swift-benchmark", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/swift-benchmark", + "name" : "Benchmark" + }, + "state" : { + "checkoutState" : { + "revision" : "a0564bf88df5f94eec81348a2f089494c6b28d80", + "version" : "0.1.1" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "swift-benchmark" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "name" : "swift-case-paths" + }, + "state" : { + "checkoutState" : { + "branch" : "main", + "revision" : "fc45e7b2cfece9dd80b5a45e6469ffe67fe67984" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "swift-case-paths" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com:443/pointfreeco/swift-custom-dump", + "name" : "swift-custom-dump" + }, + "state" : { + "checkoutState" : { + "revision" : "3a35f7892e7cf6ba28a78cd46a703c0be4e0c6dc" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "swift-custom-dump" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "name" : "SwiftDocCPlugin" + }, + "state" : { + "checkoutState" : { + "revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "version" : "1.0.0" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "swift-docc-plugin" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "name" : "xctest-dynamic-overlay" + }, + "state" : { + "checkoutState" : { + "revision" : "4af50b38daf0037cfbab15514a241224c3f62f98", + "version" : "0.8.5" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "xctest-dynamic-overlay" + } + ] + }, + "version" : 6 +} \ No newline at end of file diff --git a/swift/spec/fixtures/projects/Example-Deprecated/Package.resolved b/swift/spec/fixtures/projects/Example-Deprecated/Package.resolved new file mode 100644 index 00000000000..ae7a888e973 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/Package.resolved @@ -0,0 +1,76 @@ +{ + "pins" : [ + { + "identity" : "nimble", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Nimble.git", + "state" : { + "revision" : "7a54aaf19a8ef16f67787c260fda81ead7ba4d67", + "version" : "9.0.1" + } + }, + { + "identity" : "quick", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Quick.git", + "state" : { + "revision" : "91132c0fe9a98e76f3d7381a608685aa41770706", + "version" : "7.0.2" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", + "version" : "0.5.0" + } + }, + { + "identity" : "swift-benchmark", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/swift-benchmark", + "state" : { + "revision" : "a0564bf88df5f94eec81348a2f089494c6b28d80", + "version" : "0.1.1" + } + }, + { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "branch" : "main", + "revision" : "fc45e7b2cfece9dd80b5a45e6469ffe67fe67984" + } + }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com:443/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "3a35f7892e7cf6ba28a78cd46a703c0be4e0c6dc" + } + }, + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "state" : { + "revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "version" : "1.0.0" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "4af50b38daf0037cfbab15514a241224c3f62f98", + "version" : "0.8.5" + } + } + ], + "version" : 2 +} diff --git a/swift/spec/fixtures/projects/Example-Deprecated/Package.swift b/swift/spec/fixtures/projects/Example-Deprecated/Package.swift new file mode 100644 index 00000000000..44a79015eb6 --- /dev/null +++ b/swift/spec/fixtures/projects/Example-Deprecated/Package.swift @@ -0,0 +1,25 @@ +// swift-tools-version:5.6 +import PackageDescription + +let package = Package( + name: "Example", + dependencies: [ + .package(url: "https://github.com/Quick/Quick.git", + .upToNextMajor(from: "7.0.0")), + + .package(url: "https://github.com/Quick/Nimble.git", + .upToNextMinor(from: "9.0.0")), + + .package( + url: "https://github.com/apple/swift-docc-plugin", + .exact("1.0.0")), + + .package(name: "foo", url: "https://github.com/google/swift-benchmark", "0.1.0"..<"0.1.2"), + + .package(url: "https://github.com/pointfreeco/swift-case-paths", + .branch("main")), + + .package(url: "https://github.com:443/pointfreeco/swift-custom-dump", + .revision("3a35f7892e7cf6ba28a78cd46a703c0be4e0c6dc")) + ] +) diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Package.resolved b/swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Package.resolved new file mode 100644 index 00000000000..f99c9004623 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Package.resolved @@ -0,0 +1,43 @@ +{ + "object": { + "pins": [ + { + "package": "CwlCatchException", + "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", + "state": { + "branch": null, + "revision": "682841464136f8c66e04afe5dbd01ab51a3a56f2", + "version": "2.1.0" + } + }, + { + "package": "CwlPreconditionTesting", + "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state": { + "branch": null, + "revision": "02b7a39a99c4da27abe03cab2053a9034379639f", + "version": "2.0.0" + } + }, + { + "package": "Nimble", + "repositoryURL": "https://github.com/Quick/Nimble.git", + "state": { + "branch": null, + "revision": "af1730dde4e6c0d45bf01b99f8a41713ce536790", + "version": "9.2.0" + } + }, + { + "package": "Quick", + "repositoryURL": "https://github.com/Quick/Quick.git", + "state": { + "branch": null, + "revision": "bd86ca0141e3cfb333546de5a11ede63f0c4a0e6", + "version": "4.0.0" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Package.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Package.swift new file mode 100644 index 00000000000..ba9124b09e3 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Package.swift @@ -0,0 +1,17 @@ +// swift-tools-version:5.2 +import PackageDescription + +let package = Package( + name: "ReactiveSwift", + platforms: [ + .macOS(.v10_13), .iOS(.v11), .tvOS(.v11), .watchOS(.v4) + ], + dependencies: [ + .package(url: "https://github.com/Quick/Quick.git", from: "4.0.0"), + .package(url: "https://github.com/Quick/Nimble.git", from: "9.0.0"), + ], + targets: [ + .target(name: "ReactiveSwift", dependencies: [], path: "Sources"), + ], + swiftLanguageVersions: [.v5] +) diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Sources/Reactive.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Sources/Reactive.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/ReactiveSwift/Sources/Reactive.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Package.resolved b/swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Package.resolved new file mode 100644 index 00000000000..0e45736d216 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "xctest-dynamic-overlay", + "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state": { + "branch": null, + "revision": "f821dcbac7cb6913f8e0d1a80496d0ba0199fa81", + "version": "0.3.0" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Package.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Package.swift new file mode 100644 index 00000000000..8102fa61149 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.5 + +import PackageDescription + +let package = Package( + name: "combine-schedulers", + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), + ], + products: [ + .library( + name: "CombineSchedulers", + targets: ["CombineSchedulers"] + ) + ], + dependencies: [ + .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.3.0") + ], + targets: [ + .target( + name: "CombineSchedulers", + dependencies: [ + .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") + ] + ) + ] +) diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Sources/CombineSchedulers/SwiftUI.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Sources/CombineSchedulers/SwiftUI.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/combine-schedulers/Sources/CombineSchedulers/SwiftUI.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Package.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Package.swift new file mode 100644 index 00000000000..2956b10b848 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Package.swift @@ -0,0 +1,31 @@ +// swift-tools-version:5.2 +//===----------------------------------------------------------*- swift -*-===// +// +// This source file is part of the Swift Argument Parser open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import PackageDescription + +var package = Package( + name: "swift-argument-parser", + products: [ + .library( + name: "ArgumentParser", + targets: ["ArgumentParser"]), + ], + dependencies: [], + targets: [ + .target( + name: "ArgumentParser", + dependencies: ["ArgumentParserToolInfo"]), + .target( + name: "ArgumentParserToolInfo", + dependencies: []) + ] +) diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Sources/ArgumentParser/Parsing/Name.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Sources/ArgumentParser/Parsing/Name.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Sources/ArgumentParser/Parsing/Name.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Sources/ArgumentParserToolInfo/ToolInfo.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Sources/ArgumentParserToolInfo/ToolInfo.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-argument-parser/Sources/ArgumentParserToolInfo/ToolInfo.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Package.resolved b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Package.resolved new file mode 100644 index 00000000000..4520bbb27f0 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", + "version": "0.5.0" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Package.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Package.swift new file mode 100644 index 00000000000..dbcadfe1e9a --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.1 + +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import PackageDescription + +let package = Package( + name: "Benchmark", + products: [ + .library( + name: "Benchmark", + targets: ["Benchmark"]) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-argument-parser", from: "0.5.0"), + ], + targets: [ + .target( + name: "Benchmark", + dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")]) + ] +) diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Sources/Benchmark/Benchmark.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Sources/Benchmark/Benchmark.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-benchmark/Sources/Benchmark/Benchmark.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package.resolved b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package.resolved new file mode 100644 index 00000000000..6a606789d35 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package.resolved @@ -0,0 +1,43 @@ +{ + "object": { + "pins": [ + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "986d191f94cec88f6350056da59c2e59e83d1229", + "version": "0.4.3" + } + }, + { + "package": "Benchmark", + "repositoryURL": "https://github.com/google/swift-benchmark", + "state": { + "branch": null, + "revision": "8e0ef8bb7482ab97dcd2cd1d6855bd38921c345d", + "version": "0.1.0" + } + }, + { + "package": "SwiftDocCPlugin", + "repositoryURL": "https://github.com/apple/swift-docc-plugin", + "state": { + "branch": null, + "revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "version": "1.0.0" + } + }, + { + "package": "xctest-dynamic-overlay", + "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay.git", + "state": { + "branch": null, + "revision": "a9daebf0bf65981fd159c885d504481a65a75f02", + "version": "0.8.0" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package.swift new file mode 100644 index 00000000000..fd4d99412b7 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package.swift @@ -0,0 +1,38 @@ +// swift-tools-version:5.5 + +import PackageDescription + +let package = Package( + name: "swift-case-paths", + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), + ], + products: [ + .library( + name: "CasePaths", + targets: ["CasePaths"] + ) + ], + dependencies: [ + .package(name: "Benchmark", url: "https://github.com/google/swift-benchmark", from: "0.1.0"), + .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.8.0"), + ], + targets: [ + .target( + name: "CasePaths", + dependencies: [ + .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") + ] + ) + ] +) + +#if swift(>=5.6) + // Add the documentation compiler plugin if possible + package.dependencies.append( + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") + ) +#endif diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package@swift-5.1.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package@swift-5.1.swift new file mode 100644 index 00000000000..c0b6ebd4c9b --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Package@swift-5.1.swift @@ -0,0 +1,18 @@ +// swift-tools-version:5.1 + +import PackageDescription + +let package = Package( + name: "swift-case-paths", + products: [ + .library( + name: "CasePaths", + targets: ["CasePaths"] + ) + ], + targets: [ + .target( + name: "CasePaths" + ) + ] +) diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Sources/CasePaths/CasePath.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Sources/CasePaths/CasePath.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-case-paths/Sources/CasePaths/CasePath.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Package.resolved b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Package.resolved new file mode 100644 index 00000000000..76b883171b2 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "xctest-dynamic-overlay", + "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state": { + "branch": null, + "revision": "30314f1ece684dd60679d598a9b89107557b67d9", + "version": "0.4.1" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Package.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Package.swift new file mode 100644 index 00000000000..96553f3bfc2 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.1 + +import PackageDescription + +let package = Package( + name: "swift-custom-dump", + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), + ], + products: [ + .library( + name: "CustomDump", + targets: ["CustomDump"] + ) + ], + dependencies: [ + .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "0.2.0") + ], + targets: [ + .target( + name: "CustomDump", + dependencies: [ + .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay") + ] + ) + ] +) diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Sources/CustomDump/Dump.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Sources/CustomDump/Dump.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-custom-dump/Sources/CustomDump/Dump.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-docc-plugin/Package.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-docc-plugin/Package.swift new file mode 100644 index 00000000000..551d02074d9 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-docc-plugin/Package.swift @@ -0,0 +1,21 @@ +// swift-tools-version:5.6 +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors + +import PackageDescription + +let package = Package( + name: "SwiftDocCPlugin", + platforms: [ + .macOS("10.15.4"), + ], + targets: [ + .target(name: "SwiftDocCPluginUtilities") + ] +) diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/swift-docc-plugin/Sources/SwiftDocCPluginUtilities/Arguments.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-docc-plugin/Sources/SwiftDocCPluginUtilities/Arguments.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/swift-docc-plugin/Sources/SwiftDocCPluginUtilities/Arguments.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Package.resolved b/swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Package.resolved new file mode 100644 index 00000000000..3b6d58b80c8 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Package.resolved @@ -0,0 +1,16 @@ +{ + "object": { + "pins": [ + { + "package": "SwiftDocCPlugin", + "repositoryURL": "https://github.com/apple/swift-docc-plugin", + "state": { + "branch": null, + "revision": "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "version": "1.0.0" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Package.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Package.swift new file mode 100644 index 00000000000..53d59e647fe --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Package.swift @@ -0,0 +1,26 @@ +// swift-tools-version:5.5 + +import PackageDescription + +let package = Package( + name: "xctest-dynamic-overlay", + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), + ], + products: [ + .library(name: "XCTestDynamicOverlay", targets: ["XCTestDynamicOverlay"]) + ], + targets: [ + .target(name: "XCTestDynamicOverlay"), + ] +) + +#if swift(>=5.6) + // Add the documentation compiler plugin if possible + package.dependencies.append( + .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0") + ) +#endif diff --git a/swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Sources/XCTestDynamicOverlay/XCTFail.swift b/swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Sources/XCTestDynamicOverlay/XCTFail.swift new file mode 100644 index 00000000000..10051c76805 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/checkouts/xctest-dynamic-overlay/Sources/XCTestDynamicOverlay/XCTFail.swift @@ -0,0 +1 @@ +// Placeholder diff --git a/swift/spec/fixtures/projects/Example/.build/workspace-state.json b/swift/spec/fixtures/projects/Example/.build/workspace-state.json new file mode 100644 index 00000000000..4110a5304b0 --- /dev/null +++ b/swift/spec/fixtures/projects/Example/.build/workspace-state.json @@ -0,0 +1,145 @@ +{ + "object" : { + "artifacts" : [ + + ], + "dependencies" : [ + { + "basedOn" : null, + "packageRef" : { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "name" : "combine-schedulers" + }, + "state" : { + "checkoutState" : { + "revision" : "0625932976b3ae23949f6b816d13bd97f3b40b7c", + "version" : "0.10.0" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "combine-schedulers" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "reactiveswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git", + "name" : "ReactiveSwift" + }, + "state" : { + "checkoutState" : { + "revision" : "509916c99f49a5e6c8196c968b8b7e3dbd48db04", + "version" : "7.1.0" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "ReactiveSwift" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "name" : "swift-argument-parser" + }, + "state" : { + "checkoutState" : { + "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", + "version" : "0.5.0" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "swift-argument-parser" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "swift-benchmark", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/swift-benchmark", + "name" : "Benchmark" + }, + "state" : { + "checkoutState" : { + "revision" : "a0564bf88df5f94eec81348a2f089494c6b28d80", + "version" : "0.1.1" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "swift-benchmark" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "name" : "swift-case-paths" + }, + "state" : { + "checkoutState" : { + "branch" : "main", + "revision" : "fc45e7b2cfece9dd80b5a45e6469ffe67fe67984" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "swift-case-paths" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com:443/pointfreeco/swift-custom-dump", + "name" : "swift-custom-dump" + }, + "state" : { + "checkoutState" : { + "revision" : "3a35f7892e7cf6ba28a78cd46a703c0be4e0c6dc" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "swift-custom-dump" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "name" : "SwiftDocCPlugin" + }, + "state" : { + "checkoutState" : { + "revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "version" : "1.0.0" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "swift-docc-plugin" + }, + { + "basedOn" : null, + "packageRef" : { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "name" : "xctest-dynamic-overlay" + }, + "state" : { + "checkoutState" : { + "revision" : "4af50b38daf0037cfbab15514a241224c3f62f98", + "version" : "0.8.5" + }, + "name" : "sourceControlCheckout" + }, + "subpath" : "xctest-dynamic-overlay" + } + ] + }, + "version" : 6 +} \ No newline at end of file diff --git a/swift/spec/fixtures/projects/Example/Package.resolved b/swift/spec/fixtures/projects/Example/Package.resolved new file mode 100644 index 00000000000..f36793708bf --- /dev/null +++ b/swift/spec/fixtures/projects/Example/Package.resolved @@ -0,0 +1,76 @@ +{ + "pins" : [ + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "0625932976b3ae23949f6b816d13bd97f3b40b7c", + "version" : "0.10.0" + } + }, + { + "identity" : "reactiveswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ReactiveCocoa/ReactiveSwift.git", + "state" : { + "revision" : "509916c99f49a5e6c8196c968b8b7e3dbd48db04", + "version" : "7.1.0" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", + "version" : "0.5.0" + } + }, + { + "identity" : "swift-benchmark", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/swift-benchmark", + "state" : { + "revision" : "a0564bf88df5f94eec81348a2f089494c6b28d80", + "version" : "0.1.1" + } + }, + { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "branch" : "main", + "revision" : "fc45e7b2cfece9dd80b5a45e6469ffe67fe67984" + } + }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com:443/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "3a35f7892e7cf6ba28a78cd46a703c0be4e0c6dc" + } + }, + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-docc-plugin", + "state" : { + "revision" : "3303b164430d9a7055ba484c8ead67a52f7b74f6", + "version" : "1.0.0" + } + }, + { + "identity" : "xctest-dynamic-overlay", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "4af50b38daf0037cfbab15514a241224c3f62f98", + "version" : "0.8.5" + } + } + ], + "version" : 2 +} diff --git a/swift/spec/fixtures/projects/Example/Package.swift b/swift/spec/fixtures/projects/Example/Package.swift new file mode 100644 index 00000000000..c07fe9d4edc --- /dev/null +++ b/swift/spec/fixtures/projects/Example/Package.swift @@ -0,0 +1,23 @@ +// swift-tools-version:5.6 +import PackageDescription + +let package = Package( + name: "Example", + dependencies: [ + .package(url: "https://github.com/ReactiveCocoa/ReactiveSwift.git", + exact: "7.1.0"), + + .package( + url: "https://github.com/apple/swift-docc-plugin", + from: "1.0.0"), + + .package(url: "https://github.com/google/swift-benchmark", "0.1.0"..<"0.1.2"), + .package(url: "https://github.com/pointfreeco/combine-schedulers", "0.9.2"..."0.10.0"), + + .package(url: "https://github.com/pointfreeco/swift-case-paths", + branch: "main"), + + .package(url: "https://github.com:443/pointfreeco/swift-custom-dump", + revision: "3a35f7892e7cf6ba28a78cd46a703c0be4e0c6dc") + ] +) From d46c22bfd815e347be3412606f623ccfeb7d1c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 27 Jun 2023 17:49:26 +0200 Subject: [PATCH 5/7] Implement FileUpdater for Swift --- swift/lib/dependabot/swift/file_updater.rb | 61 +++++++++++- .../swift/file_updater/lockfile_updater.rb | 38 ++++++++ .../swift/file_updater/manifest_updater.rb | 37 +++++++ .../file_updater/requirement_replacer.rb | 28 ++++++ .../dependabot/swift/file_updater_spec.rb | 96 +++++++++++++++++++ 5 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 swift/lib/dependabot/swift/file_updater/lockfile_updater.rb create mode 100644 swift/lib/dependabot/swift/file_updater/manifest_updater.rb create mode 100644 swift/lib/dependabot/swift/file_updater/requirement_replacer.rb create mode 100644 swift/spec/dependabot/swift/file_updater_spec.rb diff --git a/swift/lib/dependabot/swift/file_updater.rb b/swift/lib/dependabot/swift/file_updater.rb index 2055d2125a9..6bdcdaea858 100644 --- a/swift/lib/dependabot/swift/file_updater.rb +++ b/swift/lib/dependabot/swift/file_updater.rb @@ -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 diff --git a/swift/lib/dependabot/swift/file_updater/lockfile_updater.rb b/swift/lib/dependabot/swift/file_updater/lockfile_updater.rb new file mode 100644 index 00000000000..b0f45f9c7c2 --- /dev/null +++ b/swift/lib/dependabot/swift/file_updater/lockfile_updater.rb @@ -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 " + ) + + File.read("Package.resolved") + end + end + end + + private + + attr_reader :dependencies, :manifest, :repo_contents_path, :credentials + end + end + end +end diff --git a/swift/lib/dependabot/swift/file_updater/manifest_updater.rb b/swift/lib/dependabot/swift/file_updater/manifest_updater.rb new file mode 100644 index 00000000000..5137550f5d1 --- /dev/null +++ b/swift/lib/dependabot/swift/file_updater/manifest_updater.rb @@ -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 diff --git a/swift/lib/dependabot/swift/file_updater/requirement_replacer.rb b/swift/lib/dependabot/swift/file_updater/requirement_replacer.rb new file mode 100644 index 00000000000..6f1311cfded --- /dev/null +++ b/swift/lib/dependabot/swift/file_updater/requirement_replacer.rb @@ -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 diff --git a/swift/spec/dependabot/swift/file_updater_spec.rb b/swift/spec/dependabot/swift/file_updater_spec.rb new file mode 100644 index 00000000000..5c55b6d9b2f --- /dev/null +++ b/swift/spec/dependabot/swift/file_updater_spec.rb @@ -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 From 40ea790a4f052699890dfadab55d585f1e2c9fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 29 Jun 2023 18:22:32 +0200 Subject: [PATCH 6/7] Implement UpdateChecker for Swift --- .../dependabot/swift/native_requirement.rb | 31 +++- swift/lib/dependabot/swift/update_checker.rb | 82 ++++++++++- .../update_checker/requirements_updater.rb | 31 ++++ .../swift/update_checker/version_resolver.rb | 54 +++++++ .../dependabot/swift/update_checker_spec.rb | 132 ++++++++++++++++++ swift/spec/fixtures/git/upload_packs/nimble | Bin 0 -> 52192 bytes swift/spec/fixtures/git/upload_packs/quick | Bin 0 -> 55965 bytes .../fixtures/git/upload_packs/reactive-swift | Bin 0 -> 49206 bytes .../projects/ReactiveCocoa/Package.resolved | 52 +++++++ .../projects/ReactiveCocoa/Package.swift | 16 +++ 10 files changed, 395 insertions(+), 3 deletions(-) create mode 100644 swift/lib/dependabot/swift/update_checker/requirements_updater.rb create mode 100644 swift/lib/dependabot/swift/update_checker/version_resolver.rb create mode 100644 swift/spec/dependabot/swift/update_checker_spec.rb create mode 100644 swift/spec/fixtures/git/upload_packs/nimble create mode 100644 swift/spec/fixtures/git/upload_packs/quick create mode 100644 swift/spec/fixtures/git/upload_packs/reactive-swift create mode 100644 swift/spec/fixtures/projects/ReactiveCocoa/Package.resolved create mode 100644 swift/spec/fixtures/projects/ReactiveCocoa/Package.swift diff --git a/swift/lib/dependabot/swift/native_requirement.rb b/swift/lib/dependabot/swift/native_requirement.rb index 3ed6e6344a6..211334b574b 100644 --- a/swift/lib/dependabot/swift/native_requirement.rb +++ b/swift/lib/dependabot/swift/native_requirement.rb @@ -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 @@ -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) diff --git a/swift/lib/dependabot/swift/update_checker.rb b/swift/lib/dependabot/swift/update_checker.rb index 372af9909d4..b3589f95442 100644 --- a/swift/lib/dependabot/swift/update_checker.rb +++ b/swift/lib/dependabot/swift/update_checker.rb @@ -2,16 +2,22 @@ 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 @@ -19,8 +25,80 @@ def latest_resolvable_version_with_no_unlock 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 diff --git a/swift/lib/dependabot/swift/update_checker/requirements_updater.rb b/swift/lib/dependabot/swift/update_checker/requirements_updater.rb new file mode 100644 index 00000000000..b0dac6ab44b --- /dev/null +++ b/swift/lib/dependabot/swift/update_checker/requirements_updater.rb @@ -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 diff --git a/swift/lib/dependabot/swift/update_checker/version_resolver.rb b/swift/lib/dependabot/swift/update_checker/version_resolver.rb new file mode 100644 index 00000000000..7bde4018e0e --- /dev/null +++ b/swift/lib/dependabot/swift/update_checker/version_resolver.rb @@ -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 diff --git a/swift/spec/dependabot/swift/update_checker_spec.rb b/swift/spec/dependabot/swift/update_checker_spec.rb new file mode 100644 index 00000000000..74e9aef5233 --- /dev/null +++ b/swift/spec/dependabot/swift/update_checker_spec.rb @@ -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 diff --git a/swift/spec/fixtures/git/upload_packs/nimble b/swift/spec/fixtures/git/upload_packs/nimble new file mode 100644 index 0000000000000000000000000000000000000000..822a0352e8f8a01a96d1e3ec999f289aa33c42f3 GIT binary patch literal 52192 zcmb8&ORp?PvL$AX`&SU?F{>iu9Rlv4ffgW5G}2f^JgeB3byy%z-R^>*|E_Q4-kIUP z*#i~t1ALswh;TPITefVOMbkXa|NO)6=WqY<^M3x@Uw;1n<3IlX%dhM4`LF-W-~RE-_n&`q z{SV*&`t!HvFa7ZQ&&T=WwthSAzx#3Q|NVzwuit+>&R@Q-Km7jJ^_O3M{qH{<=lS>Z z+mFBh{B1v9|M2bC@2|i8?fhl^{_}sFKYaW3$G`vf>tB95fB*dlfA*JuxWV`JFF(BR z{?l=OUw{7PJbrlo@Q>er{`-$^^7zp+{rC0T_s@U*+qv$4UAJG(`ak;l`+aJNNHDUcdhKxAptKuN>&vO3R|NZqJ+mG$I?bEi7-QMn&weP02AJ^g9=W*Yb$8?R$*&XXOE!Vy^{m{yhH20C^yk$~{dw%?F|OrL zEaguO+qz%-K0o@cUz%;d&avlgG_4D7( z|Kpn__>c9MpT7V4(|`TPZ}#TbZ+W`SPYmn4%}X=4!*MK4H&1JG_Vdwt#cMmyW!<;= z`ufxB@BjR^|NXb~_<3KypMGi>=gVvD{nqx^-kj<yx*Gmq%_N`*3aRwt17iEq`kN z_1o{i{_^wTH-7pb`>)5zSpM5j-`C&%a(@3kziQFWW1E(7oR(&6_j#RD zjFx6|u$pY9&-82o4^&B2;H($%pmk;DPoa^uBPmjl++CR1Vnf}`5X`J_|*_x$2 zyM1~1zscV&=g@iQb$l%Seh%f&eE;pAvET1MU;oT+Tzh93_D6SK(>1lnF((K^z*l${{HJPzihw!`t%_kyILLtJ8SxJnAWZDhv|5X zUh3gdmgDHJwVjX0=TiUuAHV$a=STBAX?dCl+c}n_nMSUr8%M9uUt>2oQsdgU=Q^C@ zI-T9;!hBxd6hAO6oYp$-?bbbZm$p~eZ{2+K>wb3r^>PgC{Axayhd`D{j#%&PjasLep^hD2vd(9< zh~+jmLw7jE{LJH8o1tmDVSfl7R^aN}g=w@`JDhv{1H~)M)_7T_VdhHO_VDsH;@s9_ z=%#2PmFk>gI8E5WCZlOpj??m(x>E{P^`4+he~j0j00apzZEg==Y=Z z*EIC=vW@37d*QAX*!PWDi|IDe$8`#ChsSEayg{*s<9M`Z4@>bM?c;GUNxw2|eyD*2rskNsZR=_~ z>c(Oh{W^P}*0$XQ?U5z5%QZAd;}G=i+APgJdcFD;?vwBkKU-$oF5K(Z4)c08T|b^) zfF%k1^J6~yWvF+tyLZw3{*~zYc@+BlD0u%CZiEolW)kH9r0p7cA}9Mg_u=7SEz8i< z9?=i{?G(bFVS(AWs$Z729YudfvOT&%q}t9c-uj%5Mf9xqu`dmw?S-J_TwKyELOxU6 z=8o-5QwOU|{R~5mOSOu9VF*@un6{laX~((iuhv@5^JHBaQq!)>;+KZ4c1V3;2$R6r z9_^U-=Szka7qdRthS`S2n+ujW_WEw;ZyCm@FgU)Q#Urv3NW;NeEQTq znE5h-ZoR+iH7G0zo)b76q(%sB{QzTJ63vd~(Qt6XV;imICy@Pog5frV$J9)dQ*b<( z!=pRe?lD})F!6&t#Jnum_3)2N?UaWiIP*MUG*`56%1n9W(8Z^OtC4SohPrL!?W$K8 z3OjhX(J*o9Hu1rct!w9L+WlI(;{-f7sr{UmFRxJ8!62MK(N{O}YU9)Z-`zZ~J-hJl z#ka)+E?o@|hQbd1HL(0z#M>e825md{ZFhAmh`?mdf!)agws~8IVd}^J>W{TP3&U8P zCZ6}~vjL9%u&xW7G#pNO^Wc)Z%dc!)R9i=up|GTXEthz`!)-}U&T5z! zoWSfM=L*gWjsSV>)P}iu2f2zjUKa)>pvkfLvqcUfB|11~2~zKOw=br2do`ecx@JLS z37&b3*EGYcB0OvYQt$f&W-arVjl8!302{3Sa?S|??RBvyX$ohXT&HOyfNQ(Mgux#=8*sR7c-lwsDoKTrqJoh^{3{xzve0!N<-$4IS z0hIO*OAR!9=H%JO9lV3kt%GwHuXQ1}lH zXvRyxg*``$b&(OdD}(@`ouLDp-6^R2S6}*%@K+-z*{wK*b-u(C&Wla3=zVTiw273z zIzau~e>n0|(_un?aYFMZ&)U#0$rCSoY5iE`)K8E4`KR{+b{7vb@9iWEh(Z!$q4PMn z@`dB<9%q{n%a2zMw0M8^F@$JwcqXp$Y0uU{?tF28)A|4@9fk^w4e#6c4Sw$cIZ>}r zwSzFB7@BS3HrwP(tpwYjS*ql!gw%U0;L4ql&{okq+kwkI2pqfUg^NU)$a~8DbNy{V zBhTNR=O~j#a-EVSh-w+m!x4Z?IVC@_oYe*lg$LgAoYLY^#F5Z))*gAYg9o>?BvF7> zdBW-eiW7{O8X>ox6*M~v6s$bAws|yXBX>t3PBP_bt2S^bHUPJPlu+#Gu6|;O^AHc- zdg6UWk35=05Clwh|F;2kmuNF0k|eTte{08#t0^RWSbr;XSeCLp4pG4#Bkx@}O9`08r=yC}F?p**rz~D9~zg@+Z`9KpqBiXjK z6-Gvd9K^o>+92756HHxrQVb>6bsQL|wep`uBIT@KsB!DY6K?W<;ig?I18$9TktlL% z@I|$MbMgA}eUKL|ISPPQ4;X6@R;V`F6yO3~B3;(X{O98MqhkbpMFL@-C1Xtc31224 z3;)@aiBDpk@wHjk)ho44DjCv$p)v)wkXGxk9UB*bse?toTz}giIt@~M^mFI{ z2N#Ix)nr7@RSqDU+r+uwst7vY2UvLof8t(%7{DiZ2U0KUO`E5=8YQq@{a~sWut)@( zEc-G6-ngtu4kIxZv;+j%Y%}d`x56;;+UgD7Cy)W*j)GLW9p?6=>TdkCU=G1He$k;k zb_8yDePRDNwH{B736xMz2X?bt5Z`F~ZgmzhzML0Ap}PDwfX57>EqEM0)*lT)dE@|r z5XU9;a&~O(P&=@tL?E-hw)V}5?N`3(;b8|zA}4&nVAkMd&}|h!m)nA{YB&Q3!<)w|0x$0)Yy-iY8D0V3ug)XKXKOJ96eP3aE$zboKg-rii)01381U*e;W{j{u04wp|e}$ z9Kn4mn!5~eXpi|+5|rLZLaXP$_g{|S&?|NJqdL>0QPJMV#hx{aJ`zg6Y7rW)@&IFh z@8<{k9?gP4IQS`GVziY&U`WO*4nz{f1GVM(L)6^Y59+W;D@T7;vD>bzQ|=hh5{Fu& z)4>l*3Mv|hfghrnKTa{c`RN((3#*h3aCmv}(VjxQLt&`C(Ctg0h)mI1si7Esc+h);bBtVI0co_(L1 z_u~oTg|lRZ%U;=kwSrKC6dQvAeH^EDYS^F7GMc)&zBm9I zpuKi06!BmIWF|Y*phUK0O@^*HcK^-uRM+1JKnY2a5B^fk9Q=j?FTgD$KS)y~b1giy zKwh5onQ{Q1Hi%DURhQu)yU?RXAyVtE8GKs-^|Tc-M?@^VS&Mr18lom zwME7Vb3qd=Q3d|d23(3()d4Pwj3C(K0mRv?9dHw=;`w%LWC$Vauhs%p4V3|Q#r~oB z;2r4S22c!O#btrY#vH%jwR%wDV`(c7P?Q+O#%tqb69Dt~ia8thd)*ifs}zxhS4Dx! zfp%{I(l@&mT-5d9mkuwE^Hq*R1nh{=HvUL`0iRz6VEUHV@+WG^lSNO}C~@MP@&=sQ zj=#f>aI6P*hHo#o|E7TY25eBx4~E1vp)A#3oY>y~>(#!oK^=Q`sLSh14rdgu5eJHi z;xFx+`n+5qDGB+COrTG(RU%YfUwB~ANfC9A+JVf0LnN4U_*&d2*MUV4ltZak*WdP| zYNjqP-yD_(jfm_3fK^}?v`hFi{DOZ6Uw*m%_JFbz5Pngt2IOG3!GWLg=_o8I^Z}kf z&^Q%@<&y6W5amJ@F2EXJFu-#oCIx(#xdL>p>ZnAlDRT(>dS8zK!s9x;I0X6XiVfia zg^ulFIl%N&Mp5ZSWVZM995&_QkVHS2V*?==ekunH!NG=X+1FYxKw*HB68#(!9Edy# zht7VZ-{1kDmN|KKkJna!troB_K>RS0Mx5$Y`knv<76$gT?x}X1? z1{#4V-O%q?1B?JfZa%q_+;x%&O(T=;IW<|tQsuC+lK1n| zLP0;|FsEl_L3Al{Icpl94%j(*adKboUt%auuaSYOKhOo>54#|eVTV(g5Y3{jb*- zE`axFfTxr{kCYtd7hxQZ2aGm|zH%Wq3k88v068nLCf_Rs8k~F2 zyWt;V9I2zs(Lh$uUmW3qpB6qZpb-;^5*B_rb&Ah*W(m7eC|Fz3@U&sK0b(G5f2@gw zA9RU6z>J>mS2u`;F^WJ?P`H@z~HOvsrA3$f~GYmr*&a`~Io1 z#a+Np$ShtI2E}`)2uB|Rlq89zVJhE$E<)(eX`NX9T3Px%8sW z;Y$|Dad$uhe;@KmyoHoTdgTfDh z?QHLo=S9whJOjs|IGd*|v_&!d<>rO#Tg8q7k9$+J;U;MU-7fMxMOD@}P5VmvZx6UvcNTvW;&GCv8mHhSs%5_z4_w;L#&u4M|VU2c2-&{Hzk0` z0)oc~)2k|qyzai9eUs*{tXIOvg~+!6c7AYEOGL`3?!xp}nZlsD?(28p4`&nFL^*s? zg!A%nPp}VCSaC)f7_ced|4|?~`4{N|mMFxry2+p_V&jM!DMmpe9Du!@N(74-eBb}U zc$oHpx01-f8R}4C3V9!=pc&zOdQYgXyua&j_od3i=9PgvY?BJ21W59aet0Y(h(LPa z^E7KzKTw=rDaQ`)t6!-CV%u07g)^Zg%_sI;`i0d;^;g%w?H6x0BW@;W#W7lTLHkgM zU+B@YdjzzUp3vo0y3dZ-`hI~Kf5*(n)a&K*9zyyl0m|u3;GZd)PB^4`fcFk0LO>iT zJ|L1f`vGIGevXL-XF$rq?()Y+wSxEa3w2@nB`H!Fd#(&41Kvcw6Qf5WAcIqBsdNYc zoXlMP&*5B*)b8iB2e10_W~yE zkof`YJ+?UcIu#ew395)J6ptVUz)V*Q_}+g+sXvN12?-Pd8YY;*w5UU5minZSyapYY z;YXG8zCQ`G|0?%9F&-212f8-2zDztdG%(5hMc4 zV;}(lbBfCH`AZ>-5QVtM-g*ZGFu51KH#?FJP{nyKoUjr}Nn&+{kVVYzVV?j-kVQ4b zNuYxXDiw=1ReWiP`gY~PN*#(K0x5@x!&87xM1-MZ(|W|FB=K6RFo~5cwktW12NwFH zl&ENr_R+e7hf>l>d5?-GZd^UG+P{(mk(}TVXVo+uAo!tbhZSZzUVw&x$enjm$gI|{ z*grv<-L{Is*y^V|T;BsqhhtQR<$Ar1y(kOBza0gqM}q14FlD>IaJb zM^i9T?JXe>Ej8W)5W*-06GBrX2#cYq5K!TMI0pc6@&bccA&rGdT$koeYCui_g9zbN z?zf;&v@V733Zqar&&1!C@LQxgYibs7lsk@A0A^Au#pxtC6ffC z%R-``c7Fmusje^f4^q<>peZx;c)PG(TFxL@@PWXL5-+_8(@;Nu`Tmd^8dkxMB3z#e z@r4{D#Rm_jsO|+}sH#)Q&F<%4JK>eR$a*nllxIYgNR&0sWaB^8F|9})Kg;}`R8WzE z5*>p-4?Wfih`_3*mMHs8V3(J#)%m;g=V*P+fw%1|q^)}#mgHYKkf ziio?FSLIep+tu~=?x#wxUS-wLyzERUL-?%vKY5;nQhaDy@2Zz??&#-_h|%N{WD5Xq z`e!u6L=b?4lTtXpdd)ba>i%!;;aNby7;4v1q+!S4uXzDje~*n-1Zuz{P5Wi~E2!M> zpB76Yf>NFiDE5v>M>k#$bu`qHA#yGryuAOTSikhCE1WpxYE9}uiS_CxBA39gur3P# zrm6x0xOCs2&thx5G`=_3x8?GaN2hz1TL&jp6zM9e`@h%ELz9_{9hgcLPa-_xb^i#v z$U4b0ly!Q}?l>HF92rP*uRxsdGQRrVSto0}m_< zJz%&pj+!Q)9wIrMk`WVQdG`83L7`ksWFP_HgXqV2QE7;~t_`a|!`Ld$Tp7N6{Yn54 z%|m3yyda|76L5T#tD6G4ws%JZB53={{EALzxK5gbb(jC8wG|Dk=s0!AL>!2j2>2M5 zSw$bL;{E;ylAFI%0YINdk`L;*D-r=412?7@Cj!0d`ETD5up|GLWG~3-kUmIk9$(^Q z4JX)@ckwu7?o_5OqJ|0|?W(FFzFMh`+nV73`8wi*^+eLKR@Psp&b$)Ov5PQg0*H7G zX)Ai3peGcg0$^^!Q3<$AC3(HRxEi`j(ZdO`3NrX;;KX54K4duF9FK^bthT=}e)UC; zCmK)JNjAcKDTKx65vGr1e_Le!Iub9}^reGaG6V�w<)?d)8iFf~h1}SgOVC5-5aszt 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�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 X<}2G25;2YXZzZpl{paeuK-2txgw4fV literal 0 HcmV?d00001 diff --git a/swift/spec/fixtures/git/upload_packs/reactive-swift b/swift/spec/fixtures/git/upload_packs/reactive-swift new file mode 100644 index 0000000000000000000000000000000000000000..770a82ca3f5b6a24e8ec04589c352f9f0ac7329d GIT binary patch literal 49206 zcmb8&+s+-yktOK9o~KA)u9=GSVSu_AU>fMRD8y-}IK?Chl39}G>HAyzKZHA#-BVJv z)KrSGBf{O>Y}vA97ERM$|K~5?uW$eP@woo&w~s$Q{PFeEm+k!Ubvyp!|7h~Bb{wa@ zU*^7loa1_I+p+A!v0qI;?)!1{$LY_HV{H2Qm;dqK|L_0jzy1Elryn2x-Sxlx`0K~d z&tLlG`^WS8uy3Ew`%fRH;Xi))^7#JYygvQde);~_?bD|(fBWUUuCLeU58pq2KCZX_ z^7+e;w?BTnK5aif{^#|}=Pw_=e*5y<9KZAb+%pGpUY$H&S6@*wOgm=(Vo8)w{3s@`-k7R zkDq^BpEv8{cgALF&PU(0Q@1YNK5vh{+lO)Phjv=}$2JXpyY>6oP37-w$B&O+K7V*v zOF#AD`;TwikL$O8 z#|J<5`FqyltckM(?X`>~JPzVzo~Z;oqQ&vkCLvm2XvTb8+N|Ku0HfBfz9 z_UU^btlh@Pyf^(dOxLm$2fQ0be`SWG{<A3tAg$^L)& znxC_^YqPh@G4y@=aACU+Th}c6*0$5yv`cqfZrL?|@^k0M?_a-sj}_*Z_O&^zw2S)T z+}ply*7<7oYwNG(+_$;e_hlKDvtF>5H@*J;_4@YlcgFWzv}ry%r*yvhwf|JmmJ{%eU_z#%%30jE`%Z&&T9MjLo|B<9S$rn{jx& zbr{BTpO$&}li&M%{q1EJ*hxIXk1s#|@%6ckb9?lUeO%9(l?=@?Jf`k(?8Dx#<7SoJ zw(Z;X`ZI&^L%)Cg{`?s$HF(C|+d2Y|vvE|1!TCBkzh*zKy&s!>JO6A6zir?Do*liq zaew$bO*?Ey+fT#UF8w%OkE!pDrr(Bk^PboIXSVVE>nCpc%N{3veao+{+ukkXyxPyb z*_Vc=xE^iWv|TrL9Lu!z^W&V3@lTfP``{F zxbzNfH%?1)Jl1ZSs~`IQ;rjbuw?Dpf!ykYAP^OQf%d7q|b+V^dBQrF(Xy7cNF z^R;x>JTL3iGw6B`zkm34ef_lko4{^Rp;d$JFYkAMH*IX{2cpBvDQ^Ks5+bM{9&S-8jkxQ;$vj(0q^$FeZ4X{$Vk z;C6lcz-w&Z{^0=Rc=nI~a%3%nz)S4ObIj}F2rukuTbqr?>igK(Zs+=oY0t~SGP<=g zgs)%D?_aM&pg%eN{ESC^?87;13$yB$$%-?n%^?<&9{n;*EMgsxDuf8c*Y^GTOaE*8 zVoaA)(s$R=%#U`pTWvG2=(RoP@v%>b%^I9OXQy1}m)-xg{eK@n{R0r#&QD*SxWmb8 z_?DR8!JDop=eoAne9Y}M_w&$s_r7~vOSNm?ug~Y369rP{M}K*RV?FrYt2u^8yS4kk zfj&f|<1x+CF&yRNyo4Jz`4Az$JP+G+ZP(Tb3e9|SQ}dD_v|L^8e$4a9rwHrwUcJ`$ zufNA!ek2M$W6jocBJDCCg0!{i#dx7_zq0>VbM@y4U2%5RPQ9Gf>v=t5C+g`*nI;d{+5hafu)N!k6+7Tu(C&M}7bC zzP>x4Vb?OGb$@g{OOL-Dx@#M3}}oB=?*1otb^uU;qT{1pzvVB#U*CRvx(gVw>8I&f)}!mraT{kp*)=x=#Oi03#lm*IjA zV1tISTDEyu*u=4N?)%ZsuR-he^5*UKLvsX%cB7a(O%LF)?Hy#XZ#wxo$dpHAzjIgJ zzPSIo4G-?HVSRhM1F<|w)Amc>FS}zgcf#G^Y*yEI@Aq%_9`bLxV_&xO;a}p>!tD8Q zey*8khohO->DAl5K7UuN{{=|SexMy5^DuP#xh;ZN%!q$+4qFcL0*I>z=>jU(~1I+DX+qYxuczA)hYug-yeFxeeG7_O; zu%}yP0Bvys``tpp7yBqy9nH4)v)tkwF5Z5$7E?P8PG-FW#R=;6WFuhk)?5jD`>{-& z0CBZjC)S+sOLIzIss$)c&^{k5#W8J|+HkJcZ~+RZc+|GBA{U1O8CTaA2Fymi^)@fF zyTdezk*7ptc6!e(IR%%%w94ym185Fe_1MJgAMHM^%eFIMKpIMVfN)Z3=YmtME-wst zo#A7#XqOd0SzOKzaX*BUZRL#xavRA=tLqB`p0~MwEZuNDW4^VzvS<6*FU#I>$Gv}x zmX!nOZu=4ab|-%AA)aY!ruG^h-F(22&BUmeR#*bLy6OOS_x874e7wIRn9pr&w`u2Y z8%K|Gj^K;aygjb5e*dAczi|{Pp&j1h=oX1pZ}qQf+*gma1Rgg}JFFI<@ZbZeHgZ)S zM9#7}L)&IcXP(8m@6U4-|HY8%`ojK1KVZ9ua3;rnlYl=H@5>Gh`?>T-9;<6#vFXbJ z>TVC*B%iTjvzPVQaUp%@*3IZC=4sw9=ck+BGR4>H-|cT`2QD7+b7?0*KZjvUuDM9` zldd-+N#iNj`uW#l0U>h1>#@iH9@f8Km+N@IT!F7mvmATdGQ#;a`oBCtUs#~b$~6s- z*9jgQ^s-xgEJkxec|m?~`qsQ+tJmub3tS)k?*e=8;09YhnSwV52v#71PZGk$ajssV zFD&r#{v+&@+c8HB|#p40AyJ1HFs0%{~ORWBMAKH&W#^g&iV%m7s$=` z%ZO?SXR!lwyN6gHlGe$5^kUS$UEN-pa9^0=EW6Wf9pdn}Lo4BR`|oFIrDhMP7BgeCB}yoL+} z@@V#144*MNl?4rj1!F>Bm!9wM2Tl`1(t)HxDJ}xCKF6;2CVd6shQfl|Wmuu$>l|bj z*r;}DoP9B6A0Tw;%4H!P#nOcg#rg|-tY&MkQ7+Hs#?ekZ)`wuWaP9ppqRYNs8I+e3 zG`zbIETQ#np?N};=3a~Nn zEg%I{qXzxt{s6GwGR~LRAB*kB%$*BPklh|5g@vw#xj*;{Cf8 z^qUx3W3d*VU~>ew*i*wQo(45_7NdIpd;4IxQQk4|``B^$j-%G?qLMV6ZH7Qp!WlgFV^1evI>V@xr3R8!z{|0LA&^Dv|IIqe#15!#a#D0LCBT zNta1jhA80E3ImSi`D+j%lc=-cXCLE3-XM=)%N=weLjeg6A)>8<@a@1PNYY{{{V3aa z_$Q!ia&&{+1+fQc+8}H1;=)^#8H#|rq2hlnh6q2@*bK?vi`-Ti{_ zs~wyQ3luDDnO8^tJ=8!3wGgBaM}}Jrf|Cx7)~9-a!UEfQXoV}-OEe$QHu$qn@RRrf z7Za+BZFFaKfTw!{p1C>)DOH|#2?R4}C*0H2oorM7dElgYhM%v$7l5k=brN)?czP{K%uqC;7KRET{-aq&_Ltp;D#{Vf|&!n2emDXCux`*m*sJxUKG+E z{q}fRfS}LmY=Z}@An5vg1!i~ya+YyoS@EST_Sg|(GcIkPdB`1*Iv*92vlLNO;`%t; z3YhlS4}j{)he?mId(#F_PC3Js2`z;QOW4{Zo}mNLA6ttEgR2E5aAW}LCDN&yl}B(X zeVFAvZQ(Yif^f!lu2AXRA_ouw=oqw!fLdK&9D(p8sKT%%xLGvl?jZHb!PmYE1KGa8 z!qyv5909z?s*WXwfLimxd$VSj<UbxnYtUl2RrvfB$uUtvLnssL~ZAzq?Nqt-hJ za-o;nAR-{d;~|)WKIuLmZx22?qW}W~phZSU|WAm0Cf0|KjaiAb(a!NVOen zgmG4N0%}p{10V}Q9HWC+UELJB7phzpH0}?JJ<2=gB$X^FM%b!^U@6zNT>Y-O@1GR( zAe>-*V825L(Fh*_V~FH!S7V&*?4xu)x&C%P|60ZE89_zccOH;!0Nyy`aM{=-#Y_PV z{F8_~hv<>@Auo88AX;F!uSZo${g7CY9|GUA;yh;O1q@$i-ca=#(_J24e(k>g!U0~Z zbJi-rIW)+OO(rd$fCqagZw6tV&0_miVeacat}-mzQLvlVl=u2vCZ&UH)j+uS2S{3) zk9z!ly}%=bXaERM;8^AKi0g^x!%j-CaY(~(ZBw;@ZD{}?Gn%aH^Xy+>IKJ6tdcjZ9-#35(7}4rQ}lBuU*v-s0zMHt&yj(}yr~~O>jx+dATC?c zS_P!#^mjlM?sb&>Yiv0&Ir+5CQ)K{cv4KiqAx{QOg%sjJ4g&tEi%4n0=aU3Xp%?1s zzc;XzCUKL?bsjPa=LV7%z6Xgk0tSaP%2LJTl1G;377K{u6Y56E1O&ntVDO+J^$o91 z)xf(6r(9FDfVTs(J$#?6F*ObGmLUONBM>hg!R}kxAnMwM+#W#M-i9+)IhA!mz$!0@ z8#OSnQH2CD>Ok_UlGAm!0fDcKSNWfwV^PI}{8e>Ok-ZcgArGOaQ1zxvcyR#TeShGa zCc$54_BapDP+7wX<;a}kWeu|sbE?+g^|uE?n5w}d;0TbEHb>AK0PI(OTPbBo9xKfp z%K(Tgx=)~-SB#*U;fkHVHDH7jR>5<*0F(?+`M2@_-EDx8Zzw7YI4#Vh0Aj7dD0E6A zE98M32Sut1!6Dd-7eIYF<;o^jAByIH9;^|b6kFsEJZy?xKkY!_!ooD4xRf591%|@G za_81t_mKD?AggtG$TEpg9=#X94z+^afpVi2S`|{}kwx?jnNN~L;lF3IwuafIS*<^bbk{F4iAwN+CpuqFw@pd2-3K(u{IOhZ`ouA;-gx zRhJhYaBRR{iI><5a81}u=jDmwxn1ix!9R{e=-JgK*@7jw+WvwDPVEHs!uESQ zl_Bn890gqUsR5-%UceYrk}L0DvY0S?FXKK3kp}BPU8jP|2Tw{`7?rpespwcaz>>uj zayT0}$x}OY1HXwjBRYgCncoD5f+w~44Tb$I__Cy(q!rRWY?P#FQ%dwOjBi)p!R5W{ zz+N2CP^=#{h`)vh7eaG}crh*)6HRjn&`L)5l(hojYL4&6r=3^$G!oP{-eN(4sDsM~ zwlK1|shCl!O4a(6=O6I*=o@>Z5yB!;2H!Fk&7Vd4sdvP7&3s#`I zyzt-R#mU#806`Fvi6#0{NIvSuYhRoK=hFUH*B9GQM|nAUy?+TDGJk9i62?J@OTzHr zf#?A0`xpM3;YST&-NQBVmdQC>DTYGUV3-V+KBIp9+y4-WN!@1olcbZ|C3s`PsMC;F zK~IMiY;2cm`KMCJAkHbH2z>;67zc+34koY^0%zp360^e$TvZEDI51VT1LKbW?rmJc zt;0)R!|DLj2TVko>=CN#3;To9uuf_QU<(FB^5M-fcUV1&ChAr7r3jp>>u&?Vd&ss9 zyp^MNSp@00fR$8szznFY+x~5@3b`E?`9^TOYjE}z?Fm~$Uy=drd2&iwe((`JE-s*Y zfZ_nGR|@fzsQ3)uLVb3{E z8StrsY~LdEjbVz=m6S$SdY^ywduEp$R5gXRTh%NzsV9XNPY8!m2fQh@Umw3X09?Dh z$AY@M;ulL$O;H#ZmOyH+r4c}6tPWsFVxzoMFIKUVJlJRz4_w@e8A>%C`~{{dF{%SN z7xv55!d&!B{2T?lKS@Ffr{(#8D?yL)GAg~Y-?^|~Nh0ABqG2$QbI8340~Y9k0Z0hoKLtB* zNCCK$R(0_t>LXP1<^I20pQ9NiH;zM&LUX}rHgW_D=@igU56my(qPJd$m+dPVWSl3- zguBAnEFaed&hei_E$SbG)ab-UjaB%q7-Wx0@w9E@3byTtD=`G>0o||)5-H~*KT=UJ z%WZ${iQhwegf~1BspLve1%0|0Qj52J7=)^d#hVoeK-}sEB1);V(CaLcggQhrDdb!P z@B6UIi|RMH4Y*b6MAb_bQNJXa14a*PDHnp@Xv2u0aO9kLwE<-cDS?qC!fSAsoDH9? z01HCNyF8cu5lIykD+?+)Y~l}SU8bSG#G681pbxi`#{mnGF)Hi+QuP3Z1tAwkqnEnIpOmHnln(Sy$<*ppqa(iH^2Z#~Ncc_~vxNt&bH3~gRt) z0P2Nc$GB+GU`u!(^%Tyl>t!%cH9jLi0o$eW`AZHNTLga5YLgt&G=Y!=tvQcq=~E<7 zOO@f*BwoP*>oaLErM#aiJQ^34q`o4^Xe9!W3`wCPgHT%CzuZ9VC-9_c2JS|Dc?0D) z#b1XJw<^Wi<4XFLfm0CPI4fe#Fz9kW#XJQB$jVAo*S~L| zw`RBxP7a6KtB51LC{P^*3STRLM@kV9)n8oyj$p(;NQvlkaBT2aO4+zH{)`8bm80Z0 zdj|%WPhSeLn6?rX9t~NAHpC|)AVe$z7*GzdB(qr5r^=g`9QMD~Joa>e;?f=e^bx6F zll`k)a$zdS1Z_xGkSz6gtOi8AkEQiHV(-@znd6F6LbBAZb#)UQsk9D(vs~_xgW1ab zA=lsdg_CczgdpF-S1SaQVe0UZdPPqVFANu;aH<7(U;hFBr~0MJi3ub`*j?Vg*#EX&9k$5!o(t$Gs7h0Y5TYh`jK;qg zL8|31DfG3CY&8i7F989lsc?b>{1R_j4JC`9fFG%DU)|r|BzGnivUq96;3zYC8MdmPRag|-vZxOhDOM4(u5@=iH?-I+m9tfhPwO%~{+OH^55M+aUWfwEbFu~JcR>hBN0 zZ|fhSBH^tf;8)HS_23WsDatthLj;z@lscFm1)eU}X9E(FUKIt;6GsL`rUB!K4oEht zNx=G}1L%Cp7bw;bJRrgsqZNLgHpj=C>upI3hrC1vPl=;QU0r@Bpe>RSQ=TrLG{qKK}}U%_BoJb{XuA5)$F35pLG0PB0Hp|)2#l(3r^ znrZ<`1E5=F{N!lF-E@Z-< zE~+T%ShE~}*0jVgx&HQ`6oZl&JnNkHFM1$h>?yqw_GC_ia|$G9{Q!jri@PMZj9TrG z_<|Z)L9SaVKocPolF+KgS1V9>Fx4XrWD9;N1c6Va*oYhz8AU*KAUK4?-npu-FH%70 zC;UTNcorf^-UH^MAL9|#p|!+;h1ivP0p3r*z*GrEgVG`H$dH2hl%p`fLN~8`DW-vS z)%%wmGWuHsp+b<#B{!^>7UFSU#y?*ZeEBaKcQ0H-Cgk!NISc49f=xM7QIiof47oYhpyN! zOVfj)*hDs^=xQ-YNA$$YB~zWGm8xV!BY50NjTQC&yKh6ZIbDK2ZOKymby2OA0x7`26lnkd03P}LxCj$jO{O-xaZ zcBT|$tL-l-^afWeZFlz8HiE7b`5ghG4i34a;-xcXAuA79Qs^DDgMx(_WwE44;h%XD z_3P{^`a*F6hN!l`pwO+@L2k_Z(Jea1C^)$g1|%|A0(YS$vXZy1%@+iCGvIbi+1n~T z)t)K&U^1jWw$(ahVW|36oidgA7S`*ag*C^Xg-1!i0pKM}o0Nh*N+WnOmRVZQcyFH; zrj(o2F{~lF9{fSUIS2&FygqW=>eE40{@3-l`;%{~G`QT^2lKUks0olITL=q+*3+s$ zPEaAxk^v9xAL8eKrUf@O$I<92=G)r(|iggsDih1PQ zLI60e_yho_!KS+Y_WxvF4h&vxyugKE zOG(&}e;`q~4tgj7;S{xa_8E**39D+*D%>pTT@guD*B9$IWR?KL73NI^Dsmd= z1DB_y1<`~<5DhB^5xjZ7Kd$An!=d^q4tXAwff$0h-+2Etg2<_8nyU48{cXUlW<`QjikTbjNvn{Lk?`AiDcv}-*y;re|BYnUcb7IAM1qjR_07Z=V?@QX<~+eM8u@ww zZv*hxkxgoygu{q*`ULq41rH7Zo>st6O94@;61b8_@dC&SoQalHu{WLC50F|EP20AE zOTr_XtL#-rc)neLt_hN%C}#vvK$_1}&t258s4*Bp*Ba^(k#D&G1&2$9gI)nysaGLqy@J`lQ1;A9Rz7 z-*jP7);SHLNqzqU0XnAEA9ND*L_Bp@14Y7}9j#+IhP{(aRSXPS#C?BG$crj|su)W) zKn~qN#Fq+b$Z4Sg83L13A*fJ7i*QN52x%ijN!yBY4%`%FE>wyga|Go`l?yIOXdc8< zlAlQaouB+W`hA@^k*7kFN{}+4YX8dvC~YFcz+8v1k%m9j?+xZ$Qlhl2&)FfW4{$+3 zhqH@t-eC(0Wjsgl3sa^9GNc3_q{dZ?xh*Kuxld|+Dg zobLMZ3-guXs~9Q#E0<`5rySz1Wig%em{YJr^9s>b2rpF3lp1O$izEgT5BfrCW5K~* zVQm0~<$tQ;eb4UTk3t+-Umo0JCZViV#Y(NO(mu4R6YT z>A!VEq!72K{LlN)0_yClGMJ^Xf1_dOH@vrWiS3flAw**cBuFX#@}{IUZxfhaAXG^q z8>XX=QLSw3mGe#DP=-|WQ_3ghij``1qK&Q^I!N_M{tR=f zGvXK!_&8=1YWe=R15cTdV%@|NagYbmmt_`!QUK|miiWga4i+y|U4Hvt8ckq7o?qcy z_@%rT`^AAo579?Zh-K6W5QAOpAIHyaa@RKB!(xI615wZBs_>mQ5h&K;Sv^2O0?DuV z4As6Qfif3rdPPw0EgXwJVFy&gDisf-ep^4@gZ6O18i)V7_zzPhObLHx2W|Aaj>BdZ)fAT4Ym8UX*V1y4w(F0%TDPYHq5^S|2<`azeK!3ZHA6H3el7d|jI#0+>%B~bsZ`hj;3 z*wc6j(rLXOsISv=G^idbq&u`a0b0ROb%@Ndxc>e4D)%^h&blB;$ej>Ge-{Zm1ebE8 zx~ytCiFTVUiWX^Wz7LPPKi2UW*KRIRn^ZxDPxnA6F|FO zHK)4%effC?sfu?;mJ-||p%iZ{m(fOuU-1wxkf}6Xx%m*^{rm)4@>aM{v)Z$AMoF~k zhse4-}i4FQ6{hH z!VBXas1+E5(*z3G46_Zj2k~#!{ojo@Q+-req+3W^sMGibnwUs7I+b&+4AM)hi{LD; zFDP(5ng~CX@2b>`Xu~Cm0hUe$MIx>Ww$QLks(-q^FhB_g8&!M*muYy?wW$dg@DK(& zu3auJGb`OE+26eY=E$hiI=Z?|FsP3-bs0y(_Bb92o=6H5;Y$JtM_ybHK+%k#9q0(9 zoWL)8d$M2A0AS~pv(8Ec1W@1C)2U(Y_t^1xY8pA zgMcSZ#L4EIU3v%}NJURfq+yqzRtD4-2IL`8cq^r^o(yDv?&7PC*n;f>NMyB`8 zpCCUL6K$9-3|S;fvT{I$iafPZ#8;kGPppv=J4Ov_pGF+Nhz@u9#sB*H!h!s2;~`%h zU{|cai5XHQ(h*aU_-SWdTURNkL(I}tz|X;Mn9>-b3&1U z=!X&7O8DeW-_{R5WY^3LK+`IP2s~0xEKcNDTEh9@j1^Fmt)f?9{`#iQG7 za!8$H=8HKBiZCV^dZu#w%Mvh_+Tf#$!yqJ8hz^>;puU}1R#K(|VPH|QFO9WuFvrih2%laUe;+5l!kPczbwK zLChY?9iDKyioX?sFFl9fxoDE5&nZrt>2B$j*) z=jhpSIRrffP-$%$EB3%&DF7(3z96Y+Lr90KMWawK#t`u_lvI-uD|(`bdI5_ABxi8) zIRdGJMqEMwkP8Z8R3-#C|3(6(I$SMaae$Ch+P{#r86_DSAq<}V$ovV!VkY4VV?P-{ zd4LT5kZ0oXsP0tlNPr+RqDtc<6_hgOLHL5Dss|_zkotzPEmCHu7I2sEQ?)xx5L5;f zSyem)>!_}OKLC;5M6H4Wk*i=4ArU@Q?1lssWs$LIN(aQt>x&WqlT&Qau|;qt?@=uk zP86Oq1{5(Mk&HT_*3hYP7dtrG@yLxSw=;SXfF?|2NFE#WKoThiX@;#3P@$q{vKV*8 zyOV-GqYJ%@3{*)Euzmpz$y&?(T>!$f2`#R`!BI)54_ZC*T*vR%4?99^2M zC>kJ6$DlgIWgh^Ino+F8u8ObpS{QF<0gTn-h3borpfOGIN(`y z-N0fB>5*}9Ki&zoYP3{U#C4DRdR$L3ZIT6J`RLzlTq>#@kLu4P4-#E?#*#uDjAMD1 zVLT^5E$SC3SfrGmfi!wBq9EqMpg=?Z4B9JAu(&;$o0&IwUxHZqtV=S6TKEf%kzb59 z1cz2#UO>QxSZ4quu~g`uOg@2t;20%9xfB+cC(@BrIcpkEMS60t01Qi^H2vNX#n?sS$`YUR6!{km4vww=Ai?e-Q#nSKTmK8zm#E zNf}B=Qcs7hm2^y3vm4@c_5Q{3XXuOc3rNNR`#WYp7Mcg>2$k0t4hZ9^ zWt&(*x{|R?MC90P6sQhoVU=k`S*?)M8xJt>Hj2e8m8OM>Sd8QVIaJRW2`USLyHyJ9xzBW0&-0n$s*7; zOoEioc=8Ivlog2Sz`ID@!6?OhhmFrMLjWpP`6L8va`u6t>i)O=Vs5hL{w^{$1FuQ) zOa+mTBO#=guq_r@82#z`+kq?4@!d2Kk_E(BI5CwJshLnrTu6jL*@MKZe&GE8pq-Rn zMrf-`EpkqhNI5+g2-Yng4NVQ6aF73QeKw!UIBMrBd3_KtRjD^-3 zo)JKP3@TBVuTEfD1JU-*Y8-vNZF_;P1-KszntVE*R}3)L4jKu z3X!ELLgy*Bc{etgWDZF9KSc>103!m2N0$1h6=NxTpxswVL_{E$GVa^^Y0-l1=w>Zq!bov**`x67ty zdK%H2vD*rtXk@fCC9rf-c0eH@g{+d0qJ+Rrs+W+ACeSCuqL$%N}9xU69alTNIuyaYYzA6AM&URyim7!Tc*j61kS@`rCjM4>S4# z5on%hW_l}H={{4p;@a^~1`;Bnd4->@f7<|hIfq8&P4`%6Bdo2b*UmwbP6&n&mSH^G zB#Iv>mS6jV@f);qsk%6U1PRyxJanW<5D^b1mtV@)hZpbd6Fq0VnF;**Xl35geN7vr zS?E)Z{lwjxau`v0_p%7V^ZRIw^}&5%L!1`f@PLn`0tnVr#SA%wGJty7`~KR`95{GY z=dQwckQWUZ_~8KRLPrXbHbc?X{fqO1G@}+aYyoJD;sgsI?C8BlfaG&5a>3NN>iZY# z7o1_2=+5~(7lG5LRN1TsxfLv+qJ(EF01b}b&(G0|4p5pp1o5ouGXW zGEO50i1;FNA*&{wB7?Q@N-uaIJfngKB$(_ytp~WtR0T+=-Fzw&NZ9c0_R<}lC$u(> zc)k&&o{s~-KRE-DU_^-@g=W=;$i(jJp{r09p&V2~oM53UjTep-@RD!d=%@lh%k3{q z5cnajE*XFh!-&^T63T}ZOW}gBL-JsfjWoWxzBHc*4yB5cq^IA2yyf#!$V`LINCUk9 z%(K4!2EeUTCU`|gX_%0q)|o1Z0uc7dm{H?gB~r7RW@)}p;eKVm0%nL{4J8KQ3ixs3 zoO!~bSOOf;@m1%qE$o-r6=C#Lpi>L0LTJ8_k|~WDCk6nA%O)$8PE@g10k2O&eA>=cF z1doFV0;`}%!3n@NAnK><@BQcJXcyGIIT5`Fpe!bVDbq6H0%#BoD^H((^#k`2rr+jt zL_SK7RJ~Dd{s|w1CZe57Kd57b;I6JOP9Vi9lG6&&A6t7;2sRuQfXZjaimE^ow~v+m zboU7qe$>c;Nf(Az&UoMUM3Mi4i6Q@|dzCsXX8H(&ot`OmAl4o*U zygm>|%C54MzKEHF2Gh&+E6Y&k$Z9sn5t`rTxR8O$U=t5ATbxy@lFA4w8G-Ehe*aL$ z(r13dk;OyshJd$^VJt}obOC!cP0f`9Da%-yKmz1R_>%UTTjU{-a4OkMBI7|NW`wep z{gh=aR!TpIf+OPCa;3HOeJCRl;*HTK5hzL{s`oGK=hSVNDM%2%pao8c$E1viHdbDY z#^jx(6V>&%{iyIzKIGd%LS0xlnSc-tRA(dwIk;147DZJC1dj^`O5@<{r;;LnQ#@gi zsX(&G%+VrbA!Spdt2}to2Lk5?`KNLY_h)pxu@?+RO^=4u5j54+fBdw7QX{6@^lj!ih|d;RUT>jUop`B}XGImyrwaQ{_U6 zLR5>m;`_9YQ(mY2ME{GruLi2j0*oIQnebE<2&j}mByRv0+^z0ll6Rhm@QN^2W4Rgw zMF}-h-3tcy-X;uW5|0r}8EHU*!;_^LlxaLtBNH*w$l(lBA*8Hi*=1KPQ$Aii%$Rx|l&qwf0yttLR=PNJO@tuh)gnp|s6my(YKMC8Ah zNvyo$zCN8G=C>fBG&CSH0_ElZCd^0(@X_RnezmQRaEXE$3XVp~CI67DrVCgJQ3=6N zx_lMt34_$5%J(m-ASBzCTH7wGBOaB&__z#h8l%AuN8ienLFF=uH(cO;es#8t;?#1O zZX~f^@s{sR3j$CVL7?H{wD#5gZx@ojUk8;~TF&8tHIpjsDP}3dW-U`RV#Q zhH_jWn>~?f<8H~8G#-GxdXhCgJ3XMRnHW_JD5oJ2e`b2CPAm?D&6CzkAdneGh+%|u z$~eq4XAKlxoyrbD9ct7%3;!2?o~Sax+;8)DA6 z$spzW6}>Fs(VbQeBvGxYghKpgi7*HS5U4a$<}xm+Uca&oCy79lN*{d-!{|0#sG~#< zkfAW@N-}INj5SmbP*`w2MnEh8yvUd(@z427W|}BvQf5#DrJESVP+fmpu#YjQRY6={ zl^pmvU5+G0Te9v-r;Gd0lSFP;U0+y$5*Y0n0uX(}-DoKzXu!?n>u#JNQXbHeO;pzx z9wm|+z59-VAGP$_!J z)qczCi`%QcDUX`TqBZIo${t^6s@|a3LEmB&XDzq9RRIfQ7bUayG`%Q1hZKd*77VPe zFARwGh28rS(s<#MGQ%uePTc|%hkyLz9wT0K#+gRPd zSby*o5t_l>Dk}NBH&Q1BIS0WcE7(qGaPBCaP{m-uX5l#V#@m za!7(O)%!@e<&^dvhnmmgfI=AYy$qppi~D*!p!9`coP5tDZ3(D37C=c77Y~FTg{*;y z;sw+_@9U+^%7yv56!n9?JChk9F&Q=dEAVEX9ImyZVJJ=R>rHP$HJFG=gP-q^+3ZvU ze5zc9i5390snk|!v1B{<_0(HwwMv*U0wmw|G65)vT0$=^&4dNuW-C3YZw21hi?lgn zxV<2e^xn$|KnDspgSc?C84V*bs@}f{pk#vD5SWODu(VPW#VFzXAUqj@z$K618kNYa z>kI#<_N+>q#vjEC$}>rfqhRmFF!?p(*Wrz7sID&#kiC)l<9;Q9QV3-y=Ax&NunxQh z&lsh1Q92M3owxVrb2I)atfQ_&elx-ZRrP$0MiMD5%1bqCRS~FcMn;1=KfaD0_SV<} z2UbSsbsT?mdWI!p_^S-a<+u5(AQS6kYMyjC(_hW>B52o&gg&J~B+pG{w0iuCN7p#! z6N*ISskTxR)3K)>rXNRbL}AN%_^+k=S1^8i{piRE_Hg}N97O|DkTM}Z3EJL7kmVT;?E4lQyWFmAegGJqABs;*P~0d9Z^k-|B^0mEclV$ zBn1d6B1Rz4spcX3x%caRGb9~T@E<82E>jXnRm}HF|CDca=kSDLNEv=}{k;JqCqIGx z#`g;R-u8d@W%MV2@UesY0mv_8YTCaD7l3vW2)}{2py4McAdW$Vm=IWPU^PtzJd`~e zdMog%BvTz?z$B$SC{KXo!Np-x%LssWeSZKGPYq$C`as)}q3gUtn{psZ@OVH83=UaZ zUeSn#hQ3`dLjX3pI~XAx7}Zin=thIYnK4cnJRd!;9nif2KIQ?UQT%{lysm7SfZmA$ z+cTS0?5C`7#;f-)SwN*H4m&qU7dTFtgaZRg{iV6mkQPH#l#{DgTz~)V@d-_yfStQV z{aYr&>8GR>w{EbVEy!dxd1&Ro3KmcTh{4v-z{(&^%AHL1QOwM6by=0$Q(B^`&ZD2nLzHa!#>fYBTq9&nS@PaVg`Kp zyoxxa8eAd>v95RkMN%PK3Mz?Fk`um^L(|4UQHWThFhp$dtJNEn1|)fpV2sPhfSZir z0EdM_COdf#R6xG-SrO?54BR_lkdgbKvB+Q;8@d4*gH&?-c$J7yn4ixJSNDI901gce z9-&Qo>QaoSzNH2Q31COqmQL~+fa)E*58$(e7}8nSPsF^?FddWTU#?I2LzN~&29fjS z?F$l5z*TaknDy*zCS4g4IZ*+Vj9yJoS#;@@kcW%BU*9N2jw2K4L@gW+%cbGBq8x%p zk3Z0pUuBS3sr&k9*D`=W7o}_`*?cCjpp;4L6fv=Q2(Ee@hQ$899`yjL>EwZYb#mp~ zAUTqH(iJzfg{mq;1Jy))?XCCqnPQ&_EK)gyWBLl%q4;ZwP>0MB~ zV95zdsHJETg$AW0^E}Jzi_^pHW^hfus%U^<(&%_o)me=Ud=u!F*78b-I+gjOz)xuE zdkBK1R=|v4gB)f~DcrRWH0+chHHQtX-OHzIj|`AvI-STm2uK)|f)v=TxlhgJizg|x zSMH;t5b<|F#Z)Rkg_f-uXe~4f89|Qvi za3L~i;{M8f3LUgG%P^pXN;@dzNZ=e^Gqk0z7Co4N`!TPgy8m5;5T76>IRc!m=ABeN z{cMo8BS{KC*r_IFf2toSycdxh%G6_M`%fnU_l3>U#43|O-!ef{b}T}9|ANBv*E9et zR~gA7>Jp{Wzo=r6))RrG#_V#tS*>6BRY>>3wCfcRtpQL9JVYoKBn^?Fpcs_|uOiS? zrSJWR5U{ox8I-Tk0Kg>eoUXu?kNV1@R9Q2Xv3&kgNgSCf*=ZEZY1#85s8kXP$HCg^ zPswxTD4kKE`ZRzeL;>mYjv0&o6LChpMO^f3zUnG{s6S{SMBUeeJS<-Nr*Xy^Qgg~M5Jlm1`9=bdP$!Ho6L?7Xz8-SM z-#I0Ky!l^gzk@cMW>Gv{;Qr_57k;bvFWd*EJKZG0LEX?y38ygYkzsy1DTzx`mdZr# z^7`WTNOrrf)WXy9{Z2gqEFV;ZP(({|p)w1S)tBGsL+U5;!7mXlT5i+pDOj;ik`BZZ zQc#7#`&HK$%a3a__+02p1=`RsAsYutIj59IQ>&Gfefw5kb$#Kzb&(M*^1MdAIAvT= zjvvIK4o6w6co2zYr6TaQ@5`@wklq7ocXC+)6r7QMxfqWkD(Vc)n2j3!y8dke@>%0# za5~m#^^A71R@7iBXs{O@I4WE-kZN9a{@%Aw%r$;VcxI}OYQ7807cxCLmt0!;ER(sk zG*rjGte8KmvPDgx7LyssZe?*rDDwv5Ak5G!%3;qOs>O=GcWCD zeP4g8EVoOjiefd&8_Ajjkxw&)gg^#f(gN#JTwn6f9xogZ+Ej+bb?XAL<<7nLkUp|s zuV>zWb$P)ap&8*Or{4HbvEJZ9x(zxz1Jokzgy+1UK85P~BKl@zk7d`8k#Bz?x&T(x zA3lSETsehfa0IHad1#irw=W+nay$hNS_E0EEC?QgVdSzIBaM{Ehf>PrgK+Nag=;6& z$rFC$A2!|nuuVwW2nd`65f!GWp1&|3XP#uw>E)FY3PiVE?)Y*Rw`13g|3y_)&NCG{U-dDDAUyt~WdIIPVhVu-1LAe2I50TJb zWilJ|UYf7DdvDh(5Tfs7_}~xrQYc4uD0veSQf|}b0^p2OfGFR;*gi)VgXP&a1+eMK)YrUziWQqo61G%R~*KN*4?Kl$@5&a$`Rw_*A1L zCB$#b2P6V-PMZ}I>WH?v+(?d62ryVR{VAd`2Bx}yVLp%>uqI1@^prnf5S2UZKV_I0 z%$W0476_rbzAzseLRA9_Too1=5_h8n%$rF(1tMvQIgrqCWxj<5klU6Sb3PCgn-NF5 zOl35_Iv=O-0tDPBf$C!hT=M@{K_%|F%}AaPaGkV%UymHijyE7>wb&Q(r&IwvGL5@oFv0%*R;gIk{=i!%6fF~#eH z-G~mXAt$f)hpAP1jp`I|%3x~w3rTS0Jqqdu!64Uk0P;OopHvnyTpb(Gw}3y96lu=n zl*{F-6kxVH)C<~4uLu%N76n3pD@1K-uXJ4bUR{Y@C}i*F&!>tV?|jJuVXIJ?VU!wz zG2cA7BuMGr39Of|@V?JPg^x)yS15cgfQZ(4#;Q^vjdFV_WF%fw zIA^Nq41pq(@%$*U^rh)ZD&N1PK2+^6#gdGS5fuE4bK`nwdLDR03oAFFZoR7GS5hB_ z?I1s&qWN_2N_^D-5C<@W4_Wq)q+9eH`l8uB}nVz>amfVR$s*+^T_A2gS1k3j?wlDDo&7Ut=M2l;lwq%gs z$$VeZB~V7_L@HL77v2X_monh{P;`bb(h~9-)=}OaSB%e0Q(Rl${{8VYwaeEMWjds4 zA?X>CyoYXwg4a}qbh0|UPE&RN!hG@_AXB_CrA`6FpG{OPvEaO!Q-BoG1y~7C&6X{e z58$P<#sK^K(H>-IgK`xanL>inr5POhlPdEmA3u#dW@xB0P%cKu0k6r{jSx`rm5kBE zXQXTS_+{xv4?pn=axttO@rp%M!&LamJ4+lROXhwn>nSsT%p$rnswaUfA0en?e#OoN8(6-z~1G(G6uHgQi4Y{{ihKV_8JsdJ%8c6Lu_Z>431l- zOaRP@QV7SX2YAlY+Y=ui8cNf^vG zV-)eIYy*U@zC>uwY=Qm&WGoHv3vhvvA}1mjm8gX+pH!lxcz&UyvfolWA;XUL%s14$ zf(lObtAbtrk*-#gOgup(xa9(r+6i@O+B0OMV6Ef`Yx{zLN7POtdyFbK9l#ZJ#lCL? z%=f~Ty>+ogdLI@qUom4*;1}vgFfkm~guCkgg#lx2qq~BcTQyi0im;(SRsu&zT zEdoLYL8KU*;8tCK8-Ni8X`q#{;Am8C=xt;zd@>PTlw@i)c&C~=0y6LCCl8?URdC=2 z@*M?&UDaM}Wj-DRm2zDK_*L#t$#7rKIxvL@5<+fc8}zRdoFPOoN{D}l1FA!g)%H__ zU(mp3G?<|1BibBL7#NfEhQh$bJW zE0~q*S87Mp`|Wh}nNpP`VH!7Zg*tll0h?+T@|$?LLjPq8E6KDO3{);!tqnx*2wY|c zD`qGc87Ubi{m&`+7p3D7Sq@ zjYg6SfE<0!H=}V85C?j?63 zI^Xxtta1U$|06o(w-JvrAmvY^DX=QUvvSY6awW-oyIyHDA8&=4GD|7l;9lJ8h`XjK zm|mlQ6*bQciv4@PdzufW>2lwY2PQJ%ojt#tFLFPNuJ$OYQj{#OF2DzJB%iX?{7-oe zf1*$YT~Ey-ox%@SM^lNCtx%$Ok?18c0klQb#Yi;=bN# z$=H1iu`lpR`tluK$QfiXM&0mirF?pb3VbMw+}9&VnjJe!EMewZKG+}{tGB41kxjv4 zfGOQndHBcz@9P!JbXi3Ik9b6pyRpSe@yB*byzs(0IVc%_PcX>01(*P=;v%DN`4D@Z0SH zWR(i&F9mxb$8T9WdS$sR_9os(Sd+b%vByA+`+8H))d_F*D5QT9&s6hFTWKbQ= zi8KPJdi-Mh0u4zoktHb}qZ<`WGE+tVgGX04KrnH z1pn%2oEc68=c@m|zP{LgM~&iHfmgMGeCx^I6QRl?k`MwUJgTZf{rtuD(+6XRO}>f4 zK|Y%{Yw8fWbp}`=a81Gz$M=#s(P?!*e})rq6ZznYPPwQxVJ_q&^&mou2b32R#?{YX z#GZs&_=(*RAX`AEhUA>XB_trsN%vaW82Cc8c^`X(&$^%tRv2 z035GUA6fF-^ZTord=P0hxPZTVYBci!Xoprcl?H{Nd8=o*me2!Kr?k{}B8AHQ=zP6K zGo!bO*B1~4Ut`=$*DFnUyFF^ic=9YAH%+L7`~_5Ft~L$;;=&{)*tE*~+@cpi+4dRR zB3>gJU;v0i?ad=;0|(#q{G|L?sm5?i@Ap?*SFcogpq;|A!hsq$<<62utaD0_zV1!C zSecLOZ{`rJg0b?^X%Ef+$r$x);C|te91R}=iyE*@UVQN7{Q+>|(wFcpekY7Uz;I?d z=pw`gV7@X@8J|<`f7!g(=-Kg#;l5GL1S}>|G6fP=MJ$yI?QAH(3VRS@-1n!6r#eIL z$dY}vN}T|hNIsuAyy4Ty@u?oF4JfIT0AH<1dVgVb*^68b0A;gku6~CG5O1L@RX%@7 zolq)cjq=4Y^bzl60J*R!U((c-i9q>0l+?C5KP7cy{7MQhHkH3oKV!>J;sD#Ilqx(a zY0%9TDYyJz=Tt}G6$H1XhuV22uc3gDU9|t8W(T8g2eShtaf;M>;zEq== z|C$1_hIbbIq9s+z_fM+(7w(tll9i=ok_N~OjuCemoT7tG1U)46^kbF(DMTygaww6Z ztceZ;IRS)51#Om(Z%TYX5c!b7u9f+@{yzT_6b0stk=1B`bm2%fJ1O|-XVd;HhtNo< zj90xtasG7|U<@)PTFu1FStg*!N&Y99v#@9&sR=@_yuSJhQiukFmplQqnJ(7Qk4!Rs zP@<3BFb2Gu-r{T(FCa5_+~YO)DNiel2Rh6?u%_|6SjY@^tX`m;pXcL+ie`l3I2SyN z5|oXP_du9=1`9w0poUPrK(T{zOMoNgenkgeEJ#O^FO)1bE6zXTDJ3L7U4MIULmQ(o zK{m1yJU_BWwLlt-jKm>GX*_?Vk=6Z+4Tv;5YAqsV8dDi`KB8c360aa3A}SFf?M}S` zg#&SDtU61lfF+V4{V{qblMnEJ!mmK;9H}0c_b*@HumfEpL~|&758ufJ$-NW*QCF`5IHNd)fy90psF6H$Ez}6r z{R;<#YEW}fDA#%~W%MaqSx5>=`R;?9+Hv6GtHvn{eXqX+1#cP29Re$Dei4m7NA^TH zBa=xR1$|gS^`gHDKcaXMf_&;=X8dQK}ji;ijIe9_^a)& zD!0UW$W8PZii__j6cbM54FoSv0L5=AdfG6`^)JZJe1KBw3+9Q~QX!c#+$s9%+ikc8 z@>>JySj11)-vJ;k=Q`XN9H$+?km-l`UrA-^59T`vQaEr``3!6pW|H)Apo&&}he$4M&QMt@ToIx20L3?)u-zay z7)Pm(aisr(IXzRpi{}J}eAh*-HKKZfTltu#y{L6)hSb0LJm3{4o==(Q(>nsA{6+!2 z`Vw?ufFcg~j=4zdkrWJ%=#LmllAwGHF`8EHsdOMW_Z8lZvQMdo{Y91=n%>)=WuQCD%v9=QB%_oI1s7bwYNW(Hk8 zAqMp$WYL`jkg+9_#Ofo`x9W#xRfPAq)ytx{N76ZIQOx)yo+RHTPc6UFJl@2Ym&aC8 z{SvsTP-#;l+0$nx{Y_FYnIb`#Nms+0Zr&=~nyD4M2gXgXd*Qn5huYFx z)%eHu+xNe;zc#-%J|Ar8yXqWmr05Wpj)>5)qDTbk95x;kv%FBBJjEEd@L#W1CtX0 zt+rI%O#W#GaJ(4sKmADm@;Z?|(VIB#5e$3$_ywVuVPvLf^bPXjS3mwwKQiP;Ku|(q zhzVio&?{qTo&W~*vTHv~F&Z5NCBBjg#gBYDUKRw9raQnuI>{t%hgFQUFa}f>un$ z75-;pWJp>Qq+$Z})3P$&3gi9LFJwnepiiL}WHnHLVgv^lH85}w<@rn=Y?9lP>Q@)D zs^`UsSYNYJZ=iK@1}U=X0Qj$BYiDq;(4+a9f}Z5AWnpyGXgi3sH9i>niy{$R`(<)`i`>Z7E+G}}sM z>|HS9NmXy(KEd&P_vaJ5{>aM-R&UVoe=h0z`X;>h6sF02f z_IonRH!N2F!pq@{=v%fF?Tw1P=p&k&&4|X<(uk3Vhsbf>rV-b_@M23!eHrxvIT*U9 zYAmHU&nCmf6mZl@7(+xRr3=Y=zPM8gHToNE>|O$DK-@&N8SQ8qiZM}2G{!uXhp+q9 z;j4CZ6+S&?rV%;9$XS`3!io(@h0v#ZAR4UR|LR9x&J#DZ8Uo=_Qp86}_`r#ytQ?aW zk3l&~)VQL;w)%I&lv103A)QsFO93nssdSL`KI+3Mh)5A@nvV7J#pP>+{ZO>C=z$U$ zafcm&3Pwh0We7!ZoPcL}uAWcu;nf^43&;;rP~C4*SkoaA)$*YT9dhP#a8qbkAGvy^ z#-5wmXEmM7P@rkpAYuijLPT0kbCrlQRM?s)Ta`!BrMfSZ%mpUP`uPIm>kEh@$~GEiHW^ST++pqMhJ(<0!KN|?DC0G-*c4ZD7N{L z;?C3SwhMjkMr?qP!-#ro6R{ix(l8K3KL<47S>%b;#SZY(oBIYd@uauJ9m{=8epA)T zcYy^7P*gc1vb3W-`_r!u@bhB$EmO<@A6NycAblVsQNU3M#t-CU($Dm`x-*M^UMzQ| zdm>l!A%^hIJXk9SXDKuR^aC6Sb9ws^)$4-U?w7~LFo)@+Ucr?ZMJc3cmwvWf%Ydb; z+S0c-4E=hs&3azUG*xMzY7O-(GYWJOsJG&i(*K%wD`WHC>nm%g%Lc`w1xCz`rEYXpl^e5!ss(aLn%}>T@p`XZ;+s emHdE)Q*J@0kXRqiLj;le*wJFeb8#NKY5pfL+>$T= literal 0 HcmV?d00001 diff --git a/swift/spec/fixtures/projects/ReactiveCocoa/Package.resolved b/swift/spec/fixtures/projects/ReactiveCocoa/Package.resolved new file mode 100644 index 00000000000..7298b48475b --- /dev/null +++ b/swift/spec/fixtures/projects/ReactiveCocoa/Package.resolved @@ -0,0 +1,52 @@ +{ + "object": { + "pins": [ + { + "package": "CwlCatchException", + "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", + "state": { + "branch": null, + "revision": "35f9e770f54ce62dd8526470f14c6e137cef3eea", + "version": "2.1.1" + } + }, + { + "package": "CwlPreconditionTesting", + "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state": { + "branch": null, + "revision": "c21f7bab5ca8eee0a9998bbd17ca1d0eb45d4688", + "version": "2.1.0" + } + }, + { + "package": "Nimble", + "repositoryURL": "https://github.com/Quick/Nimble.git", + "state": { + "branch": null, + "revision": "c93f16c25af5770f0d3e6af27c9634640946b068", + "version": "9.2.1" + } + }, + { + "package": "Quick", + "repositoryURL": "https://github.com/Quick/Quick.git", + "state": { + "branch": null, + "revision": "e206b8deba0d01fce70388a6d9dc66cba5603958", + "version": "7.0.0" + } + }, + { + "package": "ReactiveSwift", + "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift", + "state": { + "branch": null, + "revision": "40c465af19b993344e84355c00669ba2022ca3cd", + "version": "7.1.1" + } + } + ] + }, + "version": 1 +} diff --git a/swift/spec/fixtures/projects/ReactiveCocoa/Package.swift b/swift/spec/fixtures/projects/ReactiveCocoa/Package.swift new file mode 100644 index 00000000000..711e41cd0c8 --- /dev/null +++ b/swift/spec/fixtures/projects/ReactiveCocoa/Package.swift @@ -0,0 +1,16 @@ +// swift-tools-version:5.2 +// The swift-tools-version declares the minimum version of Swift required to build this package. +import PackageDescription + +let package = Package( + name: "ReactiveCocoa", + platforms: [ + .macOS(.v10_13), .iOS(.v11), .tvOS(.v11), .watchOS(.v4) + ], + dependencies: [ + .package(url: "https://github.com/ReactiveCocoa/ReactiveSwift", from: "7.0.0"), + .package(url: "https://github.com/Quick/Quick.git", from: "7.0.0"), + .package(url: "https://github.com/Quick/Nimble.git", from: "9.0.0"), + ], + swiftLanguageVersions: [.v5] +) From 0c8275fbd880306f4baec2a9b31e753908a7e48c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 27 Jun 2023 17:49:57 +0200 Subject: [PATCH 7/7] Implement MetadataFinder for Swift --- swift/lib/dependabot/swift/metadata_finder.rb | 19 +++++ .../dependabot/swift/metadata_finder_spec.rb | 75 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 swift/spec/dependabot/swift/metadata_finder_spec.rb diff --git a/swift/lib/dependabot/swift/metadata_finder.rb b/swift/lib/dependabot/swift/metadata_finder.rb index 86dd09efa62..1c472e47b42 100644 --- a/swift/lib/dependabot/swift/metadata_finder.rb +++ b/swift/lib/dependabot/swift/metadata_finder.rb @@ -9,6 +9,25 @@ class MetadataFinder < Dependabot::MetadataFinders::Base private def look_up_source + case new_source_type + when "git" then find_source_from_git_url + when "registry" then find_source_from_registry + else raise "Unexpected source type: #{new_source_type}" + end + end + + def new_source_type + dependency.source_type + end + + def find_source_from_git_url + info = dependency.source_details + + url = info[:url] || info.fetch("url") + Source.from_url(url) + end + + def find_source_from_registry raise NotImplementedError end end diff --git a/swift/spec/dependabot/swift/metadata_finder_spec.rb b/swift/spec/dependabot/swift/metadata_finder_spec.rb new file mode 100644 index 00000000000..12f9abacd4e --- /dev/null +++ b/swift/spec/dependabot/swift/metadata_finder_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/dependency" +require "dependabot/swift/metadata_finder" +require_common_spec "metadata_finders/shared_examples_for_metadata_finders" + +RSpec.describe Dependabot::Swift::MetadataFinder do + it_behaves_like "a dependency metadata finder" + + let(:credentials) do + [{ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }] + end + + let(:finder) do + described_class.new(dependency: dependency, credentials: credentials) + end + + describe "#source_url" do + context "with a direct dependency" do + let(:dependency) do + Dependabot::Dependency.new( + name: "reactiveswift", + version: "7.1.1", + requirements: [{ + file: "package.swfit", + requirement: "= 7.1.1", + groups: [], + source: { + "type" => "git", + "url" => "https://github.com/reactivecocoa/reactiveswift", + "ref" => "7.1.1", + "branch" => nil + } + }], + package_manager: "swift" + ) + end + + it "works" do + expect(finder.source_url).to eq "https://github.com/reactivecocoa/reactiveswift" + end + end + + context "with an indirect dependency" do + let(:dependency) do + Dependabot::Dependency.new( + name: "reactiveswift", + version: "7.1.1", + requirements: [], + subdependency_metadata: [ + { + source: { + "type" => "git", + "url" => "https://github.com/reactivecocoa/reactiveswift", + "ref" => "7.1.1", + "branch" => nil + } + } + ], + package_manager: "swift" + ) + end + + it "works" do + expect(finder.source_url).to eq "https://github.com/reactivecocoa/reactiveswift" + end + end + end +end