class Gem::Version
The Version 类将字符串版本处理成可比较的值。版本字符串通常应为由句点分隔的数字序列。每个部分(由句点分隔的数字)都被视为自己的数字,用于排序。例如,3.10 的排序高于 3.2,因为十大于二。
如果任何部分包含字母(目前仅支持 a-z),则该版本被视为预发布版本。在第 N 部分包含预发布部分的版本排序低于在第 N-1 部分包含预发布部分的版本。预发布部分使用标准的 Ruby 字符串排序规则按字母顺序排序。如果预发布部分同时包含字母和数字,它将被拆分成多个部分,以提供预期的排序行为(1.0.a10 变为 1.0.a.10,且大于 1.0.a9)。
预发布版本在正式版本之间排序(从新到旧)
-
1.0
-
1.0.b1
-
1.0.a.2
-
0.9
如果你想指定一个版本约束,其中包含 1.x 系列的预发布版本和正式版本,这是最好的方法。
s.add_dependency 'example', '>= 1.0.0.a', '< 2.0.0'
软件如何变化
用户期望能够指定一个版本约束,让他们对新版本的库能够与他们的软件正常工作(如果版本约束为真)以及不能正常工作(如果版本约束为假)有一个合理的预期。换句话说,完美系统将接受库的所有兼容版本,并拒绝所有不兼容的版本。
库会以 3 种方式(嗯,超过 3 种,但我们先集中讨论这 3 种!)发生变化。
-
变化可能仅是实现细节,对客户端软件没有影响。
-
变化可能添加新功能,但以一种早期版本编写的客户端软件仍然兼容的方式添加。
-
变化可能以一种旧软件不再兼容的方式改变库的公共接口。
此时给出一些示例是合适的。假设我有一个支持 push 和 pop 方法的 Stack 类。
Category 1 变化的示例
-
从基于数组的实现切换到基于链表的实现。
-
为大型堆栈提供自动(且透明)的后备存储。
Category 2 变化的示例可能是
-
添加一个
depth方法来返回堆栈的当前深度。 -
添加一个
top方法来返回当前堆栈顶部(不改变堆栈)。 -
修改
push,使其返回被推送的项(之前它没有可用的返回值)。
Category 3 变化的示例可能是
-
修改
pop,使其不再返回值(你必须使用top来获取堆栈顶部)。 -
将方法重命名为
push_item和pop_item。
RubyGems Rational 版本控制
-
版本应由三个非负整数表示,用句点分隔(例如 3.1.4)。第一个整数是“主”版本号,第二个整数是“次”版本号,第三个整数是“构建”号。
-
Category 1 变化(实现细节)将增加构建号。
-
Category 2 变化(向后兼容)将增加次版本号并将构建号重置。
-
Category 3 变化(不兼容)将增加主构建号并将次版本号和构建号重置。
-
任何 gem 的“公共”发布都应具有不同的版本。通常这意味着增加构建号。这意味着开发人员可以整天生成构建,但一旦他们进行公开发布,版本必须更新。
示例
让我们通过上面 Stack 示例的项目生命周期来工作。
Version0.0.1-
初始 Stack 类已发布。
Version0.0.2-
切换到链表实现,因为它更酷。
Version0.1.0-
添加了
depth方法。 Version1.0.0-
添加了
top,并使pop返回 nil(pop以前返回旧的顶部项)。 Version1.1.0-
push现在返回被推送的值(它以前返回 nil)。 Version1.1.1-
修复了链表实现中的一个错误。
Version1.1.2-
修复了上一次修复引入的错误。
客户端 A 需要一个具有基本 push/pop 功能的堆栈。他们编写了原始接口(没有 top),因此他们的版本约束看起来像
gem 'stack', '>= 0.0'
基本上,对于客户端 A 来说,任何版本都可以。对库的不兼容更改会给他们带来麻烦,但他们愿意承担风险(我们称客户端 A 为乐观)。
客户端 B 与客户端 A 相同,除了两件事:(1)他们使用 depth 方法,(2)他们担心未来的不兼容性,因此他们编写的版本约束如下:
gem 'stack', '~> 0.1'
depth 方法是在版本 0.1.0 中引入的,因此该版本或之后的任何版本都可以,只要版本保持在 1.0 以下,不兼容性就会被引入。我们称客户端 B 为悲观主义者,因为他们担心未来的不兼容更改(悲观主义者是 OK 的!)。
防止 Version 灾难
来自:www.zenspider.com/ruby/2008/10/rubygems-how-to-preventing-catastrophe.html
假设你依赖 fnord gem 版本 2.y.z。如果你指定依赖项为“>= 2.0.0”,那么你就万事大吉了,对吗?如果 fnord 3.0 发布了,并且它与 2.y.z 不向后兼容,会发生什么?你的东西会因为使用“>=”而中断。更好的方法是使用“近似”版本说明符(“~>”)指定你的依赖项。它们有点令人困惑,所以依赖项说明符的工作方式如下:
Specification From ... To (exclusive) ">= 3.0" 3.0 ... ∞ "~> 3.0" 3.0 ... 4.0 "~> 3.0.0" 3.0.0 ... 3.1 "~> 3.5" 3.5 ... 4.0 "~> 3.5.0" 3.5.0 ... 3.6 "~> 3" 3.0 ... 4.0
对于最后一个示例,单位数版本会自动扩展一个零以给出合理的结果。
Public Class Methods
Source
# File lib/rubygems/version.rb, line 173 def self.correct?(version) version.nil? || ANCHORED_VERSION_PATTERN.match?(version.to_s) end
如果 version 字符串符合 RubyGems 的要求,则返回 true。
Source
# File lib/rubygems/version.rb, line 184 def self.create(input) if self === input # check yourself before you wreck yourself input else new input end end
Source
# File lib/rubygems/version.rb, line 206 def initialize(version) unless self.class.correct?(version) raise ArgumentError, "Malformed version number string #{version}" end # If version is an empty string convert it to 0 version = 0 if version.nil? || (version.is_a?(String) && /\A\s*\Z/.match?(version)) @version = version.to_s # optimization to avoid allocation when given an integer, since we know # it's to_s won't have any spaces or dashes unless version.is_a?(Integer) @version = @version.strip @version.gsub!("-",".pre.") end @version = -@version @segments = nil end
从 version 字符串构造 Version。版本字符串是由点分隔的数字或 ASCII 字母序列。
Public Instance Methods
Source
# File lib/rubygems/version.rb, line 345 def <=>(other) if String === other return unless self.class.correct?(other) return self <=> self.class.new(other) end return unless Gem::Version === other return 0 if @version == other.version || canonical_segments == other.canonical_segments lhsegments = canonical_segments rhsegments = other.canonical_segments lhsize = lhsegments.size rhsize = rhsegments.size limit = (lhsize > rhsize ? rhsize : lhsize) i = 0 while i < limit lhs = lhsegments[i] rhs = rhsegments[i] i += 1 next if lhs == rhs return -1 if String === lhs && Numeric === rhs return 1 if Numeric === lhs && String === rhs return lhs <=> rhs end lhs = lhsegments[i] if lhs.nil? rhs = rhsegments[i] while i < rhsize return 1 if String === rhs return -1 unless rhs.zero? rhs = rhsegments[i += 1] end else while i < lhsize return -1 if String === lhs return 1 unless lhs.zero? lhs = lhsegments[i += 1] end end 0 end
将此版本与 other 进行比较,如果 other 版本更大、相同或更小,则返回 -1、0 或 1。other 必须是 Gem::Version 的实例,与其它类型比较可能会引发异常。
Source
# File lib/rubygems/version.rb, line 327 def approximate_recommendation segments = self.segments segments.pop while segments.any? {|s| String === s } segments.pop while segments.size > 2 segments.push 0 while segments.size < 2 recommendation = "~> #{segments.join(".")}" recommendation += ".a" if prerelease? recommendation end
与 ~> 需求一起使用的推荐版本。
Source
# File lib/rubygems/version.rb, line 232 def bump @@bump[self] ||= begin segments = self.segments segments.pop while segments.any? {|s| String === s } segments.pop if segments.size > 1 segments[-1] = segments[-1].succ self.class.new segments.join(".") end end
返回一个新的版本对象,其中倒数第二个修订号增加一(例如,5.3.1 => 5.4)。
预发布(alpha)部分,例如 5.3.1.b.2 => 5.4,将被忽略。
Source
# File lib/rubygems/version.rb, line 397 def canonical_segments @canonical_segments ||= begin # remove trailing 0 segments, using dot or letter as anchor # may leave a trailing dot which will be ignored by partition_segments canonical_version = @version.sub(/(?<=[a-zA-Z.])[.0]+\z/, "") # remove 0 segments before the first letter in a prerelease version canonical_version.sub!(/(?<=\.|\A)[0.]+(?=[a-zA-Z])/, "") if prerelease? partition_segments(canonical_version) end end
在第一个字母之前或版本末尾移除尾随零段。
Source
# File lib/rubygems/version.rb, line 247 def eql?(other) self.class === other && @version == other.version end
仅当 Version 的精度相同时,它才与另一个版本相等。版本“1.0”与版本“1”不等价。
Source
# File lib/rubygems/version.rb, line 408 def freeze prerelease? _segments canonical_segments super end
Object#freezeSource
# File lib/rubygems/version.rb, line 267 def marshal_dump [@version] end
仅转储原始版本字符串,而不转储整个对象。它是一个字符串,用于向后兼容(RubyGems 1.3.5 及更早版本)。
Source
# File lib/rubygems/version.rb, line 275 def marshal_load(array) string = array[0] raise TypeError, "wrong version string" unless string.is_a?(String) initialize string end
加载自定义 marshal 格式。它是一个字符串,用于向后兼容(RubyGems 1.3.5 及更早版本)。
Source
# File lib/rubygems/version.rb, line 295 def prerelease? unless instance_variable_defined? :@prerelease @prerelease = /[a-zA-Z]/.match?(version) end @prerelease end
如果版本包含字母,则该版本被视为预发布版本。
Source
# File lib/rubygems/version.rb, line 310 def release @@release[self] ||= if prerelease? segments = self.segments segments.pop while segments.any? {|s| String === s } self.class.new segments.join(".") else self end end
此版本的发布版本(例如 1.2.0.a -> 1.2.0)。非预发布版本返回自身。
Protected Instance Methods
Source
# File lib/rubygems/version.rb, line 417 def _segments # segments is lazy so it can pick up version values that come from # old marshaled versions, which don't go through marshal_load. # since this version object is cached in @@all, its @segments should be frozen @segments ||= partition_segments(@version) end
Source
# File lib/rubygems/version.rb, line 424 def partition_segments(ver) ver.scan(/\d+|[a-z]+/i).map! do |s| /\A\d/.match?(s) ? s.to_i : -s end.freeze end