模块版本控制 |
|
_G
表)包含一个 _VERSION
变量,其值设置为 "Lua 5.1"
或 "Lua 5.2"
[1]。许多其他模块遵循类似的约定,在其模块表中存储 _VERSION
变量(以及其他变量,如 _NAME
)。
像 "1.2.3b" 这样的版本可能不是十进制数字,因此通常将其存储为字符串。这些字符串的比较可能并不简单,可能需要特殊的解析/比较函数,例如在 LuaRocks [deps.lua](参见 [Rockspec 格式])或 [简单的版本排序] 中。如果遵循某些更严格的约定,则可以使用简单的字符串比较(例如 "010.001.001" > "009.005.001"
)或“自然比较” [3](compare("10.1.1", "9.5.1")
)。存储为数字的版本,无论是整数还是浮点数,也可以进行简单的比较(010001001 > 009005001
和 10.001.001 > 9.005001
),但零填充使其难以阅读,并且可能容易输入错误。存储为浮点数的版本号如果 Lua 使用整数编译,则会存在问题,并且没有单一的规范方法将浮点数格式化为字符串(例如 "1.010" 与 "1.01" 与 "1.010E+00")。版本可以作为表来表示 [4]( {1,2,3,'b'}
或 {major=1,minor=2,micro=3,stage='b'}
),可能在其元表上加载了比较运算符,这些运算符来自某个版本控制模块:_VERSION = require 'someversionlib' '1.2.3b'
。
以下是一些从 LuaDist 存储库中的模块中扫描到的示例
$ cd Repository $ grep -re 'VERSION *=[^=]' --include '*.lua' ./copas/tests/cosocket.lua:_VERSION = "0.1" ./copas/src/copas/copas.lua:_VERSION = "Copas 1.1.7" ./wsapi-xavante/src/wsapi/common.lua:_G.wsapi._VERSION = "WSAPI 1.3.4" ./wsapi-xavante/src/wsapi/sapi.lua: _VERSION = "WSAPI SAPI 1.0", ./lemock/build/lemock.lua:_VERSION = "LeMock 0.6" ./cgilua/src/cgilua/cgilua.lua:_VERSION = "CGILua 5.1.4" ./oil/lua/luaidl.lua:VERSION = '1.0.5' ./oil/lua/socket/url.lua:_VERSION = "URL 1.0.1" ./oil/lua/oil/compat.lua:VERSION = "OiL 0.5 beta" ./oil/lua/luaidl/lex.lua:PRAGMA_VERSION = '1.0' ./oil/lua/oil.lua:VERSION = "OiL 0.5" ./luajson/util/createRock.lua: VERSION = ("%q"):format(version), ./xavante/src/xavante/xavante.lua:_VERSION = "Xavante 2.2.0" ./lualogging/src/logging/logging.lua:_VERSION = "LuaLogging 1.1.4" ./lua_uri/uri.lua:local M = { _NAME = "uri", VERSION = "1.0" } ./luasoap/soap.lua:_VERSION = "1.0b" ./getopt/getopt.lua:_VERSION = "0.1.1" The version number can be used to check version consistency at runtime, to advise a module to use a different version of an interface, or to load a different version of the module. For checking, sometimes you see things like local foo = require 'foo'; assert(foo._VERSION >= 1.23), though possibly replacing the '>=' test with something that parses the version nu ./vstruct/vstruct/init.lua:_VERSION = "1.1" ./dado/src/dado/object.lua:_VERSION = "Dado Object 1.2.0" ./dado/src/dado/sql.lua:_VERSION = "Dado SQL 1.2.0" ./dado/src/dado.lua:_VERSION = "Dado 1.2.0" ./remdebug/src/remdebug/engine.lua:_VERSION = "1.0" ./luasql-sqlite/src/ado/ado.lua:luasql._VERSION = "LuaSQL 2.1.1" ./luasql-sqlite/src/jdbc/src/lua/jdbc.lua:luasql._VERSION = "LuaSQL 2.0.2" ./luaidl/luaidl.lua:VERSION = '0.8.9b' ./luaidl/luaidl/lex.lua:PRAGMA_VERSION = '1.0' ./wsapi-fcgi/src/wsapi/common.lua:_G.wsapi._VERSION = "WSAPI 1.3.4" ./wsapi-fcgi/src/wsapi/sapi.lua: _VERSION = "WSAPI SAPI 1.0", ./shake/src/shake/shake.lua:_VERSION = "Shake 1.0.2" ./luasocket/src/url.lua:_VERSION = "URL 1.0.1" ./luasocket/src/ltn12.lua:_VERSION = "LTN12 1.0.1" ./venv/src/stable.lua:_VERSION = "Stable 1.0" ./luasql-mysql/src/ado/ado.lua:luasql._VERSION = "LuaSQL 2.1.1" ./luasql-mysql/src/jdbc/src/lua/jdbc.lua:luasql._VERSION = "LuaSQL 2.0.2" ./penlight/lua/pl/utils.lua:utils._VERSION = "0.9.0" ./abelhas/pso.lua:VERSION = "1.0" ./luadoc/src/luadoc/init.lua:_VERSION = "LuaDoc 3.0.1" ./lanes/tests/assert.lua: VERSION= 20070603, -- last change (yyyymmdd) ./rings/src/stable.lua:_VERSION = "Stable 1.0" ./wsapi/src/wsapi/common.lua:_G.wsapi._VERSION = "WSAPI 1.3.4" ./wsapi/src/wsapi/sapi.lua: _VERSION = "WSAPI SAPI 1.0", ./luaglut/glut_test1.lua:print('_VERSION = ' .. _VERSION) ./luaglut/glut_test1.lua:print('luagl.VERSION = ' .. luagl.VERSION) ./luaglut/glut_test1.lua:print('luaglut.VERSION = ' .. luaglut.VERSION) ./luaglut/glut_test2.lua:print('_VERSION = ' .. _VERSION) ./luaglut/glut_test2.lua:print('luagl.VERSION = ' .. luagl.VERSION) ./luaglut/glut_test2.lua:print('luaglut.VERSION = ' .. luaglut.VERSION) ./luasec/src/https.lua:_VERSION = "0.4" ./luasec/src/ssl.lua:_VERSION = "0.4" ./luasql-sqlite3/src/ado/ado.lua:luasql._VERSION = "LuaSQL 2.1.1" ./luasql-sqlite3/src/jdbc/src/lua/jdbc.lua:luasql._VERSION = "LuaSQL 2.0.2" ./wxlua/bindings/genwxbind.lua:WXLUA_BINDING_VERSION = 27 -- Used to verify that the bindings are updated ./luaxmlrpc/xmlrpc.lua:_VERSION = "1.0b"
例如,
-- copas/src/copas/copas.lua ..... module ("copas", package.seeall) -- Meta information is public even if beginning with an "_" _COPYRIGHT = "Copyright (C) 2005-2010 Kepler Project" _DESCRIPTION = "Coroutine Oriented Portable Asynchronous Services" _VERSION = "Copas 1.1.7" .....
-- penlight/lua/pl/utils.lua ..... local utils = {} utils._VERSION = "0.9.0" .....
在许多情况下,项目名称(包含大小写混合,并且可能比模块名称_NAME
更不具体)会包含在版本号前面,就像_G._VERSION
一样。[为什么这样做比使用单独的变量更好,我不知道 - DM]
LuaDistributions - 像 LuaRocks、LuaDist、debian 等 - 通常会强制执行版本控制方案,尽管通常是在包级别而不是模块级别(一个包可能包含多个模块)。保持模块和包版本一致是一个好主意。此外,发行版包可以通过修补上游获得,因此 LuaRocks 会在版本号后面追加自己的连字符和数字(例如,2.0.1 可能变成 2.0.1-1,如 [Rockspec 格式] 中所述)。
版本号可用于在运行时检查版本一致性,建议模块使用不同版本的接口,或加载不同版本的模块。对于检查,有时你会看到类似local foo = require 'foo'; assert(foo._VERSION >= 1.23)
的内容,尽管可能会用解析版本号字符串的内容替换“>=
”测试。在 Perl 中,use
语句类似于 Lua 的 require
,可以传递版本号(例如“use foo 1.23;
”[2]),它会被转发到模块,模块实际上可以对它做任何事情(例如,检查它或更改接口行为)。在 Lua 中,可以使用local foo = require 'foo' (1.23)
来实现类似的功能,但这并不常见。 LuaRocks 提供了一种可选的 require
替代形式,它不仅可以检查,还可以推荐在安装多个版本的情况下加载哪个版本 ([luarocks.require]),但这不再被广泛使用。
从模块中提取版本通常涉及加载模块并读取其_VERSION
变量。如果模块不可信,您可以将其加载到沙箱中,或者通过进行基本的源代码分析(程序分析)来避免执行。枚举模块中的函数也是如此。源代码的哈希可以代替版本使用,类似于 git,但哈希本身不提供排序[5]。
一些其他语言具有更正式的模块版本控制支持,并且已经针对它们编写了相关问题。
考虑一个名为 foo
的模块,它被某些程序 myprogram.lua
使用。
-- foo.lua return {_VERSION=1.0; f = function(x) return x^2 end; g = function(x) return x^3 end} -- myprogram.lua local foo = require 'foo' print(foo.g(2))
现在,假设 foo
模块的新版本发布了,它破坏了接口(这是不可取的,但有时会发生)。
-- foo.lua return {_VERSION=2.0; f = function(x) return x^2 end; g = function(x,y) return x^2*y end}
myprogram.lua
将不再与新的 foo
正确工作。我们可以更新 myprogram
以使其与 foo
版本 2.0 兼容,甚至与版本 1.0 和版本 2.0 都兼容。有各种方法。以下方法只需要更改一行代码。
-- myprogram.lua local foo = require 'foo' local foo = setmetatable({g = (foo._VERSION < 2.0) and foo.g or function(x) return foo.g(x,x) end}, {__index = foo}) print(foo.g(2))
也可以只使用特性测试来完成此操作,而根本不使用 _VERSION
,例如 (foo.g(2,0) == 8)
。兼容性代码也可以移到 foo
模块内部。
-- foo.lua local M = {_VERSION=2.0; f = function(x) return x^2 end; g = function(x,y) return x^2*y end} M.version1 = setmetatable({g = function(x) return M.g(x,x) end}, {__index = M}) return M
-- myprogram.lua (works with both foo version 1 and 2) local foo = require 'foo'; foo = foo.version1 or foo print(foo.g(2))
foo
可以选择将版本 1 接口设置为默认值,以避免破坏现有代码。另一种可能性是,如果接口发生变化,则将 foo
模块重命名为其他名称,例如 foo2
。
还要注意,如上所述,在一个程序中同时使用 foo
接口的两个不同版本是可以的,例如在两个不同的模块中。foo = foo.version1 or foo
(它选择接口的版本 1)是词法作用域的。