模块版本控制 |
|
_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)是词法作用域的。