Module Versioning

lua-users home
wiki

The Lua standard library (_G table) contains a _VERSION variable set to "Lua 5.1" or "Lua 5.2" [1]. A number of other modules follow a similar convention of storing a _VERSION variable (as well as others variables like _NAME) in their module table.

Versions like "1.2.3b" might not be decimal numbers, so this is typically stored as a string. Comparisons of these strings may be less than straightforward and may require special parsing/comparison functions like in LuaRocks [deps.lua] (see [Rockspec format]) or [a simple version sort]. If certain stricter conventions are followed, then simple string comparison may be used (e.g. "010.001.001" > "009.005.001") or a "natural compare" [3] (compare("10.1.1", "9.5.1")). Versions stored as numbers, either integers or floating point, also can allow simple comparisons (010001001 > 009005001 and 10.001.001 > 9.005001), but the zero padding makes it harder to read and possibly easily mistyped. Versions numbers stored as floating point numbers would be problematic if Lua is compiled with integer numbers, and there is a not a single canonical way to format a floating point as a string (e.g. "1.010" v.s. "1.01" v.s. "1.010E+00"). Versions might alternately be represented as a table [4] ( {1,2,3,'b'} or {major=1,minor=2,micro=3,stage='b'} ), possibly with a comparison operators on its metatable loaded from some versioning module: _VERSION = require 'someversionlib' '1.2.3b' .

Here's some examples scanned from the modules in the LuaDist repository:

$ 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"

For example,

-- 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"
.....

In many cases, the project name (with mixed case and which may be less specific than the module name _NAME) is included in front of the version number, like _G._VERSION does. [Why this would be worth doing, rather than using a separate variable, I don't know--DM]

LuaDistributions--like LuaRocks, LuaDist, debian, etc.--often impose a versioning scheme, though often on a package level rather than on a module level (where a package may contain multiple modules). It can be a good idea to keep module and package versions the same. Moreover, the distribution packages may be obtained by patching an upstream, so LuaRocks appends its own hyphen and number to the version set by the upstream (e.g. 2.0.1 may become 2.0.1-1 as discussed in [Rockspec format]).

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 number string. In Perl, the use statement, which is somewhat analogous to Lua require, can be passed a version number (e.g. "use foo 1.23;" [2]), which is forwarded to the module, which could in fact do whatever it wants with it (e.g. check it or alter interface behavior). In Lua, something similar might be done with local foo = require 'foo' (1.23), but that's not typically seen. LuaRocks provides an optional alternate form of require that not only checks but can recommend which version to load if multiple versions are installed ([luarocks.require]), but this is not used that much anymore.

Extracting a version from a module would typically involve loading the module and reading its _VERSION variable. If the module is untrusted, you could load it in a sandbox or avoid execution by just doing rudimentary source code analysis (ProgramAnalysis). The same is true concerning enumerating the functions in a module. A hash of the source might be used in place of a version, sort of like git, but hashes don't themselves provide sequencing [5].

Some other languages have more formalized module versioning support and issues about them have been written about:

Maintaining Interface Compatibility

Consider a module foo used by some program 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))

Now, say a new version of module foo comes out that breaks the interface (which is undesirable but sometimes does happen):

-- foo.lua
return {_VERSION=2.0; f = function(x) return x^2 end; g = function(x,y) return x^2*y end}

myprogram.lua will no longer work properly with the new foo. We could update myprogram to work with foo version 2.0 or even with both version 1.0 and version 2.0. There are various ways. The following involves only a one line change:

-- 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))

It may also be possible to do this only with feature tests, without consulting _VERSION at all, e.g. (foo.g(2,0) == 8) . The compatibility code could also be moved inside the foo module:

-- 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 may alternately make the version 1 interface the default to avoid breaking existing code. Another possibility is to rename the foo module to something different like foo2 if the interface breaks.

Note also that as written it can be ok for two different versions of the foo interface to be used simultaneously in a program, such as in two different modules. The foo = foo.version1 or foo, which selects version 1 of the interface, is lexically scoped.

--DavidManura

See Also


RecentChanges · preferences
edit · history
Last edited April 10, 2012 12:43 am GMT (diff)