diff options
| author | V1K1NGbg <victor@ilchev.com> | 2024-06-09 01:37:30 +0200 |
|---|---|---|
| committer | V1K1NGbg <victor@ilchev.com> | 2024-08-05 20:49:17 +0300 |
| commit | 2377e668c424f77f90a577f42608eccfdfaca07b (patch) | |
| tree | 4e57d5b059458b2b28d2111878b215fbd6e62aa0 /src | |
| parent | d300c11c6acba4390fdde9030a45b0a31e7501d1 (diff) | |
| download | luarocks-2377e668c424f77f90a577f42608eccfdfaca07b.tar.gz luarocks-2377e668c424f77f90a577f42608eccfdfaca07b.tar.bz2 luarocks-2377e668c424f77f90a577f42608eccfdfaca07b.zip | |
Half of core halfway there
Diffstat (limited to 'src')
| -rw-r--r-- | src/luarocks/core/persist-original.lua | 81 | ||||
| -rw-r--r-- | src/luarocks/core/persist.lua | 59 | ||||
| -rw-r--r-- | src/luarocks/core/persist.tl | 81 | ||||
| -rw-r--r-- | src/luarocks/core/sysdetect-original.lua | 419 | ||||
| -rw-r--r-- | src/luarocks/core/sysdetect.lua | 214 | ||||
| -rw-r--r-- | src/luarocks/core/sysdetect.tl | 509 | ||||
| -rw-r--r-- | src/luarocks/core/util-original.lua | 322 | ||||
| -rw-r--r-- | src/luarocks/core/util.lua | 161 | ||||
| -rw-r--r-- | src/luarocks/core/util.tl | 322 | ||||
| -rw-r--r-- | src/luarocks/core/vers-original.lua | 207 | ||||
| -rw-r--r-- | src/luarocks/core/vers.lua | 162 | ||||
| -rw-r--r-- | src/luarocks/core/vers.tl | 213 |
12 files changed, 2498 insertions, 252 deletions
diff --git a/src/luarocks/core/persist-original.lua b/src/luarocks/core/persist-original.lua new file mode 100644 index 00000000..57e7b5d4 --- /dev/null +++ b/src/luarocks/core/persist-original.lua | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | |||
| 2 | local persist = {} | ||
| 3 | |||
| 4 | local require = nil | ||
| 5 | -------------------------------------------------------------------------------- | ||
| 6 | |||
| 7 | --- Load and run a Lua file in an environment. | ||
| 8 | -- @param filename string: the name of the file. | ||
| 9 | -- @param env table: the environment table. | ||
| 10 | -- @return (true, any) or (nil, string, string): true and the return value | ||
| 11 | -- of the file, or nil, an error message and an error code ("open", "load" | ||
| 12 | -- or "run") in case of errors. | ||
| 13 | function persist.run_file(filename, env) | ||
| 14 | local fd, err = io.open(filename) | ||
| 15 | if not fd then | ||
| 16 | return nil, err, "open" | ||
| 17 | end | ||
| 18 | local str, err = fd:read("*a") | ||
| 19 | fd:close() | ||
| 20 | if not str then | ||
| 21 | return nil, err, "open" | ||
| 22 | end | ||
| 23 | str = str:gsub("^#![^\n]*\n", "") | ||
| 24 | local chunk, ran | ||
| 25 | if _VERSION == "Lua 5.1" then -- Lua 5.1 | ||
| 26 | chunk, err = loadstring(str, filename) | ||
| 27 | if chunk then | ||
| 28 | setfenv(chunk, env) | ||
| 29 | ran, err = pcall(chunk) | ||
| 30 | end | ||
| 31 | else -- Lua 5.2 | ||
| 32 | chunk, err = load(str, filename, "t", env) | ||
| 33 | if chunk then | ||
| 34 | ran, err = pcall(chunk) | ||
| 35 | end | ||
| 36 | end | ||
| 37 | if not chunk then | ||
| 38 | return nil, "Error loading file: "..err, "load" | ||
| 39 | end | ||
| 40 | if not ran then | ||
| 41 | return nil, "Error running file: "..err, "run" | ||
| 42 | end | ||
| 43 | return true, err | ||
| 44 | end | ||
| 45 | |||
| 46 | --- Load a Lua file containing assignments, storing them in a table. | ||
| 47 | -- The global environment is not propagated to the loaded file. | ||
| 48 | -- @param filename string: the name of the file. | ||
| 49 | -- @param tbl table or nil: if given, this table is used to store | ||
| 50 | -- loaded values. | ||
| 51 | -- @return (table, table) or (nil, string, string): a table with the file's assignments | ||
| 52 | -- as fields and set of undefined globals accessed in file, | ||
| 53 | -- or nil, an error message and an error code ("open"; couldn't open the file, | ||
| 54 | -- "load"; compile-time error, or "run"; run-time error) | ||
| 55 | -- in case of errors. | ||
| 56 | function persist.load_into_table(filename, tbl) | ||
| 57 | assert(type(filename) == "string") | ||
| 58 | assert(type(tbl) == "table" or not tbl) | ||
| 59 | |||
| 60 | local result = tbl or {} | ||
| 61 | local globals = {} | ||
| 62 | local globals_mt = { | ||
| 63 | __index = function(t, k) | ||
| 64 | globals[k] = true | ||
| 65 | end | ||
| 66 | } | ||
| 67 | local save_mt = getmetatable(result) | ||
| 68 | setmetatable(result, globals_mt) | ||
| 69 | |||
| 70 | local ok, err, errcode = persist.run_file(filename, result) | ||
| 71 | |||
| 72 | setmetatable(result, save_mt) | ||
| 73 | |||
| 74 | if not ok then | ||
| 75 | return nil, err, errcode | ||
| 76 | end | ||
| 77 | return result, globals | ||
| 78 | end | ||
| 79 | |||
| 80 | return persist | ||
| 81 | |||
diff --git a/src/luarocks/core/persist.lua b/src/luarocks/core/persist.lua index 57e7b5d4..1435bd54 100644 --- a/src/luarocks/core/persist.lua +++ b/src/luarocks/core/persist.lua | |||
| @@ -1,58 +1,56 @@ | |||
| 1 | 1 | local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local load = _tl_compat and _tl_compat.load or load; local pcall = _tl_compat and _tl_compat.pcall or pcall; local string = _tl_compat and _tl_compat.string or string | |
| 2 | local persist = {} | 2 | local persist = {} |
| 3 | 3 | ||
| 4 | local require = nil | ||
| 5 | -------------------------------------------------------------------------------- | ||
| 6 | 4 | ||
| 7 | --- Load and run a Lua file in an environment. | 5 | |
| 8 | -- @param filename string: the name of the file. | 6 | |
| 9 | -- @param env table: the environment table. | 7 | |
| 10 | -- @return (true, any) or (nil, string, string): true and the return value | 8 | |
| 11 | -- of the file, or nil, an error message and an error code ("open", "load" | 9 | |
| 12 | -- or "run") in case of errors. | 10 | |
| 13 | function persist.run_file(filename, env) | 11 | function persist.run_file(filename, env) |
| 14 | local fd, err = io.open(filename) | 12 | local fd, open_err = io.open(filename) |
| 15 | if not fd then | 13 | if not fd then |
| 16 | return nil, err, "open" | 14 | return nil, open_err, "open" |
| 17 | end | 15 | end |
| 18 | local str, err = fd:read("*a") | 16 | local str, read_err = fd:read("*a") |
| 19 | fd:close() | 17 | fd:close() |
| 20 | if not str then | 18 | if not str then |
| 21 | return nil, err, "open" | 19 | return nil, read_err, "open" |
| 22 | end | 20 | end |
| 23 | str = str:gsub("^#![^\n]*\n", "") | 21 | str = str:gsub("^#![^\n]*\n", "") |
| 24 | local chunk, ran | 22 | local chunk, ran, err |
| 25 | if _VERSION == "Lua 5.1" then -- Lua 5.1 | 23 | if _VERSION == "Lua 5.1" then |
| 26 | chunk, err = loadstring(str, filename) | 24 | chunk, err = loadstring(str, filename) |
| 27 | if chunk then | 25 | if chunk then |
| 28 | setfenv(chunk, env) | 26 | setfenv(chunk, env) |
| 29 | ran, err = pcall(chunk) | 27 | ran, err = pcall(chunk) |
| 30 | end | 28 | end |
| 31 | else -- Lua 5.2 | 29 | else |
| 32 | chunk, err = load(str, filename, "t", env) | 30 | chunk, err = load(str, filename, "t", env) |
| 33 | if chunk then | 31 | if chunk then |
| 34 | ran, err = pcall(chunk) | 32 | ran, err = pcall(chunk) |
| 35 | end | 33 | end |
| 36 | end | 34 | end |
| 37 | if not chunk then | 35 | if not chunk then |
| 38 | return nil, "Error loading file: "..err, "load" | 36 | return nil, "Error loading file: " .. tostring(err), "load" |
| 39 | end | 37 | end |
| 40 | if not ran then | 38 | if not ran then |
| 41 | return nil, "Error running file: "..err, "run" | 39 | return nil, "Error running file: " .. tostring(err), "run" |
| 42 | end | 40 | end |
| 43 | return true, err | 41 | return true, err |
| 44 | end | 42 | end |
| 45 | 43 | ||
| 46 | --- Load a Lua file containing assignments, storing them in a table. | 44 | |
| 47 | -- The global environment is not propagated to the loaded file. | 45 | |
| 48 | -- @param filename string: the name of the file. | 46 | |
| 49 | -- @param tbl table or nil: if given, this table is used to store | 47 | |
| 50 | -- loaded values. | 48 | |
| 51 | -- @return (table, table) or (nil, string, string): a table with the file's assignments | 49 | |
| 52 | -- as fields and set of undefined globals accessed in file, | 50 | |
| 53 | -- or nil, an error message and an error code ("open"; couldn't open the file, | 51 | |
| 54 | -- "load"; compile-time error, or "run"; run-time error) | 52 | |
| 55 | -- in case of errors. | 53 | |
| 56 | function persist.load_into_table(filename, tbl) | 54 | function persist.load_into_table(filename, tbl) |
| 57 | assert(type(filename) == "string") | 55 | assert(type(filename) == "string") |
| 58 | assert(type(tbl) == "table" or not tbl) | 56 | assert(type(tbl) == "table" or not tbl) |
| @@ -60,9 +58,9 @@ function persist.load_into_table(filename, tbl) | |||
| 60 | local result = tbl or {} | 58 | local result = tbl or {} |
| 61 | local globals = {} | 59 | local globals = {} |
| 62 | local globals_mt = { | 60 | local globals_mt = { |
| 63 | __index = function(t, k) | 61 | __index = function(_, k) |
| 64 | globals[k] = true | 62 | globals[k] = true |
| 65 | end | 63 | end, |
| 66 | } | 64 | } |
| 67 | local save_mt = getmetatable(result) | 65 | local save_mt = getmetatable(result) |
| 68 | setmetatable(result, globals_mt) | 66 | setmetatable(result, globals_mt) |
| @@ -78,4 +76,3 @@ function persist.load_into_table(filename, tbl) | |||
| 78 | end | 76 | end |
| 79 | 77 | ||
| 80 | return persist | 78 | return persist |
| 81 | |||
diff --git a/src/luarocks/core/persist.tl b/src/luarocks/core/persist.tl new file mode 100644 index 00000000..67b5c8dd --- /dev/null +++ b/src/luarocks/core/persist.tl | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | |||
| 2 | local persist = {} | ||
| 3 | |||
| 4 | local require = nil --! | ||
| 5 | -------------------------------------------------------------------------------- | ||
| 6 | |||
| 7 | --- Load and run a Lua file in an environment. | ||
| 8 | -- @param filename string: the name of the file. | ||
| 9 | -- @param env table: the environment table. | ||
| 10 | -- @return (true, any) or (nil, string, string): true and the return value | ||
| 11 | -- of the file, or nil, an error message and an error code ("open", "load" | ||
| 12 | -- or "run") in case of errors. | ||
| 13 | function persist.run_file(filename: string, env: {string:any}): boolean, any | nil, string, string | ||
| 14 | local fd, open_err: FILE, any = io.open(filename) | ||
| 15 | if not fd then | ||
| 16 | return nil, open_err, "open" | ||
| 17 | end | ||
| 18 | local str, read_err: string, string = fd:read("*a") | ||
| 19 | fd:close() | ||
| 20 | if not str then | ||
| 21 | return nil, read_err, "open" | ||
| 22 | end | ||
| 23 | str = str:gsub("^#![^\n]*\n", "") | ||
| 24 | local chunk, ran, err: function(any):(any), boolean, any | ||
| 25 | if _VERSION == "Lua 5.1" then -- Lua 5.1 | ||
| 26 | chunk, err = loadstring(str, filename) --! | ||
| 27 | if chunk then | ||
| 28 | setfenv(chunk, env) --! | ||
| 29 | ran, err = pcall(chunk) | ||
| 30 | end | ||
| 31 | else -- Lua 5.2 | ||
| 32 | chunk, err = load(str, filename, "t", env) | ||
| 33 | if chunk then | ||
| 34 | ran, err = pcall(chunk) | ||
| 35 | end | ||
| 36 | end | ||
| 37 | if not chunk then | ||
| 38 | return nil, "Error loading file: "..tostring(err), "load" --? tostring | ||
| 39 | end | ||
| 40 | if not ran then | ||
| 41 | return nil, "Error running file: "..tostring(err), "run" --? tostring | ||
| 42 | end | ||
| 43 | return true, err | ||
| 44 | end | ||
| 45 | |||
| 46 | --- Load a Lua file containing assignments, storing them in a table. | ||
| 47 | -- The global environment is not propagated to the loaded file. | ||
| 48 | -- @param filename string: the name of the file. | ||
| 49 | -- @param tbl table or nil: if given, this table is used to store | ||
| 50 | -- loaded values. | ||
| 51 | -- @return (table, table) or (nil, string, string): a table with the file's assignments | ||
| 52 | -- as fields and set of undefined globals accessed in file, | ||
| 53 | -- or nil, an error message and an error code ("open"; couldn't open the file, | ||
| 54 | -- "load"; compile-time error, or "run"; run-time error) | ||
| 55 | -- in case of errors. | ||
| 56 | function persist.load_into_table(filename: string, tbl: {string:any}) : table, table | nil, string, string | ||
| 57 | assert(type(filename) == "string") --? needed | ||
| 58 | assert(type(tbl) == "table" or not tbl) --? needed | ||
| 59 | |||
| 60 | local result: {string:any} = tbl or {} | ||
| 61 | local globals: table = {} --? {string:boolean} | ||
| 62 | local globals_mt = { | ||
| 63 | __index = function(_, k: string) | ||
| 64 | globals[k] = true | ||
| 65 | end | ||
| 66 | } | ||
| 67 | local save_mt = getmetatable(result) | ||
| 68 | setmetatable(result, globals_mt) | ||
| 69 | |||
| 70 | local ok, err, errcode = persist.run_file(filename, result) --? boolean, string, string | ||
| 71 | |||
| 72 | setmetatable(result, save_mt) | ||
| 73 | |||
| 74 | if not ok then | ||
| 75 | return nil, err, errcode --! table | nil???? | ||
| 76 | end | ||
| 77 | return result, globals | ||
| 78 | end | ||
| 79 | |||
| 80 | return persist | ||
| 81 | |||
diff --git a/src/luarocks/core/sysdetect-original.lua b/src/luarocks/core/sysdetect-original.lua new file mode 100644 index 00000000..06454f2b --- /dev/null +++ b/src/luarocks/core/sysdetect-original.lua | |||
| @@ -0,0 +1,419 @@ | |||
| 1 | -- Detect the operating system and architecture without forking a subprocess. | ||
| 2 | -- | ||
| 3 | -- We are not going for exhaustive list of every historical system here, | ||
| 4 | -- but aiming to cover every platform where LuaRocks is known to run. | ||
| 5 | -- If your system is not detected, patches are welcome! | ||
| 6 | |||
| 7 | local sysdetect = {} | ||
| 8 | |||
| 9 | local function hex(s) | ||
| 10 | return s:gsub("$(..)", function(x) | ||
| 11 | return string.char(tonumber(x, 16)) | ||
| 12 | end) | ||
| 13 | end | ||
| 14 | |||
| 15 | local function read_int8(fd) | ||
| 16 | if io.type(fd) == "closed file" then | ||
| 17 | return nil | ||
| 18 | end | ||
| 19 | local s = fd:read(1) | ||
| 20 | if not s then | ||
| 21 | fd:close() | ||
| 22 | return nil | ||
| 23 | end | ||
| 24 | return s:byte() | ||
| 25 | end | ||
| 26 | |||
| 27 | local LITTLE = 1 | ||
| 28 | -- local BIG = 2 | ||
| 29 | |||
| 30 | local function bytes2number(s, endian) | ||
| 31 | local r = 0 | ||
| 32 | if endian == LITTLE then | ||
| 33 | for i = #s, 1, -1 do | ||
| 34 | r = r*256 + s:byte(i,i) | ||
| 35 | end | ||
| 36 | else | ||
| 37 | for i = 1, #s do | ||
| 38 | r = r*256 + s:byte(i,i) | ||
| 39 | end | ||
| 40 | end | ||
| 41 | return r | ||
| 42 | end | ||
| 43 | |||
| 44 | local function read(fd, bytes, endian) | ||
| 45 | if io.type(fd) == "closed file" then | ||
| 46 | return nil | ||
| 47 | end | ||
| 48 | local s = fd:read(bytes) | ||
| 49 | if not s | ||
| 50 | then fd:close() | ||
| 51 | return nil | ||
| 52 | end | ||
| 53 | return bytes2number(s, endian) | ||
| 54 | end | ||
| 55 | |||
| 56 | local function read_int32le(fd) | ||
| 57 | return read(fd, 4, LITTLE) | ||
| 58 | end | ||
| 59 | |||
| 60 | -------------------------------------------------------------------------------- | ||
| 61 | -- @section ELF | ||
| 62 | -------------------------------------------------------------------------------- | ||
| 63 | |||
| 64 | local e_osabi = { | ||
| 65 | [0x00] = "sysv", | ||
| 66 | [0x01] = "hpux", | ||
| 67 | [0x02] = "netbsd", | ||
| 68 | [0x03] = "linux", | ||
| 69 | [0x04] = "hurd", | ||
| 70 | [0x06] = "solaris", | ||
| 71 | [0x07] = "aix", | ||
| 72 | [0x08] = "irix", | ||
| 73 | [0x09] = "freebsd", | ||
| 74 | [0x0c] = "openbsd", | ||
| 75 | } | ||
| 76 | |||
| 77 | local e_machines = { | ||
| 78 | [0x02] = "sparc", | ||
| 79 | [0x03] = "x86", | ||
| 80 | [0x08] = "mips", | ||
| 81 | [0x0f] = "hppa", | ||
| 82 | [0x12] = "sparcv8", | ||
| 83 | [0x14] = "ppc", | ||
| 84 | [0x15] = "ppc64", | ||
| 85 | [0x16] = "s390", | ||
| 86 | [0x28] = "arm", | ||
| 87 | [0x2a] = "superh", | ||
| 88 | [0x2b] = "sparcv9", | ||
| 89 | [0x32] = "ia_64", | ||
| 90 | [0x3E] = "x86_64", | ||
| 91 | [0xB6] = "alpha", | ||
| 92 | [0xB7] = "aarch64", | ||
| 93 | [0xF3] = "riscv64", | ||
| 94 | [0x9026] = "alpha", | ||
| 95 | } | ||
| 96 | |||
| 97 | local SHT_NOTE = 7 | ||
| 98 | |||
| 99 | local function read_elf_section_headers(fd, hdr) | ||
| 100 | local endian = hdr.endian | ||
| 101 | local word = hdr.word | ||
| 102 | |||
| 103 | local strtab_offset | ||
| 104 | local sections = {} | ||
| 105 | for i = 0, hdr.e_shnum - 1 do | ||
| 106 | fd:seek("set", hdr.e_shoff + (i * hdr.e_shentsize)) | ||
| 107 | local section = {} | ||
| 108 | section.sh_name_off = read(fd, 4, endian) | ||
| 109 | section.sh_type = read(fd, 4, endian) | ||
| 110 | section.sh_flags = read(fd, word, endian) | ||
| 111 | section.sh_addr = read(fd, word, endian) | ||
| 112 | section.sh_offset = read(fd, word, endian) | ||
| 113 | section.sh_size = read(fd, word, endian) | ||
| 114 | section.sh_link = read(fd, 4, endian) | ||
| 115 | section.sh_info = read(fd, 4, endian) | ||
| 116 | if section.sh_type == SHT_NOTE then | ||
| 117 | fd:seek("set", section.sh_offset) | ||
| 118 | section.namesz = read(fd, 4, endian) | ||
| 119 | section.descsz = read(fd, 4, endian) | ||
| 120 | section.type = read(fd, 4, endian) | ||
| 121 | section.namedata = fd:read(section.namesz):gsub("%z.*", "") | ||
| 122 | section.descdata = fd:read(section.descsz) | ||
| 123 | elseif i == hdr.e_shstrndx then | ||
| 124 | strtab_offset = section.sh_offset | ||
| 125 | end | ||
| 126 | table.insert(sections, section) | ||
| 127 | end | ||
| 128 | if strtab_offset then | ||
| 129 | for _, section in ipairs(sections) do | ||
| 130 | fd:seek("set", strtab_offset + section.sh_name_off) | ||
| 131 | section.name = fd:read(32):gsub("%z.*", "") | ||
| 132 | sections[section.name] = section | ||
| 133 | end | ||
| 134 | end | ||
| 135 | return sections | ||
| 136 | end | ||
| 137 | |||
| 138 | local function detect_elf_system(fd, hdr, sections) | ||
| 139 | local system = e_osabi[hdr.osabi] | ||
| 140 | local endian = hdr.endian | ||
| 141 | |||
| 142 | if system == "sysv" then | ||
| 143 | local abitag = sections[".note.ABI-tag"] | ||
| 144 | if abitag then | ||
| 145 | if abitag.namedata == "GNU" and abitag.type == 1 | ||
| 146 | and abitag.descdata:sub(0, 4) == "\0\0\0\0" then | ||
| 147 | return "linux" | ||
| 148 | end | ||
| 149 | elseif sections[".SUNW_version"] | ||
| 150 | or sections[".SUNW_signature"] then | ||
| 151 | return "solaris" | ||
| 152 | elseif sections[".note.netbsd.ident"] then | ||
| 153 | return "netbsd" | ||
| 154 | elseif sections[".note.openbsd.ident"] then | ||
| 155 | return "openbsd" | ||
| 156 | elseif sections[".note.tag"] and | ||
| 157 | sections[".note.tag"].namedata == "DragonFly" then | ||
| 158 | return "dragonfly" | ||
| 159 | end | ||
| 160 | |||
| 161 | local gnu_version_r = sections[".gnu.version_r"] | ||
| 162 | if gnu_version_r then | ||
| 163 | |||
| 164 | local dynstr = sections[".dynstr"].sh_offset | ||
| 165 | |||
| 166 | local idx = 0 | ||
| 167 | for _ = 0, gnu_version_r.sh_info - 1 do | ||
| 168 | fd:seek("set", gnu_version_r.sh_offset + idx) | ||
| 169 | assert(read(fd, 2, endian)) -- vn_version | ||
| 170 | local vn_cnt = read(fd, 2, endian) | ||
| 171 | local vn_file = read(fd, 4, endian) | ||
| 172 | local vn_next = read(fd, 2, endian) | ||
| 173 | |||
| 174 | fd:seek("set", dynstr + vn_file) | ||
| 175 | local libname = fd:read(64):gsub("%z.*", "") | ||
| 176 | |||
| 177 | if hdr.e_type == 0x03 and libname == "libroot.so" then | ||
| 178 | return "haiku" | ||
| 179 | elseif libname:match("linux") then | ||
| 180 | return "linux" | ||
| 181 | end | ||
| 182 | |||
| 183 | idx = idx + (vn_next * (vn_cnt + 1)) | ||
| 184 | end | ||
| 185 | end | ||
| 186 | |||
| 187 | local procfile = io.open("/proc/sys/kernel/ostype") | ||
| 188 | if procfile then | ||
| 189 | local version = procfile:read(6) | ||
| 190 | procfile:close() | ||
| 191 | if version == "Linux\n" then | ||
| 192 | return "linux" | ||
| 193 | end | ||
| 194 | end | ||
| 195 | end | ||
| 196 | |||
| 197 | return system | ||
| 198 | end | ||
| 199 | |||
| 200 | local function read_elf_header(fd) | ||
| 201 | local hdr = {} | ||
| 202 | |||
| 203 | hdr.bits = read_int8(fd) | ||
| 204 | hdr.endian = read_int8(fd) | ||
| 205 | hdr.elf_version = read_int8(fd) | ||
| 206 | if hdr.elf_version ~= 1 then | ||
| 207 | return nil | ||
| 208 | end | ||
| 209 | hdr.osabi = read_int8(fd) | ||
| 210 | if not hdr.osabi then | ||
| 211 | return nil | ||
| 212 | end | ||
| 213 | |||
| 214 | local endian = hdr.endian | ||
| 215 | fd:seek("set", 0x10) | ||
| 216 | hdr.e_type = read(fd, 2, endian) | ||
| 217 | local machine = read(fd, 2, endian) | ||
| 218 | local processor = e_machines[machine] or "unknown" | ||
| 219 | if endian == 1 and processor == "ppc64" then | ||
| 220 | processor = "ppc64le" | ||
| 221 | end | ||
| 222 | |||
| 223 | local elfversion = read(fd, 4, endian) | ||
| 224 | if elfversion ~= 1 then | ||
| 225 | return nil | ||
| 226 | end | ||
| 227 | |||
| 228 | local word = (hdr.bits == 1) and 4 or 8 | ||
| 229 | hdr.word = word | ||
| 230 | |||
| 231 | hdr.e_entry = read(fd, word, endian) | ||
| 232 | hdr.e_phoff = read(fd, word, endian) | ||
| 233 | hdr.e_shoff = read(fd, word, endian) | ||
| 234 | hdr.e_flags = read(fd, 4, endian) | ||
| 235 | hdr.e_ehsize = read(fd, 2, endian) | ||
| 236 | hdr.e_phentsize = read(fd, 2, endian) | ||
| 237 | hdr.e_phnum = read(fd, 2, endian) | ||
| 238 | hdr.e_shentsize = read(fd, 2, endian) | ||
| 239 | hdr.e_shnum = read(fd, 2, endian) | ||
| 240 | hdr.e_shstrndx = read(fd, 2, endian) | ||
| 241 | |||
| 242 | return hdr, processor | ||
| 243 | end | ||
| 244 | |||
| 245 | local function detect_elf(fd) | ||
| 246 | local hdr, processor = read_elf_header(fd) | ||
| 247 | if not hdr then | ||
| 248 | return nil | ||
| 249 | end | ||
| 250 | local sections = read_elf_section_headers(fd, hdr) | ||
| 251 | local system = detect_elf_system(fd, hdr, sections) | ||
| 252 | return system, processor | ||
| 253 | end | ||
| 254 | |||
| 255 | -------------------------------------------------------------------------------- | ||
| 256 | -- @section Mach Objects (Apple) | ||
| 257 | -------------------------------------------------------------------------------- | ||
| 258 | |||
| 259 | local mach_l64 = { | ||
| 260 | [7] = "x86_64", | ||
| 261 | [12] = "aarch64", | ||
| 262 | } | ||
| 263 | |||
| 264 | local mach_b64 = { | ||
| 265 | [0] = "ppc64", | ||
| 266 | } | ||
| 267 | |||
| 268 | local mach_l32 = { | ||
| 269 | [7] = "x86", | ||
| 270 | [12] = "arm", | ||
| 271 | } | ||
| 272 | |||
| 273 | local mach_b32 = { | ||
| 274 | [0] = "ppc", | ||
| 275 | } | ||
| 276 | |||
| 277 | local function detect_mach(magic, fd) | ||
| 278 | if not magic then | ||
| 279 | return nil | ||
| 280 | end | ||
| 281 | |||
| 282 | if magic == hex("$CA$FE$BA$BE") then | ||
| 283 | -- fat binary, go for the first one | ||
| 284 | fd:seek("set", 0x12) | ||
| 285 | local offs = read_int8(fd) | ||
| 286 | if not offs then | ||
| 287 | return nil | ||
| 288 | end | ||
| 289 | fd:seek("set", offs * 256) | ||
| 290 | magic = fd:read(4) | ||
| 291 | return detect_mach(magic, fd) | ||
| 292 | end | ||
| 293 | |||
| 294 | local cputype = read_int8(fd) | ||
| 295 | |||
| 296 | if magic == hex("$CF$FA$ED$FE") then | ||
| 297 | return "macosx", mach_l64[cputype] or "unknown" | ||
| 298 | elseif magic == hex("$FE$ED$CF$FA") then | ||
| 299 | return "macosx", mach_b64[cputype] or "unknown" | ||
| 300 | elseif magic == hex("$CE$FA$ED$FE") then | ||
| 301 | return "macosx", mach_l32[cputype] or "unknown" | ||
| 302 | elseif magic == hex("$FE$ED$FA$CE") then | ||
| 303 | return "macosx", mach_b32[cputype] or "unknown" | ||
| 304 | end | ||
| 305 | end | ||
| 306 | |||
| 307 | -------------------------------------------------------------------------------- | ||
| 308 | -- @section PE (Windows) | ||
| 309 | -------------------------------------------------------------------------------- | ||
| 310 | |||
| 311 | local pe_machine = { | ||
| 312 | [0x8664] = "x86_64", | ||
| 313 | [0x01c0] = "arm", | ||
| 314 | [0x01c4] = "armv7l", | ||
| 315 | [0xaa64] = "arm64", | ||
| 316 | [0x014c] = "x86", | ||
| 317 | } | ||
| 318 | |||
| 319 | local function detect_pe(fd) | ||
| 320 | fd:seek("set", 60) -- position of PE header position | ||
| 321 | local peoffset = read_int32le(fd) -- read position of PE header | ||
| 322 | if not peoffset then | ||
| 323 | return nil | ||
| 324 | end | ||
| 325 | local system = "windows" | ||
| 326 | fd:seek("set", peoffset + 4) -- move to position of Machine section | ||
| 327 | local machine = read(fd, 2, LITTLE) | ||
| 328 | local processor = pe_machine[machine] | ||
| 329 | |||
| 330 | local rdata_pos = fd:read(736):match(".rdata%z%z............(....)") | ||
| 331 | if rdata_pos then | ||
| 332 | rdata_pos = bytes2number(rdata_pos, LITTLE) | ||
| 333 | fd:seek("set", rdata_pos) | ||
| 334 | local data = fd:read(512) | ||
| 335 | if data:match("cygwin") or data:match("cyggcc") then | ||
| 336 | system = "cygwin" | ||
| 337 | end | ||
| 338 | end | ||
| 339 | |||
| 340 | return system, processor or "unknown" | ||
| 341 | end | ||
| 342 | |||
| 343 | -------------------------------------------------------------------------------- | ||
| 344 | -- @section API | ||
| 345 | -------------------------------------------------------------------------------- | ||
| 346 | |||
| 347 | function sysdetect.detect_file(file) | ||
| 348 | assert(type(file) == "string") | ||
| 349 | local fd = io.open(file, "rb") | ||
| 350 | if not fd then | ||
| 351 | return nil | ||
| 352 | end | ||
| 353 | local magic = fd:read(4) | ||
| 354 | if magic == hex("$7FELF") then | ||
| 355 | return detect_elf(fd) | ||
| 356 | end | ||
| 357 | if magic == hex("MZ$90$00") then | ||
| 358 | return detect_pe(fd) | ||
| 359 | end | ||
| 360 | return detect_mach(magic, fd) | ||
| 361 | end | ||
| 362 | |||
| 363 | local cache_system | ||
| 364 | local cache_processor | ||
| 365 | |||
| 366 | function sysdetect.detect(input_file) | ||
| 367 | local dirsep = package.config:sub(1,1) | ||
| 368 | local files | ||
| 369 | |||
| 370 | if input_file then | ||
| 371 | files = { input_file } | ||
| 372 | else | ||
| 373 | if cache_system then | ||
| 374 | return cache_system, cache_processor | ||
| 375 | end | ||
| 376 | |||
| 377 | local PATHsep | ||
| 378 | local interp = arg and arg[-1] | ||
| 379 | if dirsep == "/" then | ||
| 380 | -- Unix | ||
| 381 | files = { | ||
| 382 | "/bin/sh", -- Unix: well-known POSIX path | ||
| 383 | "/proc/self/exe", -- Linux: this should always have a working binary | ||
| 384 | } | ||
| 385 | PATHsep = ":" | ||
| 386 | else | ||
| 387 | -- Windows | ||
| 388 | local systemroot = os.getenv("SystemRoot") | ||
| 389 | files = { | ||
| 390 | systemroot .. "\\system32\\notepad.exe", -- well-known Windows path | ||
| 391 | systemroot .. "\\explorer.exe", -- well-known Windows path | ||
| 392 | } | ||
| 393 | if interp and not interp:lower():match("exe$") then | ||
| 394 | interp = interp .. ".exe" | ||
| 395 | end | ||
| 396 | PATHsep = ";" | ||
| 397 | end | ||
| 398 | if interp then | ||
| 399 | if interp:match(dirsep) then | ||
| 400 | -- interpreter path is absolute | ||
| 401 | table.insert(files, 1, interp) | ||
| 402 | else | ||
| 403 | for d in (os.getenv("PATH") or ""):gmatch("[^"..PATHsep.."]+") do | ||
| 404 | table.insert(files, d .. dirsep .. interp) | ||
| 405 | end | ||
| 406 | end | ||
| 407 | end | ||
| 408 | end | ||
| 409 | for _, f in ipairs(files) do | ||
| 410 | local system, processor = sysdetect.detect_file(f) | ||
| 411 | if system then | ||
| 412 | cache_system = system | ||
| 413 | cache_processor = processor | ||
| 414 | return system, processor | ||
| 415 | end | ||
| 416 | end | ||
| 417 | end | ||
| 418 | |||
| 419 | return sysdetect | ||
diff --git a/src/luarocks/core/sysdetect.lua b/src/luarocks/core/sysdetect.lua index 06454f2b..40203808 100644 --- a/src/luarocks/core/sysdetect.lua +++ b/src/luarocks/core/sysdetect.lua | |||
| @@ -1,15 +1,65 @@ | |||
| 1 | -- Detect the operating system and architecture without forking a subprocess. | 1 | local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table |
| 2 | -- | 2 | |
| 3 | -- We are not going for exhaustive list of every historical system here, | 3 | |
| 4 | -- but aiming to cover every platform where LuaRocks is known to run. | 4 | |
| 5 | -- If your system is not detected, patches are welcome! | 5 | |
| 6 | 6 | ||
| 7 | local sysdetect = {} | 7 | local sysdetect = {} |
| 8 | 8 | ||
| 9 | |||
| 10 | |||
| 11 | |||
| 12 | |||
| 13 | |||
| 14 | |||
| 15 | |||
| 16 | |||
| 17 | |||
| 18 | |||
| 19 | |||
| 20 | |||
| 21 | |||
| 22 | |||
| 23 | |||
| 24 | |||
| 25 | |||
| 26 | |||
| 27 | |||
| 28 | |||
| 29 | |||
| 30 | |||
| 31 | |||
| 32 | |||
| 33 | |||
| 34 | |||
| 35 | |||
| 36 | |||
| 37 | |||
| 38 | |||
| 39 | |||
| 40 | |||
| 41 | |||
| 42 | |||
| 43 | |||
| 44 | |||
| 45 | |||
| 46 | |||
| 47 | |||
| 48 | |||
| 49 | |||
| 50 | |||
| 51 | |||
| 52 | |||
| 53 | |||
| 54 | |||
| 55 | |||
| 56 | |||
| 57 | |||
| 58 | |||
| 9 | local function hex(s) | 59 | local function hex(s) |
| 10 | return s:gsub("$(..)", function(x) | 60 | return (s:gsub("$(..)", function(x) |
| 11 | return string.char(tonumber(x, 16)) | 61 | return string.char(tonumber(x, 16)) |
| 12 | end) | 62 | end)) |
| 13 | end | 63 | end |
| 14 | 64 | ||
| 15 | local function read_int8(fd) | 65 | local function read_int8(fd) |
| @@ -24,18 +74,15 @@ local function read_int8(fd) | |||
| 24 | return s:byte() | 74 | return s:byte() |
| 25 | end | 75 | end |
| 26 | 76 | ||
| 27 | local LITTLE = 1 | ||
| 28 | -- local BIG = 2 | ||
| 29 | |||
| 30 | local function bytes2number(s, endian) | 77 | local function bytes2number(s, endian) |
| 31 | local r = 0 | 78 | local r = 0 |
| 32 | if endian == LITTLE then | 79 | if endian == "little" then |
| 33 | for i = #s, 1, -1 do | 80 | for i = #s, 1, -1 do |
| 34 | r = r*256 + s:byte(i,i) | 81 | r = r * 256 + s:byte(i, i) |
| 35 | end | 82 | end |
| 36 | else | 83 | else |
| 37 | for i = 1, #s do | 84 | for i = 1, #s do |
| 38 | r = r*256 + s:byte(i,i) | 85 | r = r * 256 + s:byte(i, i) |
| 39 | end | 86 | end |
| 40 | end | 87 | end |
| 41 | return r | 88 | return r |
| @@ -46,20 +93,62 @@ local function read(fd, bytes, endian) | |||
| 46 | return nil | 93 | return nil |
| 47 | end | 94 | end |
| 48 | local s = fd:read(bytes) | 95 | local s = fd:read(bytes) |
| 49 | if not s | 96 | if not s then |
| 50 | then fd:close() | 97 | fd:close() |
| 51 | return nil | 98 | return nil |
| 52 | end | 99 | end |
| 53 | return bytes2number(s, endian) | 100 | return bytes2number(s, endian) |
| 54 | end | 101 | end |
| 55 | 102 | ||
| 56 | local function read_int32le(fd) | 103 | local function read_int32le(fd) |
| 57 | return read(fd, 4, LITTLE) | 104 | return read(fd, 4, "little") |
| 58 | end | 105 | end |
| 59 | 106 | ||
| 60 | -------------------------------------------------------------------------------- | 107 | |
| 61 | -- @section ELF | 108 | |
| 62 | -------------------------------------------------------------------------------- | 109 | |
| 110 | |||
| 111 | |||
| 112 | |||
| 113 | |||
| 114 | |||
| 115 | |||
| 116 | |||
| 117 | |||
| 118 | |||
| 119 | |||
| 120 | |||
| 121 | |||
| 122 | |||
| 123 | |||
| 124 | |||
| 125 | |||
| 126 | |||
| 127 | |||
| 128 | |||
| 129 | |||
| 130 | |||
| 131 | |||
| 132 | |||
| 133 | |||
| 134 | |||
| 135 | |||
| 136 | |||
| 137 | |||
| 138 | |||
| 139 | |||
| 140 | |||
| 141 | |||
| 142 | |||
| 143 | |||
| 144 | |||
| 145 | |||
| 146 | |||
| 147 | |||
| 148 | local endians = { | ||
| 149 | [0x01] = "little", | ||
| 150 | [0x02] = "big", | ||
| 151 | } | ||
| 63 | 152 | ||
| 64 | local e_osabi = { | 153 | local e_osabi = { |
| 65 | [0x00] = "sysv", | 154 | [0x00] = "sysv", |
| @@ -97,11 +186,12 @@ local e_machines = { | |||
| 97 | local SHT_NOTE = 7 | 186 | local SHT_NOTE = 7 |
| 98 | 187 | ||
| 99 | local function read_elf_section_headers(fd, hdr) | 188 | local function read_elf_section_headers(fd, hdr) |
| 100 | local endian = hdr.endian | 189 | local endian = endians[hdr.endian] |
| 101 | local word = hdr.word | 190 | local word = hdr.word |
| 102 | 191 | ||
| 103 | local strtab_offset | 192 | local strtab_offset |
| 104 | local sections = {} | 193 | local sections = {} |
| 194 | local secarray = {} | ||
| 105 | for i = 0, hdr.e_shnum - 1 do | 195 | for i = 0, hdr.e_shnum - 1 do |
| 106 | fd:seek("set", hdr.e_shoff + (i * hdr.e_shentsize)) | 196 | fd:seek("set", hdr.e_shoff + (i * hdr.e_shentsize)) |
| 107 | local section = {} | 197 | local section = {} |
| @@ -123,10 +213,10 @@ local function read_elf_section_headers(fd, hdr) | |||
| 123 | elseif i == hdr.e_shstrndx then | 213 | elseif i == hdr.e_shstrndx then |
| 124 | strtab_offset = section.sh_offset | 214 | strtab_offset = section.sh_offset |
| 125 | end | 215 | end |
| 126 | table.insert(sections, section) | 216 | table.insert(secarray, section) |
| 127 | end | 217 | end |
| 128 | if strtab_offset then | 218 | if strtab_offset then |
| 129 | for _, section in ipairs(sections) do | 219 | for _, section in ipairs(secarray) do |
| 130 | fd:seek("set", strtab_offset + section.sh_name_off) | 220 | fd:seek("set", strtab_offset + section.sh_name_off) |
| 131 | section.name = fd:read(32):gsub("%z.*", "") | 221 | section.name = fd:read(32):gsub("%z.*", "") |
| 132 | sections[section.name] = section | 222 | sections[section.name] = section |
| @@ -137,24 +227,24 @@ end | |||
| 137 | 227 | ||
| 138 | local function detect_elf_system(fd, hdr, sections) | 228 | local function detect_elf_system(fd, hdr, sections) |
| 139 | local system = e_osabi[hdr.osabi] | 229 | local system = e_osabi[hdr.osabi] |
| 140 | local endian = hdr.endian | 230 | local endian = endians[hdr.endian] |
| 141 | 231 | ||
| 142 | if system == "sysv" then | 232 | if system == "sysv" then |
| 143 | local abitag = sections[".note.ABI-tag"] | 233 | local abitag = sections[".note.ABI-tag"] |
| 144 | if abitag then | 234 | if abitag then |
| 145 | if abitag.namedata == "GNU" and abitag.type == 1 | 235 | if abitag.namedata == "GNU" and abitag.type == 1 and |
| 146 | and abitag.descdata:sub(0, 4) == "\0\0\0\0" then | 236 | abitag.descdata:sub(0, 4) == "\0\0\0\0" then |
| 147 | return "linux" | 237 | return "linux" |
| 148 | end | 238 | end |
| 149 | elseif sections[".SUNW_version"] | 239 | elseif sections[".SUNW_version"] or |
| 150 | or sections[".SUNW_signature"] then | 240 | sections[".SUNW_signature"] then |
| 151 | return "solaris" | 241 | return "solaris" |
| 152 | elseif sections[".note.netbsd.ident"] then | 242 | elseif sections[".note.netbsd.ident"] then |
| 153 | return "netbsd" | 243 | return "netbsd" |
| 154 | elseif sections[".note.openbsd.ident"] then | 244 | elseif sections[".note.openbsd.ident"] then |
| 155 | return "openbsd" | 245 | return "openbsd" |
| 156 | elseif sections[".note.tag"] and | 246 | elseif sections[".note.tag"] and |
| 157 | sections[".note.tag"].namedata == "DragonFly" then | 247 | sections[".note.tag"].namedata == "DragonFly" then |
| 158 | return "dragonfly" | 248 | return "dragonfly" |
| 159 | end | 249 | end |
| 160 | 250 | ||
| @@ -166,7 +256,7 @@ local function detect_elf_system(fd, hdr, sections) | |||
| 166 | local idx = 0 | 256 | local idx = 0 |
| 167 | for _ = 0, gnu_version_r.sh_info - 1 do | 257 | for _ = 0, gnu_version_r.sh_info - 1 do |
| 168 | fd:seek("set", gnu_version_r.sh_offset + idx) | 258 | fd:seek("set", gnu_version_r.sh_offset + idx) |
| 169 | assert(read(fd, 2, endian)) -- vn_version | 259 | assert(read(fd, 2, endian)) |
| 170 | local vn_cnt = read(fd, 2, endian) | 260 | local vn_cnt = read(fd, 2, endian) |
| 171 | local vn_file = read(fd, 4, endian) | 261 | local vn_file = read(fd, 4, endian) |
| 172 | local vn_next = read(fd, 2, endian) | 262 | local vn_next = read(fd, 2, endian) |
| @@ -211,12 +301,12 @@ local function read_elf_header(fd) | |||
| 211 | return nil | 301 | return nil |
| 212 | end | 302 | end |
| 213 | 303 | ||
| 214 | local endian = hdr.endian | 304 | local endian = endians[hdr.endian] |
| 215 | fd:seek("set", 0x10) | 305 | fd:seek("set", 0x10) |
| 216 | hdr.e_type = read(fd, 2, endian) | 306 | hdr.e_type = read(fd, 2, endian) |
| 217 | local machine = read(fd, 2, endian) | 307 | local machine = read(fd, 2, endian) |
| 218 | local processor = e_machines[machine] or "unknown" | 308 | local processor = e_machines[machine] or "unknown" |
| 219 | if endian == 1 and processor == "ppc64" then | 309 | if endian == "little" and processor == "ppc64" then |
| 220 | processor = "ppc64le" | 310 | processor = "ppc64le" |
| 221 | end | 311 | end |
| 222 | 312 | ||
| @@ -252,9 +342,9 @@ local function detect_elf(fd) | |||
| 252 | return system, processor | 342 | return system, processor |
| 253 | end | 343 | end |
| 254 | 344 | ||
| 255 | -------------------------------------------------------------------------------- | 345 | |
| 256 | -- @section Mach Objects (Apple) | 346 | |
| 257 | -------------------------------------------------------------------------------- | 347 | |
| 258 | 348 | ||
| 259 | local mach_l64 = { | 349 | local mach_l64 = { |
| 260 | [7] = "x86_64", | 350 | [7] = "x86_64", |
| @@ -280,7 +370,7 @@ local function detect_mach(magic, fd) | |||
| 280 | end | 370 | end |
| 281 | 371 | ||
| 282 | if magic == hex("$CA$FE$BA$BE") then | 372 | if magic == hex("$CA$FE$BA$BE") then |
| 283 | -- fat binary, go for the first one | 373 | |
| 284 | fd:seek("set", 0x12) | 374 | fd:seek("set", 0x12) |
| 285 | local offs = read_int8(fd) | 375 | local offs = read_int8(fd) |
| 286 | if not offs then | 376 | if not offs then |
| @@ -304,32 +394,32 @@ local function detect_mach(magic, fd) | |||
| 304 | end | 394 | end |
| 305 | end | 395 | end |
| 306 | 396 | ||
| 307 | -------------------------------------------------------------------------------- | 397 | |
| 308 | -- @section PE (Windows) | 398 | |
| 309 | -------------------------------------------------------------------------------- | 399 | |
| 310 | 400 | ||
| 311 | local pe_machine = { | 401 | local pe_machine = { |
| 312 | [0x8664] = "x86_64", | 402 | [0x8664] = "x86_64", |
| 313 | [0x01c0] = "arm", | 403 | [0x01c0] = "arm", |
| 314 | [0x01c4] = "armv7l", | 404 | [0x01c4] = "armv7l", |
| 315 | [0xaa64] = "arm64", | 405 | [0xaa64] = "arm64", |
| 316 | [0x014c] = "x86", | 406 | [0x014c] = "x86", |
| 317 | } | 407 | } |
| 318 | 408 | ||
| 319 | local function detect_pe(fd) | 409 | local function detect_pe(fd) |
| 320 | fd:seek("set", 60) -- position of PE header position | 410 | fd:seek("set", 60) |
| 321 | local peoffset = read_int32le(fd) -- read position of PE header | 411 | local peoffset = read_int32le(fd) |
| 322 | if not peoffset then | 412 | if not peoffset then |
| 323 | return nil | 413 | return nil |
| 324 | end | 414 | end |
| 325 | local system = "windows" | 415 | local system = "windows" |
| 326 | fd:seek("set", peoffset + 4) -- move to position of Machine section | 416 | fd:seek("set", peoffset + 4) |
| 327 | local machine = read(fd, 2, LITTLE) | 417 | local machine = read(fd, 2, "little") |
| 328 | local processor = pe_machine[machine] | 418 | local processor = pe_machine[machine] |
| 329 | 419 | ||
| 330 | local rdata_pos = fd:read(736):match(".rdata%z%z............(....)") | 420 | local rdata_pos_s = fd:read(736):match(".rdata%z%z............(....)") |
| 331 | if rdata_pos then | 421 | if rdata_pos_s then |
| 332 | rdata_pos = bytes2number(rdata_pos, LITTLE) | 422 | local rdata_pos = bytes2number(rdata_pos_s, "little") |
| 333 | fd:seek("set", rdata_pos) | 423 | fd:seek("set", rdata_pos) |
| 334 | local data = fd:read(512) | 424 | local data = fd:read(512) |
| 335 | if data:match("cygwin") or data:match("cyggcc") then | 425 | if data:match("cygwin") or data:match("cyggcc") then |
| @@ -340,9 +430,9 @@ local function detect_pe(fd) | |||
| 340 | return system, processor or "unknown" | 430 | return system, processor or "unknown" |
| 341 | end | 431 | end |
| 342 | 432 | ||
| 343 | -------------------------------------------------------------------------------- | 433 | |
| 344 | -- @section API | 434 | |
| 345 | -------------------------------------------------------------------------------- | 435 | |
| 346 | 436 | ||
| 347 | function sysdetect.detect_file(file) | 437 | function sysdetect.detect_file(file) |
| 348 | assert(type(file) == "string") | 438 | assert(type(file) == "string") |
| @@ -364,7 +454,7 @@ local cache_system | |||
| 364 | local cache_processor | 454 | local cache_processor |
| 365 | 455 | ||
| 366 | function sysdetect.detect(input_file) | 456 | function sysdetect.detect(input_file) |
| 367 | local dirsep = package.config:sub(1,1) | 457 | local dirsep = package.config:sub(1, 1) |
| 368 | local files | 458 | local files |
| 369 | 459 | ||
| 370 | if input_file then | 460 | if input_file then |
| @@ -377,18 +467,18 @@ function sysdetect.detect(input_file) | |||
| 377 | local PATHsep | 467 | local PATHsep |
| 378 | local interp = arg and arg[-1] | 468 | local interp = arg and arg[-1] |
| 379 | if dirsep == "/" then | 469 | if dirsep == "/" then |
| 380 | -- Unix | 470 | |
| 381 | files = { | 471 | files = { |
| 382 | "/bin/sh", -- Unix: well-known POSIX path | 472 | "/bin/sh", |
| 383 | "/proc/self/exe", -- Linux: this should always have a working binary | 473 | "/proc/self/exe", |
| 384 | } | 474 | } |
| 385 | PATHsep = ":" | 475 | PATHsep = ":" |
| 386 | else | 476 | else |
| 387 | -- Windows | 477 | |
| 388 | local systemroot = os.getenv("SystemRoot") | 478 | local systemroot = os.getenv("SystemRoot") |
| 389 | files = { | 479 | files = { |
| 390 | systemroot .. "\\system32\\notepad.exe", -- well-known Windows path | 480 | systemroot .. "\\system32\\notepad.exe", |
| 391 | systemroot .. "\\explorer.exe", -- well-known Windows path | 481 | systemroot .. "\\explorer.exe", |
| 392 | } | 482 | } |
| 393 | if interp and not interp:lower():match("exe$") then | 483 | if interp and not interp:lower():match("exe$") then |
| 394 | interp = interp .. ".exe" | 484 | interp = interp .. ".exe" |
| @@ -397,10 +487,10 @@ function sysdetect.detect(input_file) | |||
| 397 | end | 487 | end |
| 398 | if interp then | 488 | if interp then |
| 399 | if interp:match(dirsep) then | 489 | if interp:match(dirsep) then |
| 400 | -- interpreter path is absolute | 490 | |
| 401 | table.insert(files, 1, interp) | 491 | table.insert(files, 1, interp) |
| 402 | else | 492 | else |
| 403 | for d in (os.getenv("PATH") or ""):gmatch("[^"..PATHsep.."]+") do | 493 | for d in (os.getenv("PATH") or ""):gmatch("[^" .. PATHsep .. "]+") do |
| 404 | table.insert(files, d .. dirsep .. interp) | 494 | table.insert(files, d .. dirsep .. interp) |
| 405 | end | 495 | end |
| 406 | end | 496 | end |
diff --git a/src/luarocks/core/sysdetect.tl b/src/luarocks/core/sysdetect.tl new file mode 100644 index 00000000..ee2a5bf7 --- /dev/null +++ b/src/luarocks/core/sysdetect.tl | |||
| @@ -0,0 +1,509 @@ | |||
| 1 | -- Detect the operating system and architecture without forking a subprocess. | ||
| 2 | -- | ||
| 3 | -- We are not going for exhaustive list of every historical system here, | ||
| 4 | -- but aiming to cover every platform where LuaRocks is known to run. | ||
| 5 | -- If your system is not detected, patches are welcome! | ||
| 6 | |||
| 7 | local record sysdetect | ||
| 8 | enum Processor | ||
| 9 | "unknown" | ||
| 10 | "sparc" | ||
| 11 | "x86" | ||
| 12 | "mips" | ||
| 13 | "hppa" | ||
| 14 | "sparcv8" | ||
| 15 | "ppc" | ||
| 16 | "ppc64" | ||
| 17 | "ppc64le" | ||
| 18 | "s390" | ||
| 19 | "arm" | ||
| 20 | "armv7l" | ||
| 21 | "arm64" | ||
| 22 | "superh" | ||
| 23 | "sparcv9" | ||
| 24 | "ia_64" | ||
| 25 | "x86_64" | ||
| 26 | "alpha" | ||
| 27 | "aarch64" | ||
| 28 | "riscv64" | ||
| 29 | "alpha" | ||
| 30 | end | ||
| 31 | |||
| 32 | enum System | ||
| 33 | "sysv" | ||
| 34 | "hpux" | ||
| 35 | "netbsd" | ||
| 36 | "linux" | ||
| 37 | "hurd" | ||
| 38 | "solaris" | ||
| 39 | "aix" | ||
| 40 | "irix" | ||
| 41 | "freebsd" | ||
| 42 | "openbsd" | ||
| 43 | "dragonfly" | ||
| 44 | "haiku" | ||
| 45 | "windows" | ||
| 46 | "cygwin" | ||
| 47 | "macosx" | ||
| 48 | end | ||
| 49 | end | ||
| 50 | |||
| 51 | local type System = sysdetect.System --? ? | ||
| 52 | local type Processor = sysdetect.Processor --? ? | ||
| 53 | |||
| 54 | local enum Endian | ||
| 55 | "little" | ||
| 56 | "big" | ||
| 57 | end | ||
| 58 | |||
| 59 | local function hex(s: string): string | ||
| 60 | return (s:gsub("$(..)", function(x: string): string | ||
| 61 | return string.char(tonumber(x, 16)) | ||
| 62 | end)) | ||
| 63 | end | ||
| 64 | |||
| 65 | local function read_int8(fd: FILE): integer | ||
| 66 | if io.type(fd) == "closed file" then | ||
| 67 | return nil | ||
| 68 | end | ||
| 69 | local s = fd:read(1) | ||
| 70 | if not s then | ||
| 71 | fd:close() | ||
| 72 | return nil | ||
| 73 | end | ||
| 74 | return s:byte() | ||
| 75 | end | ||
| 76 | |||
| 77 | local function bytes2number(s: string, endian: Endian): integer | ||
| 78 | local r: integer = 0 | ||
| 79 | if endian == "little" then | ||
| 80 | for i = #s, 1, -1 do | ||
| 81 | r = r*256 + s:byte(i,i) | ||
| 82 | end | ||
| 83 | else | ||
| 84 | for i = 1, #s do | ||
| 85 | r = r*256 + s:byte(i,i) | ||
| 86 | end | ||
| 87 | end | ||
| 88 | return r | ||
| 89 | end | ||
| 90 | |||
| 91 | local function read(fd: FILE, bytes: integer, endian: Endian): integer | ||
| 92 | if io.type(fd) == "closed file" then | ||
| 93 | return nil | ||
| 94 | end | ||
| 95 | local s: string = fd:read(bytes) | ||
| 96 | if not s | ||
| 97 | then fd:close() | ||
| 98 | return nil | ||
| 99 | end | ||
| 100 | return bytes2number(s, endian) | ||
| 101 | end | ||
| 102 | |||
| 103 | local function read_int32le(fd: FILE): integer | ||
| 104 | return read(fd, 4, "little") | ||
| 105 | end | ||
| 106 | |||
| 107 | -------------------------------------------------------------------------------- | ||
| 108 | -- @section ELF | ||
| 109 | -------------------------------------------------------------------------------- | ||
| 110 | |||
| 111 | local record ElfHeader | ||
| 112 | osabi: integer | ||
| 113 | bits: integer | ||
| 114 | endian: integer | ||
| 115 | elf_version: integer | ||
| 116 | word: integer | ||
| 117 | e_type: integer | ||
| 118 | e_entry: integer | ||
| 119 | e_phoff: integer | ||
| 120 | e_shoff: integer | ||
| 121 | e_flags: integer | ||
| 122 | e_ehsize: integer | ||
| 123 | e_phentsize: integer | ||
| 124 | e_phnum: integer | ||
| 125 | e_shentsize: integer | ||
| 126 | e_shnum: integer | ||
| 127 | e_shstrndx: integer | ||
| 128 | end | ||
| 129 | |||
| 130 | local record ElfSection | ||
| 131 | sh_name_off: integer | ||
| 132 | sh_type: integer | ||
| 133 | sh_flags: integer | ||
| 134 | sh_addr: integer | ||
| 135 | sh_offset: integer | ||
| 136 | sh_size: integer | ||
| 137 | sh_link: integer | ||
| 138 | sh_info: integer | ||
| 139 | |||
| 140 | name: string | ||
| 141 | namesz: integer | ||
| 142 | namedata: string | ||
| 143 | descsz: integer | ||
| 144 | descdata: string | ||
| 145 | type: integer | ||
| 146 | end | ||
| 147 | |||
| 148 | local endians: {integer:Endian} = { | ||
| 149 | [0x01] = "little", | ||
| 150 | [0x02] = "big", | ||
| 151 | } | ||
| 152 | |||
| 153 | local e_osabi: {integer:System} = { | ||
| 154 | [0x00] = "sysv", | ||
| 155 | [0x01] = "hpux", | ||
| 156 | [0x02] = "netbsd", | ||
| 157 | [0x03] = "linux", | ||
| 158 | [0x04] = "hurd", | ||
| 159 | [0x06] = "solaris", | ||
| 160 | [0x07] = "aix", | ||
| 161 | [0x08] = "irix", | ||
| 162 | [0x09] = "freebsd", | ||
| 163 | [0x0c] = "openbsd", | ||
| 164 | } | ||
| 165 | |||
| 166 | local e_machines: {integer:Processor} = { | ||
| 167 | [0x02] = "sparc", | ||
| 168 | [0x03] = "x86", | ||
| 169 | [0x08] = "mips", | ||
| 170 | [0x0f] = "hppa", | ||
| 171 | [0x12] = "sparcv8", | ||
| 172 | [0x14] = "ppc", | ||
| 173 | [0x15] = "ppc64", | ||
| 174 | [0x16] = "s390", | ||
| 175 | [0x28] = "arm", | ||
| 176 | [0x2a] = "superh", | ||
| 177 | [0x2b] = "sparcv9", | ||
| 178 | [0x32] = "ia_64", | ||
| 179 | [0x3E] = "x86_64", | ||
| 180 | [0xB6] = "alpha", | ||
| 181 | [0xB7] = "aarch64", | ||
| 182 | [0xF3] = "riscv64", | ||
| 183 | [0x9026] = "alpha", | ||
| 184 | } | ||
| 185 | |||
| 186 | local SHT_NOTE <const> = 7 | ||
| 187 | |||
| 188 | local function read_elf_section_headers(fd: FILE, hdr: ElfHeader): {string: ElfSection} | ||
| 189 | local endian = endians[hdr.endian] | ||
| 190 | local word: integer = hdr.word | ||
| 191 | |||
| 192 | local strtab_offset: integer | ||
| 193 | local sections: {string:ElfSection} = {} | ||
| 194 | local secarray: {ElfSection} = {} | ||
| 195 | for i = 0, hdr.e_shnum - 1 do | ||
| 196 | fd:seek("set", hdr.e_shoff + (i * hdr.e_shentsize)) | ||
| 197 | local section: ElfSection = {} | ||
| 198 | section.sh_name_off = read(fd, 4, endian) | ||
| 199 | section.sh_type = read(fd, 4, endian) | ||
| 200 | section.sh_flags = read(fd, word, endian) | ||
| 201 | section.sh_addr = read(fd, word, endian) | ||
| 202 | section.sh_offset = read(fd, word, endian) | ||
| 203 | section.sh_size = read(fd, word, endian) | ||
| 204 | section.sh_link = read(fd, 4, endian) | ||
| 205 | section.sh_info = read(fd, 4, endian) | ||
| 206 | if section.sh_type == SHT_NOTE then | ||
| 207 | fd:seek("set", section.sh_offset) | ||
| 208 | section.namesz = read(fd, 4, endian) | ||
| 209 | section.descsz = read(fd, 4, endian) | ||
| 210 | section.type = read(fd, 4, endian) | ||
| 211 | section.namedata = fd:read(section.namesz):gsub("%z.*", "") | ||
| 212 | section.descdata = fd:read(section.descsz) | ||
| 213 | elseif i == hdr.e_shstrndx then | ||
| 214 | strtab_offset = section.sh_offset | ||
| 215 | end | ||
| 216 | table.insert(secarray, section) | ||
| 217 | end | ||
| 218 | if strtab_offset then | ||
| 219 | for _, section in ipairs(secarray) do | ||
| 220 | fd:seek("set", strtab_offset + section.sh_name_off) | ||
| 221 | section.name = fd:read(32):gsub("%z.*", "") | ||
| 222 | sections[section.name] = section | ||
| 223 | end | ||
| 224 | end | ||
| 225 | return sections | ||
| 226 | end | ||
| 227 | |||
| 228 | local function detect_elf_system(fd: FILE, hdr: ElfHeader, sections: {string:ElfSection}): System | ||
| 229 | local system = e_osabi[hdr.osabi] | ||
| 230 | local endian = endians[hdr.endian] | ||
| 231 | |||
| 232 | if system == "sysv" then | ||
| 233 | local abitag = sections[".note.ABI-tag"] | ||
| 234 | if abitag then | ||
| 235 | if abitag.namedata == "GNU" and abitag.type == 1 | ||
| 236 | and abitag.descdata:sub(0, 4) == "\0\0\0\0" then | ||
| 237 | return "linux" | ||
| 238 | end | ||
| 239 | elseif sections[".SUNW_version"] | ||
| 240 | or sections[".SUNW_signature"] then | ||
| 241 | return "solaris" | ||
| 242 | elseif sections[".note.netbsd.ident"] then | ||
| 243 | return "netbsd" | ||
| 244 | elseif sections[".note.openbsd.ident"] then | ||
| 245 | return "openbsd" | ||
| 246 | elseif sections[".note.tag"] and | ||
| 247 | sections[".note.tag"].namedata == "DragonFly" then | ||
| 248 | return "dragonfly" | ||
| 249 | end | ||
| 250 | |||
| 251 | local gnu_version_r = sections[".gnu.version_r"] | ||
| 252 | if gnu_version_r then | ||
| 253 | |||
| 254 | local dynstr = sections[".dynstr"].sh_offset | ||
| 255 | |||
| 256 | local idx = 0 | ||
| 257 | for _ = 0, gnu_version_r.sh_info - 1 do | ||
| 258 | fd:seek("set", gnu_version_r.sh_offset + idx) | ||
| 259 | assert(read(fd, 2, endian)) -- vn_version --? | ||
| 260 | local vn_cnt = read(fd, 2, endian) | ||
| 261 | local vn_file = read(fd, 4, endian) | ||
| 262 | local vn_next = read(fd, 2, endian) | ||
| 263 | |||
| 264 | fd:seek("set", dynstr + vn_file) | ||
| 265 | local libname = fd:read(64):gsub("%z.*", "") | ||
| 266 | |||
| 267 | if hdr.e_type == 0x03 and libname == "libroot.so" then | ||
| 268 | return "haiku" | ||
| 269 | elseif libname:match("linux") then | ||
| 270 | return "linux" | ||
| 271 | end | ||
| 272 | |||
| 273 | idx = idx + (vn_next * (vn_cnt + 1)) | ||
| 274 | end | ||
| 275 | end | ||
| 276 | |||
| 277 | local procfile = io.open("/proc/sys/kernel/ostype") | ||
| 278 | if procfile then | ||
| 279 | local version = procfile:read(6) | ||
| 280 | procfile:close() | ||
| 281 | if version == "Linux\n" then | ||
| 282 | return "linux" | ||
| 283 | end | ||
| 284 | end | ||
| 285 | end | ||
| 286 | |||
| 287 | return system | ||
| 288 | end | ||
| 289 | |||
| 290 | local function read_elf_header(fd: FILE): ElfHeader, Processor | ||
| 291 | local hdr: ElfHeader = {} | ||
| 292 | |||
| 293 | hdr.bits = read_int8(fd) | ||
| 294 | hdr.endian = read_int8(fd) | ||
| 295 | hdr.elf_version = read_int8(fd) | ||
| 296 | if hdr.elf_version ~= 1 then | ||
| 297 | return nil | ||
| 298 | end | ||
| 299 | hdr.osabi = read_int8(fd) | ||
| 300 | if not hdr.osabi then | ||
| 301 | return nil | ||
| 302 | end | ||
| 303 | |||
| 304 | local endian = endians[hdr.endian] | ||
| 305 | fd:seek("set", 0x10) | ||
| 306 | hdr.e_type = read(fd, 2, endian) | ||
| 307 | local machine = read(fd, 2, endian) | ||
| 308 | local processor = e_machines[machine] or "unknown" | ||
| 309 | if endian == "little" and processor == "ppc64" then | ||
| 310 | processor = "ppc64le" | ||
| 311 | end | ||
| 312 | |||
| 313 | local elfversion = read(fd, 4, endian) | ||
| 314 | if elfversion ~= 1 then | ||
| 315 | return nil | ||
| 316 | end | ||
| 317 | |||
| 318 | local word = (hdr.bits == 1) and 4 or 8 | ||
| 319 | hdr.word = word | ||
| 320 | |||
| 321 | hdr.e_entry = read(fd, word, endian) | ||
| 322 | hdr.e_phoff = read(fd, word, endian) | ||
| 323 | hdr.e_shoff = read(fd, word, endian) | ||
| 324 | hdr.e_flags = read(fd, 4, endian) | ||
| 325 | hdr.e_ehsize = read(fd, 2, endian) | ||
| 326 | hdr.e_phentsize = read(fd, 2, endian) | ||
| 327 | hdr.e_phnum = read(fd, 2, endian) | ||
| 328 | hdr.e_shentsize = read(fd, 2, endian) | ||
| 329 | hdr.e_shnum = read(fd, 2, endian) | ||
| 330 | hdr.e_shstrndx = read(fd, 2, endian) | ||
| 331 | |||
| 332 | return hdr, processor | ||
| 333 | end | ||
| 334 | |||
| 335 | local function detect_elf(fd: FILE): System, Processor | ||
| 336 | local hdr, processor = read_elf_header(fd) | ||
| 337 | if not hdr then | ||
| 338 | return nil | ||
| 339 | end | ||
| 340 | local sections = read_elf_section_headers(fd, hdr) | ||
| 341 | local system = detect_elf_system(fd, hdr, sections) | ||
| 342 | return system, processor | ||
| 343 | end | ||
| 344 | |||
| 345 | -------------------------------------------------------------------------------- | ||
| 346 | -- @section Mach Objects (Apple) | ||
| 347 | -------------------------------------------------------------------------------- | ||
| 348 | |||
| 349 | local mach_l64: {integer:Processor} = { | ||
| 350 | [7] = "x86_64", | ||
| 351 | [12] = "aarch64", | ||
| 352 | } | ||
| 353 | |||
| 354 | local mach_b64: {integer:Processor} = { | ||
| 355 | [0] = "ppc64", | ||
| 356 | } | ||
| 357 | |||
| 358 | local mach_l32: {integer:Processor} = { | ||
| 359 | [7] = "x86", | ||
| 360 | [12] = "arm", | ||
| 361 | } | ||
| 362 | |||
| 363 | local mach_b32: {integer:Processor} = { | ||
| 364 | [0] = "ppc", | ||
| 365 | } | ||
| 366 | |||
| 367 | local function detect_mach(magic: string, fd: FILE): System, Processor | ||
| 368 | if not magic then | ||
| 369 | return nil | ||
| 370 | end | ||
| 371 | |||
| 372 | if magic == hex("$CA$FE$BA$BE") then | ||
| 373 | -- fat binary, go for the first one | ||
| 374 | fd:seek("set", 0x12) | ||
| 375 | local offs = read_int8(fd) | ||
| 376 | if not offs then | ||
| 377 | return nil | ||
| 378 | end | ||
| 379 | fd:seek("set", offs * 256) | ||
| 380 | magic = fd:read(4) | ||
| 381 | return detect_mach(magic, fd) | ||
| 382 | end | ||
| 383 | |||
| 384 | local cputype = read_int8(fd) | ||
| 385 | |||
| 386 | if magic == hex("$CF$FA$ED$FE") then | ||
| 387 | return "macosx", mach_l64[cputype] or "unknown" | ||
| 388 | elseif magic == hex("$FE$ED$CF$FA") then | ||
| 389 | return "macosx", mach_b64[cputype] or "unknown" | ||
| 390 | elseif magic == hex("$CE$FA$ED$FE") then | ||
| 391 | return "macosx", mach_l32[cputype] or "unknown" | ||
| 392 | elseif magic == hex("$FE$ED$FA$CE") then | ||
| 393 | return "macosx", mach_b32[cputype] or "unknown" | ||
| 394 | end | ||
| 395 | end | ||
| 396 | |||
| 397 | -------------------------------------------------------------------------------- | ||
| 398 | -- @section PE (Windows) | ||
| 399 | -------------------------------------------------------------------------------- | ||
| 400 | |||
| 401 | local pe_machine = { | ||
| 402 | [0x8664] = "x86_64", | ||
| 403 | [0x01c0] = "arm", | ||
| 404 | [0x01c4] = "armv7l", | ||
| 405 | [0xaa64] = "arm64", | ||
| 406 | [0x014c] = "x86", | ||
| 407 | } | ||
| 408 | |||
| 409 | local function detect_pe(fd: FILE): System, Processor | ||
| 410 | fd:seek("set", 60) -- position of PE header position | ||
| 411 | local peoffset = read_int32le(fd) -- read position of PE header | ||
| 412 | if not peoffset then | ||
| 413 | return nil | ||
| 414 | end | ||
| 415 | local system: System = "windows" | ||
| 416 | fd:seek("set", peoffset + 4) -- move to position of Machine section | ||
| 417 | local machine = read(fd, 2, "little") | ||
| 418 | local processor: Processor = pe_machine[machine] | ||
| 419 | |||
| 420 | local rdata_pos_s = fd:read(736):match(".rdata%z%z............(....)") | ||
| 421 | if rdata_pos_s then | ||
| 422 | local rdata_pos = bytes2number(rdata_pos_s, "little") | ||
| 423 | fd:seek("set", rdata_pos) | ||
| 424 | local data = fd:read(512) | ||
| 425 | if data:match("cygwin") or data:match("cyggcc") then | ||
| 426 | system = "cygwin" | ||
| 427 | end | ||
| 428 | end | ||
| 429 | |||
| 430 | return system, processor or "unknown" | ||
| 431 | end | ||
| 432 | |||
| 433 | -------------------------------------------------------------------------------- | ||
| 434 | -- @section API | ||
| 435 | -------------------------------------------------------------------------------- | ||
| 436 | |||
| 437 | function sysdetect.detect_file(file: string): System, Processor | ||
| 438 | assert(type(file) == "string") | ||
| 439 | local fd = io.open(file, "rb") | ||
| 440 | if not fd then | ||
| 441 | return nil | ||
| 442 | end | ||
| 443 | local magic = fd:read(4) | ||
| 444 | if magic == hex("$7FELF") then | ||
| 445 | return detect_elf(fd) | ||
| 446 | end | ||
| 447 | if magic == hex("MZ$90$00") then | ||
| 448 | return detect_pe(fd) | ||
| 449 | end | ||
| 450 | return detect_mach(magic, fd) | ||
| 451 | end | ||
| 452 | |||
| 453 | local cache_system: System | ||
| 454 | local cache_processor: Processor | ||
| 455 | |||
| 456 | function sysdetect.detect(input_file: string): System, Processor | ||
| 457 | local dirsep = package.config:sub(1,1) | ||
| 458 | local files: {string} | ||
| 459 | |||
| 460 | if input_file then | ||
| 461 | files = { input_file } | ||
| 462 | else | ||
| 463 | if cache_system then | ||
| 464 | return cache_system, cache_processor | ||
| 465 | end | ||
| 466 | |||
| 467 | local PATHsep: string | ||
| 468 | local interp = arg and arg[-1] | ||
| 469 | if dirsep == "/" then | ||
| 470 | -- Unix | ||
| 471 | files = { | ||
| 472 | "/bin/sh", -- Unix: well-known POSIX path | ||
| 473 | "/proc/self/exe", -- Linux: this should always have a working binary | ||
| 474 | } | ||
| 475 | PATHsep = ":" | ||
| 476 | else | ||
| 477 | -- Windows | ||
| 478 | local systemroot = os.getenv("SystemRoot") | ||
| 479 | files = { | ||
| 480 | systemroot .. "\\system32\\notepad.exe", -- well-known Windows path | ||
| 481 | systemroot .. "\\explorer.exe", -- well-known Windows path | ||
| 482 | } | ||
| 483 | if interp and not interp:lower():match("exe$") then | ||
| 484 | interp = interp .. ".exe" | ||
| 485 | end | ||
| 486 | PATHsep = ";" | ||
| 487 | end | ||
| 488 | if interp then | ||
| 489 | if interp:match(dirsep) then | ||
| 490 | -- interpreter path is absolute | ||
| 491 | table.insert(files, 1, interp) | ||
| 492 | else | ||
| 493 | for d in (os.getenv("PATH") or ""):gmatch("[^"..PATHsep.."]+") do | ||
| 494 | table.insert(files, d .. dirsep .. interp) | ||
| 495 | end | ||
| 496 | end | ||
| 497 | end | ||
| 498 | end | ||
| 499 | for _, f in ipairs(files) do | ||
| 500 | local system, processor = sysdetect.detect_file(f) | ||
| 501 | if system then | ||
| 502 | cache_system = system | ||
| 503 | cache_processor = processor | ||
| 504 | return system, processor | ||
| 505 | end | ||
| 506 | end | ||
| 507 | end | ||
| 508 | |||
| 509 | return sysdetect | ||
diff --git a/src/luarocks/core/util-original.lua b/src/luarocks/core/util-original.lua new file mode 100644 index 00000000..e9abdd34 --- /dev/null +++ b/src/luarocks/core/util-original.lua | |||
| @@ -0,0 +1,322 @@ | |||
| 1 | |||
| 2 | local util = {} | ||
| 3 | |||
| 4 | local require = nil | ||
| 5 | -------------------------------------------------------------------------------- | ||
| 6 | |||
| 7 | local dir_sep = package.config:sub(1, 1) | ||
| 8 | |||
| 9 | --- Run a process and read a its output. | ||
| 10 | -- Equivalent to io.popen(cmd):read("*l"), except that it | ||
| 11 | -- closes the fd right away. | ||
| 12 | -- @param cmd string: The command to execute | ||
| 13 | -- @param spec string: "*l" by default, to read a single line. | ||
| 14 | -- May be used to read more, passing, for instance, "*a". | ||
| 15 | -- @return string: the output of the program. | ||
| 16 | function util.popen_read(cmd, spec) | ||
| 17 | local tmpfile = (dir_sep == "\\") | ||
| 18 | and (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000))) | ||
| 19 | or os.tmpname() | ||
| 20 | os.execute(cmd .. " > " .. tmpfile) | ||
| 21 | local fd = io.open(tmpfile, "rb") | ||
| 22 | if not fd then | ||
| 23 | os.remove(tmpfile) | ||
| 24 | return "" | ||
| 25 | end | ||
| 26 | local out = fd:read(spec or "*l") | ||
| 27 | fd:close() | ||
| 28 | os.remove(tmpfile) | ||
| 29 | return out or "" | ||
| 30 | end | ||
| 31 | |||
| 32 | --- | ||
| 33 | -- Formats tables with cycles recursively to any depth. | ||
| 34 | -- References to other tables are shown as values. | ||
| 35 | -- Self references are indicated. | ||
| 36 | -- The string returned is "Lua code", which can be processed | ||
| 37 | -- (in the case in which indent is composed by spaces or "--"). | ||
| 38 | -- Userdata and function keys and values are shown as strings, | ||
| 39 | -- which logically are exactly not equivalent to the original code. | ||
| 40 | -- This routine can serve for pretty formating tables with | ||
| 41 | -- proper indentations, apart from printing them: | ||
| 42 | -- io.write(table.show(t, "t")) -- a typical use | ||
| 43 | -- Written by Julio Manuel Fernandez-Diaz, | ||
| 44 | -- Heavily based on "Saving tables with cycles", PIL2, p. 113. | ||
| 45 | -- @param t table: is the table. | ||
| 46 | -- @param tname string: is the name of the table (optional) | ||
| 47 | -- @param top_indent string: is a first indentation (optional). | ||
| 48 | -- @return string: the pretty-printed table | ||
| 49 | function util.show_table(t, tname, top_indent) | ||
| 50 | local cart -- a container | ||
| 51 | local autoref -- for self references | ||
| 52 | |||
| 53 | local function is_empty_table(tbl) return next(tbl) == nil end | ||
| 54 | |||
| 55 | local function basic_serialize(o) | ||
| 56 | local so = tostring(o) | ||
| 57 | if type(o) == "function" then | ||
| 58 | local info = debug and debug.getinfo(o, "S") | ||
| 59 | if not info then | ||
| 60 | return ("%q"):format(so) | ||
| 61 | end | ||
| 62 | -- info.name is nil because o is not a calling level | ||
| 63 | if info.what == "C" then | ||
| 64 | return ("%q"):format(so .. ", C function") | ||
| 65 | else | ||
| 66 | -- the information is defined through lines | ||
| 67 | return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source) | ||
| 68 | end | ||
| 69 | elseif type(o) == "number" then | ||
| 70 | return so | ||
| 71 | else | ||
| 72 | return ("%q"):format(so) | ||
| 73 | end | ||
| 74 | end | ||
| 75 | |||
| 76 | local function add_to_cart(value, name, indent, saved, field) | ||
| 77 | indent = indent or "" | ||
| 78 | saved = saved or {} | ||
| 79 | field = field or name | ||
| 80 | |||
| 81 | cart = cart .. indent .. field | ||
| 82 | |||
| 83 | if type(value) ~= "table" then | ||
| 84 | cart = cart .. " = " .. basic_serialize(value) .. ";\n" | ||
| 85 | else | ||
| 86 | if saved[value] then | ||
| 87 | cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n" | ||
| 88 | autoref = autoref .. name .. " = " .. saved[value] .. ";\n" | ||
| 89 | else | ||
| 90 | saved[value] = name | ||
| 91 | if is_empty_table(value) then | ||
| 92 | cart = cart .. " = {};\n" | ||
| 93 | else | ||
| 94 | cart = cart .. " = {\n" | ||
| 95 | for k, v in pairs(value) do | ||
| 96 | k = basic_serialize(k) | ||
| 97 | local fname = ("%s[%s]"):format(name, k) | ||
| 98 | field = ("[%s]"):format(k) | ||
| 99 | -- three spaces between levels | ||
| 100 | add_to_cart(v, fname, indent .. " ", saved, field) | ||
| 101 | end | ||
| 102 | cart = cart .. indent .. "};\n" | ||
| 103 | end | ||
| 104 | end | ||
| 105 | end | ||
| 106 | end | ||
| 107 | |||
| 108 | tname = tname or "__unnamed__" | ||
| 109 | if type(t) ~= "table" then | ||
| 110 | return tname .. " = " .. basic_serialize(t) | ||
| 111 | end | ||
| 112 | cart, autoref = "", "" | ||
| 113 | add_to_cart(t, tname, top_indent) | ||
| 114 | return cart .. autoref | ||
| 115 | end | ||
| 116 | |||
| 117 | --- Merges contents of src on top of dst's contents | ||
| 118 | -- (i.e. if an key from src already exists in dst, replace it). | ||
| 119 | -- @param dst Destination table, which will receive src's contents. | ||
| 120 | -- @param src Table which provides new contents to dst. | ||
| 121 | function util.deep_merge(dst, src) | ||
| 122 | for k, v in pairs(src) do | ||
| 123 | if type(v) == "table" then | ||
| 124 | if dst[k] == nil then | ||
| 125 | dst[k] = {} | ||
| 126 | end | ||
| 127 | if type(dst[k]) == "table" then | ||
| 128 | util.deep_merge(dst[k], v) | ||
| 129 | else | ||
| 130 | dst[k] = v | ||
| 131 | end | ||
| 132 | else | ||
| 133 | dst[k] = v | ||
| 134 | end | ||
| 135 | end | ||
| 136 | end | ||
| 137 | |||
| 138 | --- Merges contents of src below those of dst's contents | ||
| 139 | -- (i.e. if an key from src already exists in dst, do not replace it). | ||
| 140 | -- @param dst Destination table, which will receive src's contents. | ||
| 141 | -- @param src Table which provides new contents to dst. | ||
| 142 | function util.deep_merge_under(dst, src) | ||
| 143 | for k, v in pairs(src) do | ||
| 144 | if type(v) == "table" then | ||
| 145 | if dst[k] == nil then | ||
| 146 | dst[k] = {} | ||
| 147 | end | ||
| 148 | if type(dst[k]) == "table" then | ||
| 149 | util.deep_merge_under(dst[k], v) | ||
| 150 | end | ||
| 151 | elseif dst[k] == nil then | ||
| 152 | dst[k] = v | ||
| 153 | end | ||
| 154 | end | ||
| 155 | end | ||
| 156 | |||
| 157 | --- Clean up a path-style string ($PATH, $LUA_PATH/package.path, etc.), | ||
| 158 | -- removing repeated entries and making sure only the relevant | ||
| 159 | -- Lua version is used. | ||
| 160 | -- Example: given ("a;b;c;a;b;d", ";"), returns "a;b;c;d". | ||
| 161 | -- @param list string: A path string (from $PATH or package.path) | ||
| 162 | -- @param sep string: The separator | ||
| 163 | -- @param lua_version (optional) string: The Lua version to use. | ||
| 164 | -- @param keep_first (optional) if true, keep first occurrence in case | ||
| 165 | -- of duplicates; otherwise keep last occurrence. The default is false. | ||
| 166 | function util.cleanup_path(list, sep, lua_version, keep_first) | ||
| 167 | assert(type(list) == "string") | ||
| 168 | assert(type(sep) == "string") | ||
| 169 | |||
| 170 | list = list:gsub(dir_sep, "/") | ||
| 171 | |||
| 172 | local parts = util.split_string(list, sep) | ||
| 173 | local final, entries = {}, {} | ||
| 174 | local start, stop, step | ||
| 175 | |||
| 176 | if keep_first then | ||
| 177 | start, stop, step = 1, #parts, 1 | ||
| 178 | else | ||
| 179 | start, stop, step = #parts, 1, -1 | ||
| 180 | end | ||
| 181 | |||
| 182 | for i = start, stop, step do | ||
| 183 | local part = parts[i]:gsub("//", "/") | ||
| 184 | if lua_version then | ||
| 185 | part = part:gsub("/lua/([%d.]+)/", function(part_version) | ||
| 186 | if part_version:sub(1, #lua_version) ~= lua_version then | ||
| 187 | return "/lua/"..lua_version.."/" | ||
| 188 | end | ||
| 189 | end) | ||
| 190 | end | ||
| 191 | if not entries[part] then | ||
| 192 | local at = keep_first and #final+1 or 1 | ||
| 193 | table.insert(final, at, part) | ||
| 194 | entries[part] = true | ||
| 195 | end | ||
| 196 | end | ||
| 197 | |||
| 198 | return (table.concat(final, sep):gsub("/", dir_sep)) | ||
| 199 | end | ||
| 200 | |||
| 201 | -- from http://lua-users.org/wiki/SplitJoin | ||
| 202 | -- by Philippe Lhoste | ||
| 203 | function util.split_string(str, delim, maxNb) | ||
| 204 | -- Eliminate bad cases... | ||
| 205 | if string.find(str, delim) == nil then | ||
| 206 | return { str } | ||
| 207 | end | ||
| 208 | if maxNb == nil or maxNb < 1 then | ||
| 209 | maxNb = 0 -- No limit | ||
| 210 | end | ||
| 211 | local result = {} | ||
| 212 | local pat = "(.-)" .. delim .. "()" | ||
| 213 | local nb = 0 | ||
| 214 | local lastPos | ||
| 215 | for part, pos in string.gmatch(str, pat) do | ||
| 216 | nb = nb + 1 | ||
| 217 | result[nb] = part | ||
| 218 | lastPos = pos | ||
| 219 | if nb == maxNb then break end | ||
| 220 | end | ||
| 221 | -- Handle the last field | ||
| 222 | if nb ~= maxNb then | ||
| 223 | result[nb + 1] = string.sub(str, lastPos) | ||
| 224 | end | ||
| 225 | return result | ||
| 226 | end | ||
| 227 | |||
| 228 | --- Return an array of keys of a table. | ||
| 229 | -- @param tbl table: The input table. | ||
| 230 | -- @return table: The array of keys. | ||
| 231 | function util.keys(tbl) | ||
| 232 | local ks = {} | ||
| 233 | for k,_ in pairs(tbl) do | ||
| 234 | table.insert(ks, k) | ||
| 235 | end | ||
| 236 | return ks | ||
| 237 | end | ||
| 238 | |||
| 239 | --- Print a line to standard error | ||
| 240 | function util.printerr(...) | ||
| 241 | io.stderr:write(table.concat({...},"\t")) | ||
| 242 | io.stderr:write("\n") | ||
| 243 | end | ||
| 244 | |||
| 245 | --- Display a warning message. | ||
| 246 | -- @param msg string: the warning message | ||
| 247 | function util.warning(msg) | ||
| 248 | util.printerr("Warning: "..msg) | ||
| 249 | end | ||
| 250 | |||
| 251 | --- Simple sort function used as a default for util.sortedpairs. | ||
| 252 | local function default_sort(a, b) | ||
| 253 | local ta = type(a) | ||
| 254 | local tb = type(b) | ||
| 255 | if ta == "number" and tb == "number" then | ||
| 256 | return a < b | ||
| 257 | elseif ta == "number" then | ||
| 258 | return true | ||
| 259 | elseif tb == "number" then | ||
| 260 | return false | ||
| 261 | else | ||
| 262 | return tostring(a) < tostring(b) | ||
| 263 | end | ||
| 264 | end | ||
| 265 | |||
| 266 | --- A table iterator generator that returns elements sorted by key, | ||
| 267 | -- to be used in "for" loops. | ||
| 268 | -- @param tbl table: The table to be iterated. | ||
| 269 | -- @param sort_function function or table or nil: An optional comparison function | ||
| 270 | -- to be used by table.sort when sorting keys, or an array listing an explicit order | ||
| 271 | -- for keys. If a value itself is an array, it is taken so that the first element | ||
| 272 | -- is a string representing the field name, and the second element is a priority table | ||
| 273 | -- for that key, which is returned by the iterator as the third value after the key | ||
| 274 | -- and the value. | ||
| 275 | -- @return function: the iterator function. | ||
| 276 | function util.sortedpairs(tbl, sort_function) | ||
| 277 | sort_function = sort_function or default_sort | ||
| 278 | local keys = util.keys(tbl) | ||
| 279 | local sub_orders = {} | ||
| 280 | |||
| 281 | if type(sort_function) == "function" then | ||
| 282 | table.sort(keys, sort_function) | ||
| 283 | else | ||
| 284 | local order = sort_function | ||
| 285 | local ordered_keys = {} | ||
| 286 | local all_keys = keys | ||
| 287 | keys = {} | ||
| 288 | |||
| 289 | for _, order_entry in ipairs(order) do | ||
| 290 | local key, sub_order | ||
| 291 | if type(order_entry) == "table" then | ||
| 292 | key = order_entry[1] | ||
| 293 | sub_order = order_entry[2] | ||
| 294 | else | ||
| 295 | key = order_entry | ||
| 296 | end | ||
| 297 | |||
| 298 | if tbl[key] then | ||
| 299 | ordered_keys[key] = true | ||
| 300 | sub_orders[key] = sub_order | ||
| 301 | table.insert(keys, key) | ||
| 302 | end | ||
| 303 | end | ||
| 304 | |||
| 305 | table.sort(all_keys, default_sort) | ||
| 306 | for _, key in ipairs(all_keys) do | ||
| 307 | if not ordered_keys[key] then | ||
| 308 | table.insert(keys, key) | ||
| 309 | end | ||
| 310 | end | ||
| 311 | end | ||
| 312 | |||
| 313 | local i = 1 | ||
| 314 | return function() | ||
| 315 | local key = keys[i] | ||
| 316 | i = i + 1 | ||
| 317 | return key, tbl[key], sub_orders[key] | ||
| 318 | end | ||
| 319 | end | ||
| 320 | |||
| 321 | return util | ||
| 322 | |||
diff --git a/src/luarocks/core/util.lua b/src/luarocks/core/util.lua index e9abdd34..44c9e63b 100644 --- a/src/luarocks/core/util.lua +++ b/src/luarocks/core/util.lua | |||
| @@ -1,22 +1,22 @@ | |||
| 1 | 1 | local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local debug = _tl_compat and _tl_compat.debug or debug; local io = _tl_compat and _tl_compat.io or io; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local os = _tl_compat and _tl_compat.os or os; local package = _tl_compat and _tl_compat.package or package; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table | |
| 2 | local util = {} | 2 | local util = {} |
| 3 | 3 | ||
| 4 | local require = nil | 4 | local require = nil |
| 5 | -------------------------------------------------------------------------------- | 5 | |
| 6 | 6 | ||
| 7 | local dir_sep = package.config:sub(1, 1) | 7 | local dir_sep = package.config:sub(1, 1) |
| 8 | 8 | ||
| 9 | --- Run a process and read a its output. | 9 | |
| 10 | -- Equivalent to io.popen(cmd):read("*l"), except that it | 10 | |
| 11 | -- closes the fd right away. | 11 | |
| 12 | -- @param cmd string: The command to execute | 12 | |
| 13 | -- @param spec string: "*l" by default, to read a single line. | 13 | |
| 14 | -- May be used to read more, passing, for instance, "*a". | 14 | |
| 15 | -- @return string: the output of the program. | 15 | |
| 16 | function util.popen_read(cmd, spec) | 16 | function util.popen_read(cmd, spec) |
| 17 | local tmpfile = (dir_sep == "\\") | 17 | local tmpfile = (dir_sep == "\\") and |
| 18 | and (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000))) | 18 | (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000))) or |
| 19 | or os.tmpname() | 19 | os.tmpname() |
| 20 | os.execute(cmd .. " > " .. tmpfile) | 20 | os.execute(cmd .. " > " .. tmpfile) |
| 21 | local fd = io.open(tmpfile, "rb") | 21 | local fd = io.open(tmpfile, "rb") |
| 22 | if not fd then | 22 | if not fd then |
| @@ -29,26 +29,26 @@ function util.popen_read(cmd, spec) | |||
| 29 | return out or "" | 29 | return out or "" |
| 30 | end | 30 | end |
| 31 | 31 | ||
| 32 | --- | 32 | |
| 33 | -- Formats tables with cycles recursively to any depth. | 33 | |
| 34 | -- References to other tables are shown as values. | 34 | |
| 35 | -- Self references are indicated. | 35 | |
| 36 | -- The string returned is "Lua code", which can be processed | 36 | |
| 37 | -- (in the case in which indent is composed by spaces or "--"). | 37 | |
| 38 | -- Userdata and function keys and values are shown as strings, | 38 | |
| 39 | -- which logically are exactly not equivalent to the original code. | 39 | |
| 40 | -- This routine can serve for pretty formating tables with | 40 | |
| 41 | -- proper indentations, apart from printing them: | 41 | |
| 42 | -- io.write(table.show(t, "t")) -- a typical use | 42 | |
| 43 | -- Written by Julio Manuel Fernandez-Diaz, | 43 | |
| 44 | -- Heavily based on "Saving tables with cycles", PIL2, p. 113. | 44 | |
| 45 | -- @param t table: is the table. | 45 | |
| 46 | -- @param tname string: is the name of the table (optional) | 46 | |
| 47 | -- @param top_indent string: is a first indentation (optional). | 47 | |
| 48 | -- @return string: the pretty-printed table | 48 | |
| 49 | function util.show_table(t, tname, top_indent) | 49 | function util.show_table(t, tname, top_indent) |
| 50 | local cart -- a container | 50 | local cart |
| 51 | local autoref -- for self references | 51 | local autoref |
| 52 | 52 | ||
| 53 | local function is_empty_table(tbl) return next(tbl) == nil end | 53 | local function is_empty_table(tbl) return next(tbl) == nil end |
| 54 | 54 | ||
| @@ -59,11 +59,11 @@ function util.show_table(t, tname, top_indent) | |||
| 59 | if not info then | 59 | if not info then |
| 60 | return ("%q"):format(so) | 60 | return ("%q"):format(so) |
| 61 | end | 61 | end |
| 62 | -- info.name is nil because o is not a calling level | 62 | |
| 63 | if info.what == "C" then | 63 | if info.what == "C" then |
| 64 | return ("%q"):format(so .. ", C function") | 64 | return ("%q"):format(so .. ", C function") |
| 65 | else | 65 | else |
| 66 | -- the information is defined through lines | 66 | |
| 67 | return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source) | 67 | return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source) |
| 68 | end | 68 | end |
| 69 | elseif type(o) == "number" then | 69 | elseif type(o) == "number" then |
| @@ -85,7 +85,7 @@ function util.show_table(t, tname, top_indent) | |||
| 85 | else | 85 | else |
| 86 | if saved[value] then | 86 | if saved[value] then |
| 87 | cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n" | 87 | cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n" |
| 88 | autoref = autoref .. name .. " = " .. saved[value] .. ";\n" | 88 | autoref = autoref .. name .. " = " .. saved[value] .. ";\n" |
| 89 | else | 89 | else |
| 90 | saved[value] = name | 90 | saved[value] = name |
| 91 | if is_empty_table(value) then | 91 | if is_empty_table(value) then |
| @@ -96,7 +96,7 @@ function util.show_table(t, tname, top_indent) | |||
| 96 | k = basic_serialize(k) | 96 | k = basic_serialize(k) |
| 97 | local fname = ("%s[%s]"):format(name, k) | 97 | local fname = ("%s[%s]"):format(name, k) |
| 98 | field = ("[%s]"):format(k) | 98 | field = ("[%s]"):format(k) |
| 99 | -- three spaces between levels | 99 | |
| 100 | add_to_cart(v, fname, indent .. " ", saved, field) | 100 | add_to_cart(v, fname, indent .. " ", saved, field) |
| 101 | end | 101 | end |
| 102 | cart = cart .. indent .. "};\n" | 102 | cart = cart .. indent .. "};\n" |
| @@ -114,10 +114,10 @@ function util.show_table(t, tname, top_indent) | |||
| 114 | return cart .. autoref | 114 | return cart .. autoref |
| 115 | end | 115 | end |
| 116 | 116 | ||
| 117 | --- Merges contents of src on top of dst's contents | 117 | |
| 118 | -- (i.e. if an key from src already exists in dst, replace it). | 118 | |
| 119 | -- @param dst Destination table, which will receive src's contents. | 119 | |
| 120 | -- @param src Table which provides new contents to dst. | 120 | |
| 121 | function util.deep_merge(dst, src) | 121 | function util.deep_merge(dst, src) |
| 122 | for k, v in pairs(src) do | 122 | for k, v in pairs(src) do |
| 123 | if type(v) == "table" then | 123 | if type(v) == "table" then |
| @@ -135,10 +135,10 @@ function util.deep_merge(dst, src) | |||
| 135 | end | 135 | end |
| 136 | end | 136 | end |
| 137 | 137 | ||
| 138 | --- Merges contents of src below those of dst's contents | 138 | |
| 139 | -- (i.e. if an key from src already exists in dst, do not replace it). | 139 | |
| 140 | -- @param dst Destination table, which will receive src's contents. | 140 | |
| 141 | -- @param src Table which provides new contents to dst. | 141 | |
| 142 | function util.deep_merge_under(dst, src) | 142 | function util.deep_merge_under(dst, src) |
| 143 | for k, v in pairs(src) do | 143 | for k, v in pairs(src) do |
| 144 | if type(v) == "table" then | 144 | if type(v) == "table" then |
| @@ -154,15 +154,15 @@ function util.deep_merge_under(dst, src) | |||
| 154 | end | 154 | end |
| 155 | end | 155 | end |
| 156 | 156 | ||
| 157 | --- Clean up a path-style string ($PATH, $LUA_PATH/package.path, etc.), | 157 | |
| 158 | -- removing repeated entries and making sure only the relevant | 158 | |
| 159 | -- Lua version is used. | 159 | |
| 160 | -- Example: given ("a;b;c;a;b;d", ";"), returns "a;b;c;d". | 160 | |
| 161 | -- @param list string: A path string (from $PATH or package.path) | 161 | |
| 162 | -- @param sep string: The separator | 162 | |
| 163 | -- @param lua_version (optional) string: The Lua version to use. | 163 | |
| 164 | -- @param keep_first (optional) if true, keep first occurrence in case | 164 | |
| 165 | -- of duplicates; otherwise keep last occurrence. The default is false. | 165 | |
| 166 | function util.cleanup_path(list, sep, lua_version, keep_first) | 166 | function util.cleanup_path(list, sep, lua_version, keep_first) |
| 167 | assert(type(list) == "string") | 167 | assert(type(list) == "string") |
| 168 | assert(type(sep) == "string") | 168 | assert(type(sep) == "string") |
| @@ -184,12 +184,12 @@ function util.cleanup_path(list, sep, lua_version, keep_first) | |||
| 184 | if lua_version then | 184 | if lua_version then |
| 185 | part = part:gsub("/lua/([%d.]+)/", function(part_version) | 185 | part = part:gsub("/lua/([%d.]+)/", function(part_version) |
| 186 | if part_version:sub(1, #lua_version) ~= lua_version then | 186 | if part_version:sub(1, #lua_version) ~= lua_version then |
| 187 | return "/lua/"..lua_version.."/" | 187 | return "/lua/" .. lua_version .. "/" |
| 188 | end | 188 | end |
| 189 | end) | 189 | end) |
| 190 | end | 190 | end |
| 191 | if not entries[part] then | 191 | if not entries[part] then |
| 192 | local at = keep_first and #final+1 or 1 | 192 | local at = keep_first and #final + 1 or 1 |
| 193 | table.insert(final, at, part) | 193 | table.insert(final, at, part) |
| 194 | entries[part] = true | 194 | entries[part] = true |
| 195 | end | 195 | end |
| @@ -198,15 +198,15 @@ function util.cleanup_path(list, sep, lua_version, keep_first) | |||
| 198 | return (table.concat(final, sep):gsub("/", dir_sep)) | 198 | return (table.concat(final, sep):gsub("/", dir_sep)) |
| 199 | end | 199 | end |
| 200 | 200 | ||
| 201 | -- from http://lua-users.org/wiki/SplitJoin | 201 | |
| 202 | -- by Philippe Lhoste | 202 | |
| 203 | function util.split_string(str, delim, maxNb) | 203 | function util.split_string(str, delim, maxNb) |
| 204 | -- Eliminate bad cases... | 204 | |
| 205 | if string.find(str, delim) == nil then | 205 | if string.find(str, delim) == nil then |
| 206 | return { str } | 206 | return { str } |
| 207 | end | 207 | end |
| 208 | if maxNb == nil or maxNb < 1 then | 208 | if maxNb == nil or maxNb < 1 then |
| 209 | maxNb = 0 -- No limit | 209 | maxNb = 0 |
| 210 | end | 210 | end |
| 211 | local result = {} | 211 | local result = {} |
| 212 | local pat = "(.-)" .. delim .. "()" | 212 | local pat = "(.-)" .. delim .. "()" |
| @@ -218,42 +218,42 @@ function util.split_string(str, delim, maxNb) | |||
| 218 | lastPos = pos | 218 | lastPos = pos |
| 219 | if nb == maxNb then break end | 219 | if nb == maxNb then break end |
| 220 | end | 220 | end |
| 221 | -- Handle the last field | 221 | |
| 222 | if nb ~= maxNb then | 222 | if nb ~= maxNb then |
| 223 | result[nb + 1] = string.sub(str, lastPos) | 223 | result[nb + 1] = string.sub(str, lastPos) |
| 224 | end | 224 | end |
| 225 | return result | 225 | return result |
| 226 | end | 226 | end |
| 227 | 227 | ||
| 228 | --- Return an array of keys of a table. | 228 | |
| 229 | -- @param tbl table: The input table. | 229 | |
| 230 | -- @return table: The array of keys. | 230 | |
| 231 | function util.keys(tbl) | 231 | function util.keys(tbl) |
| 232 | local ks = {} | 232 | local ks = {} |
| 233 | for k,_ in pairs(tbl) do | 233 | for k, _ in pairs(tbl) do |
| 234 | table.insert(ks, k) | 234 | table.insert(ks, k) |
| 235 | end | 235 | end |
| 236 | return ks | 236 | return ks |
| 237 | end | 237 | end |
| 238 | 238 | ||
| 239 | --- Print a line to standard error | 239 | |
| 240 | function util.printerr(...) | 240 | function util.printerr(...) |
| 241 | io.stderr:write(table.concat({...},"\t")) | 241 | io.stderr:write(table.concat({ ... }, "\t")) |
| 242 | io.stderr:write("\n") | 242 | io.stderr:write("\n") |
| 243 | end | 243 | end |
| 244 | 244 | ||
| 245 | --- Display a warning message. | 245 | |
| 246 | -- @param msg string: the warning message | 246 | |
| 247 | function util.warning(msg) | 247 | function util.warning(msg) |
| 248 | util.printerr("Warning: "..msg) | 248 | util.printerr("Warning: " .. msg) |
| 249 | end | 249 | end |
| 250 | 250 | ||
| 251 | --- Simple sort function used as a default for util.sortedpairs. | 251 | |
| 252 | local function default_sort(a, b) | 252 | local function default_sort(a, b) |
| 253 | local ta = type(a) | 253 | local ta = type(a) |
| 254 | local tb = type(b) | 254 | local tb = type(b) |
| 255 | if ta == "number" and tb == "number" then | 255 | if ta == "number" and tb == "number" then |
| 256 | return a < b | 256 | return tonumber(a) < tonumber(b) |
| 257 | elseif ta == "number" then | 257 | elseif ta == "number" then |
| 258 | return true | 258 | return true |
| 259 | elseif tb == "number" then | 259 | elseif tb == "number" then |
| @@ -263,16 +263,16 @@ local function default_sort(a, b) | |||
| 263 | end | 263 | end |
| 264 | end | 264 | end |
| 265 | 265 | ||
| 266 | --- A table iterator generator that returns elements sorted by key, | 266 | |
| 267 | -- to be used in "for" loops. | 267 | |
| 268 | -- @param tbl table: The table to be iterated. | 268 | |
| 269 | -- @param sort_function function or table or nil: An optional comparison function | 269 | |
| 270 | -- to be used by table.sort when sorting keys, or an array listing an explicit order | 270 | |
| 271 | -- for keys. If a value itself is an array, it is taken so that the first element | 271 | |
| 272 | -- is a string representing the field name, and the second element is a priority table | 272 | |
| 273 | -- for that key, which is returned by the iterator as the third value after the key | 273 | |
| 274 | -- and the value. | 274 | |
| 275 | -- @return function: the iterator function. | 275 | |
| 276 | function util.sortedpairs(tbl, sort_function) | 276 | function util.sortedpairs(tbl, sort_function) |
| 277 | sort_function = sort_function or default_sort | 277 | sort_function = sort_function or default_sort |
| 278 | local keys = util.keys(tbl) | 278 | local keys = util.keys(tbl) |
| @@ -319,4 +319,3 @@ function util.sortedpairs(tbl, sort_function) | |||
| 319 | end | 319 | end |
| 320 | 320 | ||
| 321 | return util | 321 | return util |
| 322 | |||
diff --git a/src/luarocks/core/util.tl b/src/luarocks/core/util.tl new file mode 100644 index 00000000..5fd423a9 --- /dev/null +++ b/src/luarocks/core/util.tl | |||
| @@ -0,0 +1,322 @@ | |||
| 1 | |||
| 2 | local util = {} | ||
| 3 | |||
| 4 | local require = nil --! | ||
| 5 | -------------------------------------------------------------------------------- | ||
| 6 | |||
| 7 | local dir_sep = package.config:sub(1, 1) | ||
| 8 | |||
| 9 | --- Run a process and read a its output. | ||
| 10 | -- Equivalent to io.popen(cmd):read("*l"), except that it | ||
| 11 | -- closes the fd right away. | ||
| 12 | -- @param cmd string: The command to execute | ||
| 13 | -- @param spec string: "*l" by default, to read a single line. | ||
| 14 | -- May be used to read more, passing, for instance, "*a". | ||
| 15 | -- @return string: the output of the program. | ||
| 16 | function util.popen_read(cmd: string, spec: string): string | ||
| 17 | local tmpfile: string = (dir_sep == "\\") | ||
| 18 | and (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000))) | ||
| 19 | or os.tmpname() | ||
| 20 | os.execute(cmd .. " > " .. tmpfile) | ||
| 21 | local fd = io.open(tmpfile, "rb") | ||
| 22 | if not fd then | ||
| 23 | os.remove(tmpfile) | ||
| 24 | return "" | ||
| 25 | end | ||
| 26 | local out = fd:read(spec or "*l") | ||
| 27 | fd:close() | ||
| 28 | os.remove(tmpfile) | ||
| 29 | return out or "" | ||
| 30 | end | ||
| 31 | |||
| 32 | --- | ||
| 33 | -- Formats tables with cycles recursively to any depth. | ||
| 34 | -- References to other tables are shown as values. | ||
| 35 | -- Self references are indicated. | ||
| 36 | -- The string returned is "Lua code", which can be processed | ||
| 37 | -- (in the case in which indent is composed by spaces or "--"). | ||
| 38 | -- Userdata and function keys and values are shown as strings, | ||
| 39 | -- which logically are exactly not equivalent to the original code. | ||
| 40 | -- This routine can serve for pretty formating tables with | ||
| 41 | -- proper indentations, apart from printing them: | ||
| 42 | -- io.write(table.show(t, "t")) -- a typical use | ||
| 43 | -- Written by Julio Manuel Fernandez-Diaz, | ||
| 44 | -- Heavily based on "Saving tables with cycles", PIL2, p. 113. | ||
| 45 | -- @param t table: is the table. | ||
| 46 | -- @param tname string: is the name of the table (optional) | ||
| 47 | -- @param top_indent string: is a first indentation (optional). | ||
| 48 | -- @return string: the pretty-printed table | ||
| 49 | function util.show_table(t: {any:any}, tname: string, top_indent: string): string | ||
| 50 | local cart: string -- a container | ||
| 51 | local autoref: string -- for self references | ||
| 52 | |||
| 53 | local function is_empty_table(tbl: {any:any}): boolean return next(tbl) == nil end | ||
| 54 | |||
| 55 | local function basic_serialize(o: any): string | ||
| 56 | local so = tostring(o) | ||
| 57 | if type(o) == "function" then | ||
| 58 | local info = debug and debug.getinfo(o, "S") | ||
| 59 | if not info then | ||
| 60 | return ("%q"):format(so) | ||
| 61 | end | ||
| 62 | -- info.name is nil because o is not a calling level | ||
| 63 | if info.what == "C" then | ||
| 64 | return ("%q"):format(so .. ", C function") | ||
| 65 | else | ||
| 66 | -- the information is defined through lines | ||
| 67 | return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source) | ||
| 68 | end | ||
| 69 | elseif type(o) == "number" then | ||
| 70 | return so | ||
| 71 | else | ||
| 72 | return ("%q"):format(so) | ||
| 73 | end | ||
| 74 | end | ||
| 75 | |||
| 76 | local function add_to_cart (value: {any:any}, name: string, indent: string, saved: {any: string}, field: string) --? <A,B> value: {A:B} | ||
| 77 | indent = indent or "" | ||
| 78 | saved = saved or {} | ||
| 79 | field = field or name | ||
| 80 | |||
| 81 | cart = cart .. indent .. field | ||
| 82 | |||
| 83 | if type(value) ~= "table" then --? 1 | ||
| 84 | cart = cart .. " = " .. basic_serialize(value) .. ";\n" | ||
| 85 | else | ||
| 86 | if saved[value] then | ||
| 87 | cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n" | ||
| 88 | autoref = autoref .. name .. " = " .. saved[value] .. ";\n" | ||
| 89 | else | ||
| 90 | saved[value] = name | ||
| 91 | if is_empty_table(value) then --? 1 | ||
| 92 | cart = cart .. " = {};\n" | ||
| 93 | else | ||
| 94 | cart = cart .. " = {\n" | ||
| 95 | for k, v in pairs(value) do | ||
| 96 | k = basic_serialize(k) | ||
| 97 | local fname = ("%s[%s]"):format(name, k) | ||
| 98 | field = ("[%s]"):format(k) | ||
| 99 | -- three spaces between levels | ||
| 100 | add_to_cart(v, fname, indent .. " ", saved, field) --! | ||
| 101 | end | ||
| 102 | cart = cart .. indent .. "};\n" | ||
| 103 | end | ||
| 104 | end | ||
| 105 | end | ||
| 106 | end | ||
| 107 | |||
| 108 | tname = tname or "__unnamed__" | ||
| 109 | if type(t) ~= "table" then | ||
| 110 | return tname .. " = " .. basic_serialize(t) | ||
| 111 | end | ||
| 112 | cart, autoref = "", "" | ||
| 113 | add_to_cart(t, tname, top_indent) | ||
| 114 | return cart .. autoref | ||
| 115 | end | ||
| 116 | |||
| 117 | --- Merges contents of src on top of dst's contents | ||
| 118 | -- (i.e. if an key from src already exists in dst, replace it). | ||
| 119 | -- @param dst Destination table, which will receive src's contents. | ||
| 120 | -- @param src Table which provides new contents to dst. | ||
| 121 | function util.deep_merge(dst: {integer: any}, src: {integer: any}) --? {integer: {any:any}}, {integer: {any:any}} | ||
| 122 | for k, v in pairs(src) do | ||
| 123 | if type(v) == "table" then | ||
| 124 | if dst[k] == nil then | ||
| 125 | dst[k] = {} | ||
| 126 | end | ||
| 127 | if type(dst[k]) == "table" then | ||
| 128 | util.deep_merge(dst[k], v) --! | ||
| 129 | else | ||
| 130 | dst[k] = v | ||
| 131 | end | ||
| 132 | else | ||
| 133 | dst[k] = v | ||
| 134 | end | ||
| 135 | end | ||
| 136 | end | ||
| 137 | |||
| 138 | --- Merges contents of src below those of dst's contents | ||
| 139 | -- (i.e. if an key from src already exists in dst, do not replace it). | ||
| 140 | -- @param dst Destination table, which will receive src's contents. | ||
| 141 | -- @param src Table which provides new contents to dst. | ||
| 142 | function util.deep_merge_under(dst: {integer: any}, src: {integer: any}) --? {integer: {any:any}}, {integer: {any:any}} | ||
| 143 | for k, v in pairs(src) do | ||
| 144 | if type(v) == "table" then | ||
| 145 | if dst[k] == nil then | ||
| 146 | dst[k] = {} | ||
| 147 | end | ||
| 148 | if type(dst[k]) == "table" then | ||
| 149 | util.deep_merge_under(dst[k], v) | ||
| 150 | end | ||
| 151 | elseif dst[k] == nil then | ||
| 152 | dst[k] = v | ||
| 153 | end | ||
| 154 | end | ||
| 155 | end | ||
| 156 | |||
| 157 | --- Clean up a path-style string ($PATH, $LUA_PATH/package.path, etc.), | ||
| 158 | -- removing repeated entries and making sure only the relevant | ||
| 159 | -- Lua version is used. | ||
| 160 | -- Example: given ("a;b;c;a;b;d", ";"), returns "a;b;c;d". | ||
| 161 | -- @param list string: A path string (from $PATH or package.path) | ||
| 162 | -- @param sep string: The separator | ||
| 163 | -- @param lua_version (optional) string: The Lua version to use. | ||
| 164 | -- @param keep_first (optional) if true, keep first occurrence in case | ||
| 165 | -- of duplicates; otherwise keep last occurrence. The default is false. | ||
| 166 | function util.cleanup_path(list: string, sep: string, lua_version: string, keep_first: boolean): string | ||
| 167 | assert(type(list) == "string") --? | ||
| 168 | assert(type(sep) == "string") --? | ||
| 169 | |||
| 170 | list = list:gsub(dir_sep, "/") | ||
| 171 | |||
| 172 | local parts = util.split_string(list, sep) --! | ||
| 173 | local final, entries = {}, {} | ||
| 174 | local start, stop, step: number, number, number | ||
| 175 | |||
| 176 | if keep_first then | ||
| 177 | start, stop, step = 1, #parts, 1 | ||
| 178 | else | ||
| 179 | start, stop, step = #parts, 1, -1 | ||
| 180 | end | ||
| 181 | |||
| 182 | for i = start, stop, step do | ||
| 183 | local part = parts[i]:gsub("//", "/") | ||
| 184 | if lua_version then | ||
| 185 | part = part:gsub("/lua/([%d.]+)/", function(part_version) | ||
| 186 | if part_version:sub(1, #lua_version) ~= lua_version then --! | ||
| 187 | return "/lua/"..lua_version.."/" --! | ||
| 188 | end | ||
| 189 | end) | ||
| 190 | end | ||
| 191 | if not entries[part] then | ||
| 192 | local at = keep_first and #final+1 or 1 | ||
| 193 | table.insert(final, at, part) | ||
| 194 | entries[part] = true | ||
| 195 | end | ||
| 196 | end | ||
| 197 | |||
| 198 | return (table.concat(final, sep):gsub("/", dir_sep)) --! | ||
| 199 | end | ||
| 200 | |||
| 201 | -- from http://lua-users.org/wiki/SplitJoin | ||
| 202 | -- by Philippe Lhoste | ||
| 203 | function util.split_string(str: string, delim: string, maxNb: number): {string} | ||
| 204 | -- Eliminate bad cases... | ||
| 205 | if string.find(str, delim) == nil then | ||
| 206 | return { str } | ||
| 207 | end | ||
| 208 | if maxNb == nil or maxNb < 1 then | ||
| 209 | maxNb = 0 -- No limit | ||
| 210 | end | ||
| 211 | local result = {} | ||
| 212 | local pat = "(.-)" .. delim .. "()" | ||
| 213 | local nb = 0 | ||
| 214 | local lastPos: any | ||
| 215 | for part, pos in string.gmatch(str, pat) do --! pos: number or string | ||
| 216 | nb = nb + 1 | ||
| 217 | result[nb] = part | ||
| 218 | lastPos = pos --! number or string | ||
| 219 | if nb == maxNb then break end | ||
| 220 | end | ||
| 221 | -- Handle the last field | ||
| 222 | if nb ~= maxNb then | ||
| 223 | result[nb + 1] = string.sub(str, lastPos) | ||
| 224 | end | ||
| 225 | return result | ||
| 226 | end | ||
| 227 | |||
| 228 | --- Return an array of keys of a table. | ||
| 229 | -- @param tbl table: The input table. | ||
| 230 | -- @return table: The array of keys. | ||
| 231 | function util.keys(tbl: {any:any}): {any} | ||
| 232 | local ks = {} | ||
| 233 | for k,_ in pairs(tbl) do | ||
| 234 | table.insert(ks, k) | ||
| 235 | end | ||
| 236 | return ks | ||
| 237 | end | ||
| 238 | |||
| 239 | --- Print a line to standard error | ||
| 240 | function util.printerr(...: string | number) | ||
| 241 | io.stderr:write(table.concat({...},"\t")) | ||
| 242 | io.stderr:write("\n") | ||
| 243 | end | ||
| 244 | |||
| 245 | --- Display a warning message. | ||
| 246 | -- @param msg string: the warning message | ||
| 247 | function util.warning(msg: string) | ||
| 248 | util.printerr("Warning: "..msg) | ||
| 249 | end | ||
| 250 | |||
| 251 | --- Simple sort function used as a default for util.sortedpairs. | ||
| 252 | local function default_sort(a: any, b: any): boolean | ||
| 253 | local ta = type(a) | ||
| 254 | local tb = type(b) | ||
| 255 | if ta == "number" and tb == "number" then | ||
| 256 | return tonumber(a) < tonumber(b) | ||
| 257 | elseif ta == "number" then | ||
| 258 | return true | ||
| 259 | elseif tb == "number" then | ||
| 260 | return false | ||
| 261 | else | ||
| 262 | return tostring(a) < tostring(b) | ||
| 263 | end | ||
| 264 | end | ||
| 265 | |||
| 266 | --- A table iterator generator that returns elements sorted by key, | ||
| 267 | -- to be used in "for" loops. | ||
| 268 | -- @param tbl table: The table to be iterated. | ||
| 269 | -- @param sort_function function or table or nil: An optional comparison function | ||
| 270 | -- to be used by table.sort when sorting keys, or an array listing an explicit order | ||
| 271 | -- for keys. If a value itself is an array, it is taken so that the first element | ||
| 272 | -- is a string representing the field name, and the second element is a priority table | ||
| 273 | -- for that key, which is returned by the iterator as the third value after the key | ||
| 274 | -- and the value. | ||
| 275 | -- @return function: the iterator function. | ||
| 276 | function util.sortedpairs(tbl: {any: any}, sort_function: function<any>(any, any): boolean | {any:any} | nil): function --? function<A>(A, A): boolean | {any:integer} | nil | ||
| 277 | sort_function = sort_function or default_sort | ||
| 278 | local keys = util.keys(tbl) | ||
| 279 | local sub_orders = {} | ||
| 280 | |||
| 281 | if type(sort_function) == "function" then | ||
| 282 | table.sort(keys, sort_function) --! | ||
| 283 | else | ||
| 284 | local order = sort_function | ||
| 285 | local ordered_keys = {} | ||
| 286 | local all_keys = keys | ||
| 287 | keys = {} | ||
| 288 | |||
| 289 | for _, order_entry in ipairs(order) do --! | ||
| 290 | local key, sub_order: any, any --! | ||
| 291 | if type(order_entry) == "table" then | ||
| 292 | key = order_entry[1] --! | ||
| 293 | sub_order = order_entry[2] --! | ||
| 294 | else | ||
| 295 | key = order_entry | ||
| 296 | end | ||
| 297 | |||
| 298 | if tbl[key] then | ||
| 299 | ordered_keys[key] = true | ||
| 300 | sub_orders[key] = sub_order | ||
| 301 | table.insert(keys, key) | ||
| 302 | end | ||
| 303 | end | ||
| 304 | |||
| 305 | table.sort(all_keys, default_sort) | ||
| 306 | for _, key in ipairs(all_keys) do | ||
| 307 | if not ordered_keys[key] then | ||
| 308 | table.insert(keys, key) | ||
| 309 | end | ||
| 310 | end | ||
| 311 | end | ||
| 312 | |||
| 313 | local i = 1 | ||
| 314 | return function() | ||
| 315 | local key = keys[i] | ||
| 316 | i = i + 1 | ||
| 317 | return key, tbl[key], sub_orders[key] --! | ||
| 318 | end | ||
| 319 | end | ||
| 320 | |||
| 321 | return util | ||
| 322 | |||
diff --git a/src/luarocks/core/vers-original.lua b/src/luarocks/core/vers-original.lua new file mode 100644 index 00000000..8e617984 --- /dev/null +++ b/src/luarocks/core/vers-original.lua | |||
| @@ -0,0 +1,207 @@ | |||
| 1 | |||
| 2 | local vers = {} | ||
| 3 | |||
| 4 | local util = require("luarocks.core.util") | ||
| 5 | local require = nil | ||
| 6 | -------------------------------------------------------------------------------- | ||
| 7 | |||
| 8 | local deltas = { | ||
| 9 | dev = 120000000, | ||
| 10 | scm = 110000000, | ||
| 11 | cvs = 100000000, | ||
| 12 | rc = -1000, | ||
| 13 | pre = -10000, | ||
| 14 | beta = -100000, | ||
| 15 | alpha = -1000000 | ||
| 16 | } | ||
| 17 | |||
| 18 | local version_mt = { | ||
| 19 | --- Equality comparison for versions. | ||
| 20 | -- All version numbers must be equal. | ||
| 21 | -- If both versions have revision numbers, they must be equal; | ||
| 22 | -- otherwise the revision number is ignored. | ||
| 23 | -- @param v1 table: version table to compare. | ||
| 24 | -- @param v2 table: version table to compare. | ||
| 25 | -- @return boolean: true if they are considered equivalent. | ||
| 26 | __eq = function(v1, v2) | ||
| 27 | if #v1 ~= #v2 then | ||
| 28 | return false | ||
| 29 | end | ||
| 30 | for i = 1, #v1 do | ||
| 31 | if v1[i] ~= v2[i] then | ||
| 32 | return false | ||
| 33 | end | ||
| 34 | end | ||
| 35 | if v1.revision and v2.revision then | ||
| 36 | return (v1.revision == v2.revision) | ||
| 37 | end | ||
| 38 | return true | ||
| 39 | end, | ||
| 40 | --- Size comparison for versions. | ||
| 41 | -- All version numbers are compared. | ||
| 42 | -- If both versions have revision numbers, they are compared; | ||
| 43 | -- otherwise the revision number is ignored. | ||
| 44 | -- @param v1 table: version table to compare. | ||
| 45 | -- @param v2 table: version table to compare. | ||
| 46 | -- @return boolean: true if v1 is considered lower than v2. | ||
| 47 | __lt = function(v1, v2) | ||
| 48 | for i = 1, math.max(#v1, #v2) do | ||
| 49 | local v1i, v2i = v1[i] or 0, v2[i] or 0 | ||
| 50 | if v1i ~= v2i then | ||
| 51 | return (v1i < v2i) | ||
| 52 | end | ||
| 53 | end | ||
| 54 | if v1.revision and v2.revision then | ||
| 55 | return (v1.revision < v2.revision) | ||
| 56 | end | ||
| 57 | return false | ||
| 58 | end, | ||
| 59 | -- @param v1 table: version table to compare. | ||
| 60 | -- @param v2 table: version table to compare. | ||
| 61 | -- @return boolean: true if v1 is considered lower than or equal to v2. | ||
| 62 | __le = function(v1, v2) | ||
| 63 | return not (v2 < v1) -- luacheck: ignore | ||
| 64 | end, | ||
| 65 | --- Return version as a string. | ||
| 66 | -- @param v The version table. | ||
| 67 | -- @return The string representation. | ||
| 68 | __tostring = function(v) | ||
| 69 | return v.string | ||
| 70 | end, | ||
| 71 | } | ||
| 72 | |||
| 73 | local version_cache = {} | ||
| 74 | setmetatable(version_cache, { | ||
| 75 | __mode = "kv" | ||
| 76 | }) | ||
| 77 | |||
| 78 | --- Parse a version string, converting to table format. | ||
| 79 | -- A version table contains all components of the version string | ||
| 80 | -- converted to numeric format, stored in the array part of the table. | ||
| 81 | -- If the version contains a revision, it is stored numerically | ||
| 82 | -- in the 'revision' field. The original string representation of | ||
| 83 | -- the string is preserved in the 'string' field. | ||
| 84 | -- Returned version tables use a metatable | ||
| 85 | -- allowing later comparison through relational operators. | ||
| 86 | -- @param vstring string: A version number in string format. | ||
| 87 | -- @return table or nil: A version table or nil | ||
| 88 | -- if the input string contains invalid characters. | ||
| 89 | function vers.parse_version(vstring) | ||
| 90 | if not vstring then return nil end | ||
| 91 | assert(type(vstring) == "string") | ||
| 92 | |||
| 93 | local cached = version_cache[vstring] | ||
| 94 | if cached then | ||
| 95 | return cached | ||
| 96 | end | ||
| 97 | |||
| 98 | local version = {} | ||
| 99 | local i = 1 | ||
| 100 | |||
| 101 | local function add_token(number) | ||
| 102 | version[i] = version[i] and version[i] + number/100000 or number | ||
| 103 | i = i + 1 | ||
| 104 | end | ||
| 105 | |||
| 106 | -- trim leading and trailing spaces | ||
| 107 | local v = vstring:match("^%s*(.*)%s*$") | ||
| 108 | version.string = v | ||
| 109 | -- store revision separately if any | ||
| 110 | local main, revision = v:match("(.*)%-(%d+)$") | ||
| 111 | if revision then | ||
| 112 | v = main | ||
| 113 | version.revision = tonumber(revision) | ||
| 114 | end | ||
| 115 | while #v > 0 do | ||
| 116 | -- extract a number | ||
| 117 | local token, rest = v:match("^(%d+)[%.%-%_]*(.*)") | ||
| 118 | if token then | ||
| 119 | add_token(tonumber(token)) | ||
| 120 | else | ||
| 121 | -- extract a word | ||
| 122 | token, rest = v:match("^(%a+)[%.%-%_]*(.*)") | ||
| 123 | if not token then | ||
| 124 | util.warning("version number '"..v.."' could not be parsed.") | ||
| 125 | version[i] = 0 | ||
| 126 | break | ||
| 127 | end | ||
| 128 | version[i] = deltas[token] or (token:byte() / 1000) | ||
| 129 | end | ||
| 130 | v = rest | ||
| 131 | end | ||
| 132 | setmetatable(version, version_mt) | ||
| 133 | version_cache[vstring] = version | ||
| 134 | return version | ||
| 135 | end | ||
| 136 | |||
| 137 | --- Utility function to compare version numbers given as strings. | ||
| 138 | -- @param a string: one version. | ||
| 139 | -- @param b string: another version. | ||
| 140 | -- @return boolean: True if a > b. | ||
| 141 | function vers.compare_versions(a, b) | ||
| 142 | if a == b then | ||
| 143 | return false | ||
| 144 | end | ||
| 145 | return vers.parse_version(a) > vers.parse_version(b) | ||
| 146 | end | ||
| 147 | |||
| 148 | --- A more lenient check for equivalence between versions. | ||
| 149 | -- This returns true if the requested components of a version | ||
| 150 | -- match and ignore the ones that were not given. For example, | ||
| 151 | -- when requesting "2", then "2", "2.1", "2.3.5-9"... all match. | ||
| 152 | -- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2" | ||
| 153 | -- doesn't. | ||
| 154 | -- @param version string or table: Version to be tested; may be | ||
| 155 | -- in string format or already parsed into a table. | ||
| 156 | -- @param requested string or table: Version requested; may be | ||
| 157 | -- in string format or already parsed into a table. | ||
| 158 | -- @return boolean: True if the tested version matches the requested | ||
| 159 | -- version, false otherwise. | ||
| 160 | local function partial_match(version, requested) | ||
| 161 | assert(type(version) == "string" or type(version) == "table") | ||
| 162 | assert(type(requested) == "string" or type(version) == "table") | ||
| 163 | |||
| 164 | if type(version) ~= "table" then version = vers.parse_version(version) end | ||
| 165 | if type(requested) ~= "table" then requested = vers.parse_version(requested) end | ||
| 166 | if not version or not requested then return false end | ||
| 167 | |||
| 168 | for i, ri in ipairs(requested) do | ||
| 169 | local vi = version[i] or 0 | ||
| 170 | if ri ~= vi then return false end | ||
| 171 | end | ||
| 172 | if requested.revision then | ||
| 173 | return requested.revision == version.revision | ||
| 174 | end | ||
| 175 | return true | ||
| 176 | end | ||
| 177 | |||
| 178 | --- Check if a version satisfies a set of constraints. | ||
| 179 | -- @param version table: A version in table format | ||
| 180 | -- @param constraints table: An array of constraints in table format. | ||
| 181 | -- @return boolean: True if version satisfies all constraints, | ||
| 182 | -- false otherwise. | ||
| 183 | function vers.match_constraints(version, constraints) | ||
| 184 | assert(type(version) == "table") | ||
| 185 | assert(type(constraints) == "table") | ||
| 186 | local ok = true | ||
| 187 | setmetatable(version, version_mt) | ||
| 188 | for _, constr in pairs(constraints) do | ||
| 189 | if type(constr.version) == "string" then | ||
| 190 | constr.version = vers.parse_version(constr.version) | ||
| 191 | end | ||
| 192 | local constr_version, constr_op = constr.version, constr.op | ||
| 193 | setmetatable(constr_version, version_mt) | ||
| 194 | if constr_op == "==" then ok = version == constr_version | ||
| 195 | elseif constr_op == "~=" then ok = version ~= constr_version | ||
| 196 | elseif constr_op == ">" then ok = version > constr_version | ||
| 197 | elseif constr_op == "<" then ok = version < constr_version | ||
| 198 | elseif constr_op == ">=" then ok = version >= constr_version | ||
| 199 | elseif constr_op == "<=" then ok = version <= constr_version | ||
| 200 | elseif constr_op == "~>" then ok = partial_match(version, constr_version) | ||
| 201 | end | ||
| 202 | if not ok then break end | ||
| 203 | end | ||
| 204 | return ok | ||
| 205 | end | ||
| 206 | |||
| 207 | return vers | ||
diff --git a/src/luarocks/core/vers.lua b/src/luarocks/core/vers.lua index 8e617984..b230c68e 100644 --- a/src/luarocks/core/vers.lua +++ b/src/luarocks/core/vers.lua | |||
| @@ -1,28 +1,34 @@ | |||
| 1 | 1 | local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local assert = _tl_compat and _tl_compat.assert or assert; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local math = _tl_compat and _tl_compat.math or math; local pairs = _tl_compat and _tl_compat.pairs or pairs; local string = _tl_compat and _tl_compat.string or string | |
| 2 | local vers = {} | 2 | local vers = {} |
| 3 | 3 | ||
| 4 | local util = require("luarocks.core.util") | 4 | local util = require("luarocks.core.util") |
| 5 | local require = nil | 5 | local require = nil |
| 6 | -------------------------------------------------------------------------------- | 6 | |
| 7 | 7 | ||
| 8 | local deltas = { | 8 | local deltas = { |
| 9 | dev = 120000000, | 9 | dev = 120000000, |
| 10 | scm = 110000000, | 10 | scm = 110000000, |
| 11 | cvs = 100000000, | 11 | cvs = 100000000, |
| 12 | rc = -1000, | 12 | rc = -1000, |
| 13 | pre = -10000, | 13 | pre = -10000, |
| 14 | beta = -100000, | 14 | beta = -100000, |
| 15 | alpha = -1000000 | 15 | alpha = -1000000, |
| 16 | } | 16 | } |
| 17 | 17 | ||
| 18 | |||
| 19 | |||
| 20 | |||
| 21 | |||
| 22 | |||
| 23 | |||
| 18 | local version_mt = { | 24 | local version_mt = { |
| 19 | --- Equality comparison for versions. | 25 | |
| 20 | -- All version numbers must be equal. | 26 | |
| 21 | -- If both versions have revision numbers, they must be equal; | 27 | |
| 22 | -- otherwise the revision number is ignored. | 28 | |
| 23 | -- @param v1 table: version table to compare. | 29 | |
| 24 | -- @param v2 table: version table to compare. | 30 | |
| 25 | -- @return boolean: true if they are considered equivalent. | 31 | |
| 26 | __eq = function(v1, v2) | 32 | __eq = function(v1, v2) |
| 27 | if #v1 ~= #v2 then | 33 | if #v1 ~= #v2 then |
| 28 | return false | 34 | return false |
| @@ -37,13 +43,13 @@ local version_mt = { | |||
| 37 | end | 43 | end |
| 38 | return true | 44 | return true |
| 39 | end, | 45 | end, |
| 40 | --- Size comparison for versions. | 46 | |
| 41 | -- All version numbers are compared. | 47 | |
| 42 | -- If both versions have revision numbers, they are compared; | 48 | |
| 43 | -- otherwise the revision number is ignored. | 49 | |
| 44 | -- @param v1 table: version table to compare. | 50 | |
| 45 | -- @param v2 table: version table to compare. | 51 | |
| 46 | -- @return boolean: true if v1 is considered lower than v2. | 52 | |
| 47 | __lt = function(v1, v2) | 53 | __lt = function(v1, v2) |
| 48 | for i = 1, math.max(#v1, #v2) do | 54 | for i = 1, math.max(#v1, #v2) do |
| 49 | local v1i, v2i = v1[i] or 0, v2[i] or 0 | 55 | local v1i, v2i = v1[i] or 0, v2[i] or 0 |
| @@ -56,15 +62,15 @@ local version_mt = { | |||
| 56 | end | 62 | end |
| 57 | return false | 63 | return false |
| 58 | end, | 64 | end, |
| 59 | -- @param v1 table: version table to compare. | 65 | |
| 60 | -- @param v2 table: version table to compare. | 66 | |
| 61 | -- @return boolean: true if v1 is considered lower than or equal to v2. | 67 | |
| 62 | __le = function(v1, v2) | 68 | __le = function(v1, v2) |
| 63 | return not (v2 < v1) -- luacheck: ignore | 69 | return not (v2 < v1) |
| 64 | end, | 70 | end, |
| 65 | --- Return version as a string. | 71 | |
| 66 | -- @param v The version table. | 72 | |
| 67 | -- @return The string representation. | 73 | |
| 68 | __tostring = function(v) | 74 | __tostring = function(v) |
| 69 | return v.string | 75 | return v.string |
| 70 | end, | 76 | end, |
| @@ -72,20 +78,20 @@ local version_mt = { | |||
| 72 | 78 | ||
| 73 | local version_cache = {} | 79 | local version_cache = {} |
| 74 | setmetatable(version_cache, { | 80 | setmetatable(version_cache, { |
| 75 | __mode = "kv" | 81 | __mode = "kv", |
| 76 | }) | 82 | }) |
| 77 | 83 | ||
| 78 | --- Parse a version string, converting to table format. | 84 | |
| 79 | -- A version table contains all components of the version string | 85 | |
| 80 | -- converted to numeric format, stored in the array part of the table. | 86 | |
| 81 | -- If the version contains a revision, it is stored numerically | 87 | |
| 82 | -- in the 'revision' field. The original string representation of | 88 | |
| 83 | -- the string is preserved in the 'string' field. | 89 | |
| 84 | -- Returned version tables use a metatable | 90 | |
| 85 | -- allowing later comparison through relational operators. | 91 | |
| 86 | -- @param vstring string: A version number in string format. | 92 | |
| 87 | -- @return table or nil: A version table or nil | 93 | |
| 88 | -- if the input string contains invalid characters. | 94 | |
| 89 | function vers.parse_version(vstring) | 95 | function vers.parse_version(vstring) |
| 90 | if not vstring then return nil end | 96 | if not vstring then return nil end |
| 91 | assert(type(vstring) == "string") | 97 | assert(type(vstring) == "string") |
| @@ -99,29 +105,29 @@ function vers.parse_version(vstring) | |||
| 99 | local i = 1 | 105 | local i = 1 |
| 100 | 106 | ||
| 101 | local function add_token(number) | 107 | local function add_token(number) |
| 102 | version[i] = version[i] and version[i] + number/100000 or number | 108 | version[i] = version[i] and version[i] + number / 100000 or number |
| 103 | i = i + 1 | 109 | i = i + 1 |
| 104 | end | 110 | end |
| 105 | 111 | ||
| 106 | -- trim leading and trailing spaces | 112 | |
| 107 | local v = vstring:match("^%s*(.*)%s*$") | 113 | local v = vstring:match("^%s*(.*)%s*$") |
| 108 | version.string = v | 114 | version.string = v |
| 109 | -- store revision separately if any | 115 | |
| 110 | local main, revision = v:match("(.*)%-(%d+)$") | 116 | local main, revision = v:match("(.*)%-(%d+)$") |
| 111 | if revision then | 117 | if revision then |
| 112 | v = main | 118 | v = main |
| 113 | version.revision = tonumber(revision) | 119 | version.revision = tonumber(revision) |
| 114 | end | 120 | end |
| 115 | while #v > 0 do | 121 | while #v > 0 do |
| 116 | -- extract a number | 122 | |
| 117 | local token, rest = v:match("^(%d+)[%.%-%_]*(.*)") | 123 | local token, rest = v:match("^(%d+)[%.%-%_]*(.*)") |
| 118 | if token then | 124 | if token then |
| 119 | add_token(tonumber(token)) | 125 | add_token(tonumber(token)) |
| 120 | else | 126 | else |
| 121 | -- extract a word | 127 | |
| 122 | token, rest = v:match("^(%a+)[%.%-%_]*(.*)") | 128 | token, rest = v:match("^(%a+)[%.%-%_]*(.*)") |
| 123 | if not token then | 129 | if not token then |
| 124 | util.warning("version number '"..v.."' could not be parsed.") | 130 | util.warning("version number '" .. v .. "' could not be parsed.") |
| 125 | version[i] = 0 | 131 | version[i] = 0 |
| 126 | break | 132 | break |
| 127 | end | 133 | end |
| @@ -134,10 +140,10 @@ function vers.parse_version(vstring) | |||
| 134 | return version | 140 | return version |
| 135 | end | 141 | end |
| 136 | 142 | ||
| 137 | --- Utility function to compare version numbers given as strings. | 143 | |
| 138 | -- @param a string: one version. | 144 | |
| 139 | -- @param b string: another version. | 145 | |
| 140 | -- @return boolean: True if a > b. | 146 | |
| 141 | function vers.compare_versions(a, b) | 147 | function vers.compare_versions(a, b) |
| 142 | if a == b then | 148 | if a == b then |
| 143 | return false | 149 | return false |
| @@ -145,24 +151,24 @@ function vers.compare_versions(a, b) | |||
| 145 | return vers.parse_version(a) > vers.parse_version(b) | 151 | return vers.parse_version(a) > vers.parse_version(b) |
| 146 | end | 152 | end |
| 147 | 153 | ||
| 148 | --- A more lenient check for equivalence between versions. | 154 | |
| 149 | -- This returns true if the requested components of a version | 155 | |
| 150 | -- match and ignore the ones that were not given. For example, | 156 | |
| 151 | -- when requesting "2", then "2", "2.1", "2.3.5-9"... all match. | 157 | |
| 152 | -- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2" | 158 | |
| 153 | -- doesn't. | 159 | |
| 154 | -- @param version string or table: Version to be tested; may be | 160 | |
| 155 | -- in string format or already parsed into a table. | 161 | |
| 156 | -- @param requested string or table: Version requested; may be | 162 | |
| 157 | -- in string format or already parsed into a table. | 163 | |
| 158 | -- @return boolean: True if the tested version matches the requested | 164 | |
| 159 | -- version, false otherwise. | 165 | |
| 160 | local function partial_match(version, requested) | 166 | local function partial_match(version_for_parse, requested_for_parce) |
| 161 | assert(type(version) == "string" or type(version) == "table") | 167 | assert(type(version_for_parse) == "string" or type(version_for_parse) == "table") |
| 162 | assert(type(requested) == "string" or type(version) == "table") | 168 | assert(type(requested_for_parce) == "string" or type(requested_for_parce) == "table") |
| 163 | 169 | ||
| 164 | if type(version) ~= "table" then version = vers.parse_version(version) end | 170 | if type(version_for_parse) ~= "table" then local version = vers.parse_version(version_for_parse) end |
| 165 | if type(requested) ~= "table" then requested = vers.parse_version(requested) end | 171 | if type(requested_for_parce) ~= "table" then local requested = vers.parse_version(requested_for_parce) end |
| 166 | if not version or not requested then return false end | 172 | if not version or not requested then return false end |
| 167 | 173 | ||
| 168 | for i, ri in ipairs(requested) do | 174 | for i, ri in ipairs(requested) do |
| @@ -175,11 +181,11 @@ local function partial_match(version, requested) | |||
| 175 | return true | 181 | return true |
| 176 | end | 182 | end |
| 177 | 183 | ||
| 178 | --- Check if a version satisfies a set of constraints. | 184 | |
| 179 | -- @param version table: A version in table format | 185 | |
| 180 | -- @param constraints table: An array of constraints in table format. | 186 | |
| 181 | -- @return boolean: True if version satisfies all constraints, | 187 | |
| 182 | -- false otherwise. | 188 | |
| 183 | function vers.match_constraints(version, constraints) | 189 | function vers.match_constraints(version, constraints) |
| 184 | assert(type(version) == "table") | 190 | assert(type(version) == "table") |
| 185 | assert(type(constraints) == "table") | 191 | assert(type(constraints) == "table") |
| @@ -191,10 +197,10 @@ function vers.match_constraints(version, constraints) | |||
| 191 | end | 197 | end |
| 192 | local constr_version, constr_op = constr.version, constr.op | 198 | local constr_version, constr_op = constr.version, constr.op |
| 193 | setmetatable(constr_version, version_mt) | 199 | setmetatable(constr_version, version_mt) |
| 194 | if constr_op == "==" then ok = version == constr_version | 200 | if constr_op == "==" then ok = version == constr_version |
| 195 | elseif constr_op == "~=" then ok = version ~= constr_version | 201 | elseif constr_op == "~=" then ok = version ~= constr_version |
| 196 | elseif constr_op == ">" then ok = version > constr_version | 202 | elseif constr_op == ">" then ok = version > constr_version |
| 197 | elseif constr_op == "<" then ok = version < constr_version | 203 | elseif constr_op == "<" then ok = version < constr_version |
| 198 | elseif constr_op == ">=" then ok = version >= constr_version | 204 | elseif constr_op == ">=" then ok = version >= constr_version |
| 199 | elseif constr_op == "<=" then ok = version <= constr_version | 205 | elseif constr_op == "<=" then ok = version <= constr_version |
| 200 | elseif constr_op == "~>" then ok = partial_match(version, constr_version) | 206 | elseif constr_op == "~>" then ok = partial_match(version, constr_version) |
diff --git a/src/luarocks/core/vers.tl b/src/luarocks/core/vers.tl new file mode 100644 index 00000000..fd1e5acb --- /dev/null +++ b/src/luarocks/core/vers.tl | |||
| @@ -0,0 +1,213 @@ | |||
| 1 | |||
| 2 | local vers = {} | ||
| 3 | |||
| 4 | local util <const> = require("luarocks.core.util") --! | ||
| 5 | local require = nil --! | ||
| 6 | -------------------------------------------------------------------------------- | ||
| 7 | |||
| 8 | local deltas = { | ||
| 9 | dev = 120000000, | ||
| 10 | scm = 110000000, | ||
| 11 | cvs = 100000000, | ||
| 12 | rc = -1000, | ||
| 13 | pre = -10000, | ||
| 14 | beta = -100000, | ||
| 15 | alpha = -1000000 | ||
| 16 | } | ||
| 17 | |||
| 18 | local record Version --? | ||
| 19 | string: string | ||
| 20 | revision: number | ||
| 21 | number: {number} | ||
| 22 | end | ||
| 23 | |||
| 24 | local version_mt = { | ||
| 25 | --- Equality comparison for versions. | ||
| 26 | -- All version numbers must be equal. | ||
| 27 | -- If both versions have revision numbers, they must be equal; | ||
| 28 | -- otherwise the revision number is ignored. | ||
| 29 | -- @param v1 table: version table to compare. | ||
| 30 | -- @param v2 table: version table to compare. | ||
| 31 | -- @return boolean: true if they are considered equivalent. | ||
| 32 | __eq = function(v1: Version, v2: Version): boolean | ||
| 33 | if #v1 ~= #v2 then | ||
| 34 | return false | ||
| 35 | end | ||
| 36 | for i = 1, #v1 do | ||
| 37 | if v1[i] ~= v2[i] then | ||
| 38 | return false | ||
| 39 | end | ||
| 40 | end | ||
| 41 | if v1.revision and v2.revision then | ||
| 42 | return (v1.revision == v2.revision) | ||
| 43 | end | ||
| 44 | return true | ||
| 45 | end, | ||
| 46 | --- Size comparison for versions. | ||
| 47 | -- All version numbers are compared. | ||
| 48 | -- If both versions have revision numbers, they are compared; | ||
| 49 | -- otherwise the revision number is ignored. | ||
| 50 | -- @param v1 table: version table to compare. | ||
| 51 | -- @param v2 table: version table to compare. | ||
| 52 | -- @return boolean: true if v1 is considered lower than v2. | ||
| 53 | __lt = function(v1: Version, v2: Version): boolean | ||
| 54 | for i = 1, math.max(#v1, #v2) do | ||
| 55 | local v1i, v2i = v1[i] or 0, v2[i] or 0 | ||
| 56 | if v1i ~= v2i then | ||
| 57 | return (v1i < v2i) | ||
| 58 | end | ||
| 59 | end | ||
| 60 | if v1.revision and v2.revision then | ||
| 61 | return (v1.revision < v2.revision) | ||
| 62 | end | ||
| 63 | return false | ||
| 64 | end, | ||
| 65 | -- @param v1 table: version table to compare. | ||
| 66 | -- @param v2 table: version table to compare. | ||
| 67 | -- @return boolean: true if v1 is considered lower than or equal to v2. | ||
| 68 | __le = function(v1: Version, v2: Version): boolean | ||
| 69 | return not (v2 < v1) -- luacheck: ignore | ||
| 70 | end, | ||
| 71 | --- Return version as a string. | ||
| 72 | -- @param v The version table. | ||
| 73 | -- @return The string representation. | ||
| 74 | __tostring = function(v: Version): string | ||
| 75 | return v.string | ||
| 76 | end, | ||
| 77 | } | ||
| 78 | |||
| 79 | local version_cache: {string: Version} = {} | ||
| 80 | setmetatable(version_cache, { | ||
| 81 | __mode = "kv" | ||
| 82 | }) | ||
| 83 | |||
| 84 | --- Parse a version string, converting to table format. | ||
| 85 | -- A version table contains all components of the version string | ||
| 86 | -- converted to numeric format, stored in the array part of the table. | ||
| 87 | -- If the version contains a revision, it is stored numerically | ||
| 88 | -- in the 'revision' field. The original string representation of | ||
| 89 | -- the string is preserved in the 'string' field. | ||
| 90 | -- Returned version tables use a metatable | ||
| 91 | -- allowing later comparison through relational operators. | ||
| 92 | -- @param vstring string: A version number in string format. | ||
| 93 | -- @return table or nil: A version table or nil | ||
| 94 | -- if the input string contains invalid characters. | ||
| 95 | function vers.parse_version(vstring: string): Version | nil | ||
| 96 | if not vstring then return nil end --? | ||
| 97 | assert(type(vstring) == "string") --? | ||
| 98 | |||
| 99 | local cached = version_cache[vstring] | ||
| 100 | if cached then | ||
| 101 | return cached | ||
| 102 | end | ||
| 103 | |||
| 104 | local version: Version = {} | ||
| 105 | local i = 1 | ||
| 106 | |||
| 107 | local function add_token(number: number) | ||
| 108 | version[i] = version[i] and version[i] + number/100000 or number --? | ||
| 109 | i = i + 1 | ||
| 110 | end | ||
| 111 | |||
| 112 | -- trim leading and trailing spaces | ||
| 113 | local v = vstring:match("^%s*(.*)%s*$") | ||
| 114 | version.string = v | ||
| 115 | -- store revision separately if any | ||
| 116 | local main, revision = v:match("(.*)%-(%d+)$") | ||
| 117 | if revision then | ||
| 118 | v = main | ||
| 119 | version.revision = tonumber(revision) | ||
| 120 | end | ||
| 121 | while #v > 0 do | ||
| 122 | -- extract a number | ||
| 123 | local token, rest = v:match("^(%d+)[%.%-%_]*(.*)") | ||
| 124 | if token then | ||
| 125 | add_token(tonumber(token)) | ||
| 126 | else | ||
| 127 | -- extract a word | ||
| 128 | token, rest = v:match("^(%a+)[%.%-%_]*(.*)") | ||
| 129 | if not token then | ||
| 130 | util.warning("version number '"..v.."' could not be parsed.") | ||
| 131 | version[i] = 0 | ||
| 132 | break | ||
| 133 | end | ||
| 134 | version[i] = deltas[token] or (token:byte() / 1000) | ||
| 135 | end | ||
| 136 | v = rest | ||
| 137 | end | ||
| 138 | setmetatable(version, version_mt) | ||
| 139 | version_cache[vstring] = version | ||
| 140 | return version | ||
| 141 | end | ||
| 142 | |||
| 143 | --- Utility function to compare version numbers given as strings. | ||
| 144 | -- @param a string: one version. | ||
| 145 | -- @param b string: another version. | ||
| 146 | -- @return boolean: True if a > b. | ||
| 147 | function vers.compare_versions(a: string, b: string): boolean | ||
| 148 | if a == b then | ||
| 149 | return false | ||
| 150 | end | ||
| 151 | return vers.parse_version(a) > vers.parse_version(b) | ||
| 152 | end | ||
| 153 | |||
| 154 | --- A more lenient check for equivalence between versions. | ||
| 155 | -- This returns true if the requested components of a version | ||
| 156 | -- match and ignore the ones that were not given. For example, | ||
| 157 | -- when requesting "2", then "2", "2.1", "2.3.5-9"... all match. | ||
| 158 | -- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2" | ||
| 159 | -- doesn't. | ||
| 160 | -- @param version string or table: Version to be tested; may be | ||
| 161 | -- in string format or already parsed into a table. | ||
| 162 | -- @param requested string or table: Version requested; may be | ||
| 163 | -- in string format or already parsed into a table. | ||
| 164 | -- @return boolean: True if the tested version matches the requested | ||
| 165 | -- version, false otherwise. | ||
| 166 | local function partial_match(version_for_parse: string | table, requested_for_parce: string | table): boolean | ||
| 167 | assert(type(version_for_parse) == "string" or type(version_for_parse) == "table") | ||
| 168 | assert(type(requested_for_parce) == "string" or type(requested_for_parce) == "table") --! the lines were: assert(type(version) == "string" or type(version) == "table"); assert(type(requested) == "string" or type(version) == "table") | ||
| 169 | |||
| 170 | if type(version_for_parse) ~= "table" then local version = vers.parse_version(version_for_parse) end --? if type(version) ~= "table" then version = vers.parse_version(version) end | ||
| 171 | if type(requested_for_parce) ~= "table" then local requested = vers.parse_version(requested_for_parce) end --? if type(requested) ~= "table" then requested = vers.parse_version(requested) end | ||
| 172 | if not version or not requested then return false end | ||
| 173 | |||
| 174 | for i, ri in ipairs(requested) do --! I am lost here | ||
| 175 | local vi = version[i] or 0 | ||
| 176 | if ri ~= vi then return false end | ||
| 177 | end | ||
| 178 | if requested.revision then | ||
| 179 | return requested.revision == version.revision | ||
| 180 | end | ||
| 181 | return true | ||
| 182 | end | ||
| 183 | |||
| 184 | --- Check if a version satisfies a set of constraints. | ||
| 185 | -- @param version table: A version in table format | ||
| 186 | -- @param constraints table: An array of constraints in table format. | ||
| 187 | -- @return boolean: True if version satisfies all constraints, | ||
| 188 | -- false otherwise. | ||
| 189 | function vers.match_constraints(version: Version, constraints: Version): boolean | ||
| 190 | assert(type(version) == "table") | ||
| 191 | assert(type(constraints) == "table") | ||
| 192 | local ok = true | ||
| 193 | setmetatable(version, version_mt) | ||
| 194 | for _, constr in pairs(constraints) do | ||
| 195 | if type(constr.version) == "string" then | ||
| 196 | constr.version = vers.parse_version(constr.version) | ||
| 197 | end | ||
| 198 | local constr_version, constr_op = constr.version, constr.op --! op?? | ||
| 199 | setmetatable(constr_version, version_mt) | ||
| 200 | if constr_op == "==" then ok = version == constr_version | ||
| 201 | elseif constr_op == "~=" then ok = version ~= constr_version | ||
| 202 | elseif constr_op == ">" then ok = version > constr_version | ||
| 203 | elseif constr_op == "<" then ok = version < constr_version | ||
| 204 | elseif constr_op == ">=" then ok = version >= constr_version | ||
| 205 | elseif constr_op == "<=" then ok = version <= constr_version | ||
| 206 | elseif constr_op == "~>" then ok = partial_match(version, constr_version) | ||
| 207 | end | ||
| 208 | if not ok then break end | ||
| 209 | end | ||
| 210 | return ok | ||
| 211 | end | ||
| 212 | |||
| 213 | return vers | ||
