Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

SPT-1990 ISG Анализ версий SPM-пакетов #1

Merged
merged 3 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ GEM

PLATFORMS
arm64-darwin-22
x86_64-darwin-23
x86_64-linux

DEPENDENCIES
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Особенности surf version
В отличие от оригинальной версии, где проверяются только зависимости проекта, в этой версии еще анализируются зависимости локальных пакетов.
В данный момент поддерживаются следующие варианты объявления зависимости в Package.swift (остальные просто не будут распознаны):
- ``.package(url:, exact:)``
- ``.package(url:, revision:)``, в качестве значения ``revision`` поддерживается исключительно версия. Если указан commit-hash, зависимость не будет проверена.
- ``.package(url:, branch:)``
- ``.package(url:, from:)``
- ``.package(url:, .upToNextMinor(from:))``
- ``.package(url:, .upToNextMajor(from:))``
- ``.package(url:, ""..<"")``
# danger-spm_version_updates

[![CI](https://github.com/hbmartin/danger-spm_version_updates/actions/workflows/lint_and_test.yml/badge.svg)](https://github.com/hbmartin/danger-spm_version_updates/actions/workflows/lint_and_test.yml)
Expand Down
87 changes: 87 additions & 0 deletions lib/spm_version_updates/local.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# frozen_string_literal: true

def get_local_packages(where_to_search_local_packages)
get_manifests(where_to_search_local_packages)
.flat_map { |manifest| get_dependencies(manifest) }
end

def get_manifests(where_to_search_local_packages)
# Do not parse files added at build phase
Dir.glob("#{where_to_search_local_packages}/**/Package.swift")
.reject { |manifest| manifest.start_with?("buildData/") }
end

def get_dependencies(manifest)
content = File.read(manifest)
package_name = package_name(content)
requirement_kinds.each_with_object([]) { |kind, dependencies|
regex = package_regex(kind)
content.scan(regex).each { |match|
url = match[0]
requirement = {
"kind" => process_kind_for_hash(kind),
"package_name" => package_name
}

requirement["branch"] = match[1] if kind == "branch"
requirement["maximumVersion"] = match[2] if kind == "range"
requirement["revision"] = match[1] if kind == "revision"

dependencies << [url, requirement]
}
}
end

def package_name(content)
content.scan(/Package\(\s*name:\s*"([^"]+)"/).first.first
end

def requirement_kinds
[
"revision",
"exact",
"branch",
"upToNextMajor",
"upToNextMinor",
"range",
]
end

def process_kind_for_hash(kind)
case kind
when "exact", "upToNextMinor", "upToNextMajor"
"#{kind}Version"
else
kind
end
end

def package_regex(req_kind)
NullIsOne marked this conversation as resolved.
Show resolved Hide resolved
indent = '\s*'
dot = '\.'
less = "<"
lp = /\(#{indent}/
rp = /#{indent}\)/
req_val = /"([^"]+)"/

base = /#{dot}package#{lp}url:#{indent}#{req_val},#{indent}/

req_arg = /#{req_kind}:#{indent}#{req_val}/

from = /from:#{indent}#{req_val}/
up_to_next = /#{dot}#{req_kind}#{lp}#{from}#{rp}/
any_major = /(?:#{up_to_next}|#{from})/

excl_range = /#{dot}#{dot}#{less}/

case req_kind
when "exact", "branch", "revision"
/#{base}#{req_arg}#{rp}/
when "upToNextMajor"
/#{base}#{any_major}#{rp}/
when "upToNextMinor"
/#{base}#{up_to_next}#{rp}/
when "range"
/#{base}#{req_val}#{excl_range}#{req_val}#{rp}/
end
end
83 changes: 65 additions & 18 deletions lib/spm_version_updates/plugin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
require "semantic"
require "xcodeproj"

require_relative "local"

module Danger
# A plugin for checking if there are versions upgrades available for SPM packages
#
Expand Down Expand Up @@ -34,26 +36,51 @@ class DangerSpmVersionUpdates < Plugin
# @param [String] xcodeproj_path
# The path to your Xcode project
# @return [void]
def check_for_updates(xcodeproj_path)
def check_for_updates(xcodeproj_path, where_to_search_local_packages = ".")
raise(XcodeprojPathMustBeSet) if xcodeproj_path.nil?

project = Xcodeproj::Project.open(xcodeproj_path)
remote_packages = filter_remote_packages(project)
packages = get_local_packages(where_to_search_local_packages) + filter_remote_packages(project)

resolved_path = find_packages_resolved(xcodeproj_path)
raise(CouldNotFindResolvedFile) unless File.exist?(resolved_path)

resolved_versions = JSON.load_file!(resolved_path)["pins"]
.to_h { |pin| [pin["location"], pin["state"]["version"] || pin["state"]["revision"]] }
.to_h { |pin|
[
pin["location"],
[
pin["state"]["version"] || pin["state"]["revision"],
pin["state"]["branch"],
],
]
}

remote_packages.each { |repository_url, requirement|
packages.each { |repository_url, requirement|
next if ignore_repos&.include?(repository_url)

name = repo_name(repository_url)
resolved_version = resolved_versions[repository_url]
name = "(#{requirement.fetch('package_name', 'project')}) #{repo_name(repository_url)}"
kind = requirement["kind"]

# kind can be major, minor, range, exact, branch, or commit
resolved_version = resolved_versions[repository_url]
if resolved_version.nil?
warn("Unable to locate the current version for #{name} (#{repository_url})")
next
end

# To show only versions not commit-hashes
resolved_version = if git_version(resolved_version[0])
resolved_version[0]
else
resolved_version[1]
end

# kind can be major, minor, range, exact, branch, revision

if kind == "revision" && !git_version(requirement["revision"])
warn("#{name}: non-version values in revision are not analyzed: #{requirement['revision']}")
next
end

if kind == "branch" && check_when_exact
last_commit = git_branch_last_commit(repository_url, requirement["branch"])
Expand All @@ -64,7 +91,7 @@ def check_for_updates(xcodeproj_path)
available_versions = git_versions(repository_url)
next if available_versions.first.to_s == resolved_version

if kind == "exactVersion" && @check_when_exact
if ["exactVersion", "revision"].include?(kind) && @check_when_exact
warn_for_new_versions_exact(available_versions, name, resolved_version)
elsif kind == "upToNextMajorVersion"
warn_for_new_versions(:major, available_versions, name, resolved_version)
Expand All @@ -89,13 +116,15 @@ def repo_name(repo_url)
end

# Find the configured SPM dependencies in the xcodeproj
# @return [Hash<String, Hash>]
# @return [String, Hash<String, String>]
def filter_remote_packages(project)
project.objects.select { |obj|
obj.kind_of?(Xcodeproj::Project::Object::XCRemoteSwiftPackageReference) &&
obj.requirement["kind"] != "commit"
}
.to_h { |package| [package.repositoryURL, package.requirement] }
project.objects
.select { |obj|
obj.kind_of?(Xcodeproj::Project::Object::XCRemoteSwiftPackageReference) &&
obj.requirement["kind"] != "commit"
}.map { |package|
[package.repositoryURL, package.requirement]
}
end

# Find the Packages.resolved file
Expand Down Expand Up @@ -130,11 +159,16 @@ def warn_for_new_versions_range(available_versions, name, requirement, resolved_
version < max_version && (report_pre_releases ? true : version.pre.nil?)
}
warn("Newer version of #{name}: #{newest_meeting_reqs} ") unless newest_meeting_reqs.to_s == resolved_version
return unless report_above_maximum

newest_above_reqs = available_versions.find { |version|
report_pre_releases ? true : version.pre.nil?
}
warn(
<<-TEXT
Newest version of #{name}: #{available_versions.first} (but this package is configured up to the next #{max_version} version)
Newest version of #{name}: #{newest_above_reqs} (but this package is configured up to the next #{max_version} version)
TEXT
) if report_above_maximum
) unless newest_above_reqs == newest_meeting_reqs
end
end

Expand All @@ -152,11 +186,22 @@ def warn_for_new_versions(major_or_minor, available_versions, name, resolved_ver
}
warn(
<<-TEXT
Newest version of #{name}: #{available_versions.first} (but this package is configured up to the next #{major_or_minor} version)
Newest version of #{name}: #{newest_above_reqs} (but this package is configured up to the next #{major_or_minor} version)
TEXT
) unless newest_above_reqs == newest_meeting_reqs || newest_meeting_reqs.to_s == resolved_version
end

# Assumed using only 3-levels digital version-notation
def git_version(input)
parts = input.split(".")

return nil if parts.length > 3
return nil unless parts.all? { |part| part.match?(/\A\d+\z/) }

(3 - parts.length).times { parts << "0" }
parts.join(".")
end

# Remove git call to list tags
# @return [Array<Semantic::Version>]
def git_versions(repo_url)
Expand All @@ -167,7 +212,9 @@ def git_versions(repo_url)
begin
Semantic::Version.new(line)
rescue ArgumentError
nil
if (recovered_version = git_version(line))
Semantic::Version.new(recovered_version)
end
end
}
.sort
Expand Down
Loading