Class: Dependabot::Vcpkg::Version

Inherits:
Dependabot::Version
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/dependabot/vcpkg/version.rb

Overview

vcpkg has four version schemes and a #<port-version> suffix, and it refuses to compare versions whose schemes are incomparable. This class mirrors compare_any in vcpkg-tool's src/vcpkg/versions.cpp: a version's comparison class is inferred from its text, dates compare with dates, dot-versions with dot-versions, and arbitrary strings only with themselves.

See https://learn.microsoft.com/vcpkg/users/versioning#version-schemes

Constant Summary collapse

DATE_PATTERN =

version-date: an ISO-8601 date with optional dot-separated disambiguators.

/\d{4}-\d{2}-\d{2}(?:\.(?:0|[1-9]\d*))*/
DOT_PATTERN =

version and version-semver: dot-separated numbers with optional semver pre-release identifiers and build metadata.

/
  (?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))*
  (?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?
  (?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?
/x
STRING_PATTERN =

version-string accepts arbitrary text. # is reserved for the port version.

/[^\s#]+/
PORT_VERSION_PATTERN =
/(?:\#(?:0|[1-9]\d*))?/
VERSION_PATTERN =
/#{STRING_PATTERN}#{PORT_VERSION_PATTERN}/
REQUIREMENT_VERSION_PATTERN =

Requirement strings prefix a version with an operator, so the string scheme must not be allowed to swallow one.

/(?![=<>~!])#{VERSION_PATTERN}/
ANCHORED_VERSION_PATTERN =
/\A\s*#{VERSION_PATTERN}\s*\z/
ANCHORED_DATE_PATTERN =
/\A#{DATE_PATTERN}\z/
ANCHORED_DOT_PATTERN =
/\A#{DOT_PATTERN}\z/
DIGIT_PATTERN =

A vcpkg version always contains a digit. Requiring one keeps git refs out: a registry baseline's previous version is a ref such as master, and MetadataFinders, Labeler and UpdateCheckers::Base all use correct? to tell a version from a ref. Treating master as a version-string made ReleaseFinder compare release tags against it and discard them, dropping the release notes from baseline pull requests.

/[0-9]/
GIT_SHA_PATTERN =

A handful of ancient ports use a bare commit SHA as a version-string, but so does every vcpkg registry baseline. Treating those as versions would stop UpdateCheckers::Base from recognizing a baseline as a git SHA, so this rejects them. Digit-only strings still count, because they are valid relaxed versions.

/\A(?=[0-9a-f]*[a-f])[0-9a-f]{7,40}\z/
DATE_LENGTH =
10
SCHEME_CLASSES =

A version's scheme is declared in the versions database, not implied by its text: vcpkg publishes plenty of dotted values under version-string. Inference is only a fallback for values that arrive without one, such as advisory version lists.

T.let(
  {
    "version" => :dot,
    "version-semver" => :dot,
    "version-date" => :date,
    "version-string" => :string
  }.freeze,
  T::Hash[String, Symbol]
)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(version) ⇒ Version

Returns a new instance of Version.

Raises:

  • (ArgumentError)


63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/dependabot/vcpkg/version.rb', line 63

def initialize(version)
  @version_string = T.let(version.to_s.strip, String)
  raise ArgumentError, "Malformed version number string #{version}" unless self.class.correct?(@version_string)

  text, port_version = self.class.split_port_version(@version_string)
  @text = T.let(text, String)
  @port_version = T.let(port_version, Integer)
  @comparison_class = T.let(self.class.comparison_class_for(text), Symbol)

  @dot_parts = T.let(nil, T.nilable([T::Array[Integer], T::Array[String]]))
  @date_identifiers = T.let(nil, T.nilable(T::Array[Integer]))
  @natural_key = T.let(nil, T.nilable(T::Array[T::Array[T.any(Integer, String)]]))

  super(self.class.numeric_surrogate(text))
end

Instance Attribute Details

#comparison_classObject (readonly)

Returns the value of attribute comparison_class.



87
88
89
# File 'lib/dependabot/vcpkg/version.rb', line 87

def comparison_class
  @comparison_class
end

#port_versionObject (readonly)

Returns the value of attribute port_version.



83
84
85
# File 'lib/dependabot/vcpkg/version.rb', line 83

def port_version
  @port_version
end

#textObject (readonly)

Returns the value of attribute text.



80
81
82
# File 'lib/dependabot/vcpkg/version.rb', line 80

def text
  @text
end

Class Method Details

.coerce(other) ⇒ Object



169
170
171
172
173
174
175
# File 'lib/dependabot/vcpkg/version.rb', line 169

def self.coerce(other)
  return other if other.is_a?(Vcpkg::Version)
  return nil unless other.is_a?(String) || other.is_a?(Integer) || other.is_a?(Gem::Version)

  string = other.to_s
  correct?(string) ? new(string) : nil
end

.comparable?(left, left_scheme, right, right_scheme) ⇒ Boolean

Returns:

  • (Boolean)


150
151
152
153
154
155
156
157
158
# File 'lib/dependabot/vcpkg/version.rb', line 150

def self.comparable?(left, left_scheme, right, right_scheme)
  left_class = comparison_class_for_scheme(left_scheme) || left.comparison_class
  right_class = comparison_class_for_scheme(right_scheme) || right.comparison_class

  return left.text == right.text if left_class == :string && right_class == :string
  return false if left_class == :string || right_class == :string

  left_class == right_class
end

.compare_identifier(left, right) ⇒ Object



193
194
195
196
197
198
199
200
201
202
# File 'lib/dependabot/vcpkg/version.rb', line 193

def self.compare_identifier(left, right)
  left_numeric = left.match?(/\A\d+\z/)
  right_numeric = right.match?(/\A\d+\z/)

  return left.to_i <=> right.to_i if left_numeric && right_numeric
  return -1 if left_numeric
  return 1 if right_numeric

  T.must(left <=> right)
end

.compare_identifiers(left, right) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
# File 'lib/dependabot/vcpkg/version.rb', line 180

def self.compare_identifiers(left, right)
  left.each_with_index do |identifier, index|
    other_identifier = right[index]
    return 1 if other_identifier.nil?

    comparison = compare_identifier(identifier, other_identifier)
    return comparison unless comparison.zero?
  end

  left.length <=> right.length
end

.comparison_class_for(text) ⇒ Object



114
115
116
117
118
119
# File 'lib/dependabot/vcpkg/version.rb', line 114

def self.comparison_class_for(text)
  return :date if ANCHORED_DATE_PATTERN.match?(text)
  return :dot if ANCHORED_DOT_PATTERN.match?(text)

  :string
end

.comparison_class_for_scheme(scheme) ⇒ Object



135
136
137
# File 'lib/dependabot/vcpkg/version.rb', line 135

def self.comparison_class_for_scheme(scheme)
  scheme && SCHEME_CLASSES[scheme]
end

.correct?(version) ⇒ Boolean

Returns:

  • (Boolean)


95
96
97
98
99
100
101
102
103
104
105
# File 'lib/dependabot/vcpkg/version.rb', line 95

def self.correct?(version)
  return true if version.is_a?(Gem::Version)
  return false if version.nil?

  string = version.to_s.strip
  return false if string.empty?
  return false unless DIGIT_PATTERN.match?(string)
  return false if GIT_SHA_PATTERN.match?(string)

  ANCHORED_VERSION_PATTERN.match?(string)
end

.new(version) ⇒ Object



90
91
92
# File 'lib/dependabot/vcpkg/version.rb', line 90

def self.new(version)
  T.cast(super, Dependabot::Vcpkg::Version)
end

.numeric_surrogate(text) ⇒ Object



164
165
166
# File 'lib/dependabot/vcpkg/version.rb', line 164

def self.numeric_surrogate(text)
  text[/\A\d+(?:\.\d+)*/] || "0"
end

.split_port_version(version_string) ⇒ Object



108
109
110
111
# File 'lib/dependabot/vcpkg/version.rb', line 108

def self.split_port_version(version_string)
  text, port_version = version_string.split("#", 2)
  [text.to_s, port_version.to_i]
end

Instance Method Details

#<=>(other) ⇒ Object



245
246
247
248
249
250
251
252
253
# File 'lib/dependabot/vcpkg/version.rb', line 245

def <=>(other)
  other = self.class.coerce(other)
  return nil unless other

  comparison = compare_text(other)
  return comparison unless comparison.zero?

  port_version <=> other.port_version
end

#comparable_with?(other) ⇒ Boolean

Returns:

  • (Boolean)


217
218
219
220
221
222
# File 'lib/dependabot/vcpkg/version.rb', line 217

def comparable_with?(other)
  return true if dot? && other.dot?
  return true if date? && other.date?

  string? && other.string? && text == other.text
end

#date?Boolean

Returns:

  • (Boolean)


208
# File 'lib/dependabot/vcpkg/version.rb', line 208

def date? = comparison_class == :date

#dot?Boolean

Returns:

  • (Boolean)


205
# File 'lib/dependabot/vcpkg/version.rb', line 205

def dot? = comparison_class == :dot

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


237
238
239
# File 'lib/dependabot/vcpkg/version.rb', line 237

def eql?(other)
  other.is_a?(Vcpkg::Version) && text == other.text && port_version == other.port_version
end

#hashObject



242
# File 'lib/dependabot/vcpkg/version.rb', line 242

def hash = [text, port_version].hash

#inspectObject



231
# File 'lib/dependabot/vcpkg/version.rb', line 231

def inspect = "#<#{self.class} #{@version_string.inspect}>"

#prerelease?Boolean

Returns:

  • (Boolean)


234
# File 'lib/dependabot/vcpkg/version.rb', line 234

def prerelease? = dot? && dot_identifiers.any?

#string?Boolean

Returns:

  • (Boolean)


211
# File 'lib/dependabot/vcpkg/version.rb', line 211

def string? = comparison_class == :string

#to_sObject



225
# File 'lib/dependabot/vcpkg/version.rb', line 225

def to_s = @version_string

#to_semverObject



228
# File 'lib/dependabot/vcpkg/version.rb', line 228

def to_semver = @version_string