From 2377e668c424f77f90a577f42608eccfdfaca07b Mon Sep 17 00:00:00 2001 From: V1K1NGbg Date: Sun, 9 Jun 2024 01:37:30 +0200 Subject: Half of core halfway there --- src/luarocks/core/persist-original.lua | 81 +++++ src/luarocks/core/persist.lua | 59 ++-- src/luarocks/core/persist.tl | 81 +++++ src/luarocks/core/sysdetect-original.lua | 419 +++++++++++++++++++++++++ src/luarocks/core/sysdetect.lua | 214 +++++++++---- src/luarocks/core/sysdetect.tl | 509 +++++++++++++++++++++++++++++++ src/luarocks/core/util-original.lua | 322 +++++++++++++++++++ src/luarocks/core/util.lua | 161 +++++----- src/luarocks/core/util.tl | 322 +++++++++++++++++++ src/luarocks/core/vers-original.lua | 207 +++++++++++++ src/luarocks/core/vers.lua | 162 +++++----- src/luarocks/core/vers.tl | 213 +++++++++++++ 12 files changed, 2498 insertions(+), 252 deletions(-) create mode 100644 src/luarocks/core/persist-original.lua create mode 100644 src/luarocks/core/persist.tl create mode 100644 src/luarocks/core/sysdetect-original.lua create mode 100644 src/luarocks/core/sysdetect.tl create mode 100644 src/luarocks/core/util-original.lua create mode 100644 src/luarocks/core/util.tl create mode 100644 src/luarocks/core/vers-original.lua create mode 100644 src/luarocks/core/vers.tl (limited to 'src') 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 @@ + +local persist = {} + +local require = nil +-------------------------------------------------------------------------------- + +--- Load and run a Lua file in an environment. +-- @param filename string: the name of the file. +-- @param env table: the environment table. +-- @return (true, any) or (nil, string, string): true and the return value +-- of the file, or nil, an error message and an error code ("open", "load" +-- or "run") in case of errors. +function persist.run_file(filename, env) + local fd, err = io.open(filename) + if not fd then + return nil, err, "open" + end + local str, err = fd:read("*a") + fd:close() + if not str then + return nil, err, "open" + end + str = str:gsub("^#![^\n]*\n", "") + local chunk, ran + if _VERSION == "Lua 5.1" then -- Lua 5.1 + chunk, err = loadstring(str, filename) + if chunk then + setfenv(chunk, env) + ran, err = pcall(chunk) + end + else -- Lua 5.2 + chunk, err = load(str, filename, "t", env) + if chunk then + ran, err = pcall(chunk) + end + end + if not chunk then + return nil, "Error loading file: "..err, "load" + end + if not ran then + return nil, "Error running file: "..err, "run" + end + return true, err +end + +--- Load a Lua file containing assignments, storing them in a table. +-- The global environment is not propagated to the loaded file. +-- @param filename string: the name of the file. +-- @param tbl table or nil: if given, this table is used to store +-- loaded values. +-- @return (table, table) or (nil, string, string): a table with the file's assignments +-- as fields and set of undefined globals accessed in file, +-- or nil, an error message and an error code ("open"; couldn't open the file, +-- "load"; compile-time error, or "run"; run-time error) +-- in case of errors. +function persist.load_into_table(filename, tbl) + assert(type(filename) == "string") + assert(type(tbl) == "table" or not tbl) + + local result = tbl or {} + local globals = {} + local globals_mt = { + __index = function(t, k) + globals[k] = true + end + } + local save_mt = getmetatable(result) + setmetatable(result, globals_mt) + + local ok, err, errcode = persist.run_file(filename, result) + + setmetatable(result, save_mt) + + if not ok then + return nil, err, errcode + end + return result, globals +end + +return persist + 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 @@ - +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 local persist = {} -local require = nil --------------------------------------------------------------------------------- ---- Load and run a Lua file in an environment. --- @param filename string: the name of the file. --- @param env table: the environment table. --- @return (true, any) or (nil, string, string): true and the return value --- of the file, or nil, an error message and an error code ("open", "load" --- or "run") in case of errors. + + + + + + function persist.run_file(filename, env) - local fd, err = io.open(filename) + local fd, open_err = io.open(filename) if not fd then - return nil, err, "open" + return nil, open_err, "open" end - local str, err = fd:read("*a") + local str, read_err = fd:read("*a") fd:close() if not str then - return nil, err, "open" + return nil, read_err, "open" end str = str:gsub("^#![^\n]*\n", "") - local chunk, ran - if _VERSION == "Lua 5.1" then -- Lua 5.1 + local chunk, ran, err + if _VERSION == "Lua 5.1" then chunk, err = loadstring(str, filename) if chunk then setfenv(chunk, env) ran, err = pcall(chunk) end - else -- Lua 5.2 + else chunk, err = load(str, filename, "t", env) if chunk then ran, err = pcall(chunk) end end if not chunk then - return nil, "Error loading file: "..err, "load" + return nil, "Error loading file: " .. tostring(err), "load" end if not ran then - return nil, "Error running file: "..err, "run" + return nil, "Error running file: " .. tostring(err), "run" end return true, err end ---- Load a Lua file containing assignments, storing them in a table. --- The global environment is not propagated to the loaded file. --- @param filename string: the name of the file. --- @param tbl table or nil: if given, this table is used to store --- loaded values. --- @return (table, table) or (nil, string, string): a table with the file's assignments --- as fields and set of undefined globals accessed in file, --- or nil, an error message and an error code ("open"; couldn't open the file, --- "load"; compile-time error, or "run"; run-time error) --- in case of errors. + + + + + + + + + + function persist.load_into_table(filename, tbl) assert(type(filename) == "string") assert(type(tbl) == "table" or not tbl) @@ -60,9 +58,9 @@ function persist.load_into_table(filename, tbl) local result = tbl or {} local globals = {} local globals_mt = { - __index = function(t, k) + __index = function(_, k) globals[k] = true - end + end, } local save_mt = getmetatable(result) setmetatable(result, globals_mt) @@ -78,4 +76,3 @@ function persist.load_into_table(filename, tbl) end return persist - 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 @@ + +local persist = {} + +local require = nil --! +-------------------------------------------------------------------------------- + +--- Load and run a Lua file in an environment. +-- @param filename string: the name of the file. +-- @param env table: the environment table. +-- @return (true, any) or (nil, string, string): true and the return value +-- of the file, or nil, an error message and an error code ("open", "load" +-- or "run") in case of errors. +function persist.run_file(filename: string, env: {string:any}): boolean, any | nil, string, string + local fd, open_err: FILE, any = io.open(filename) + if not fd then + return nil, open_err, "open" + end + local str, read_err: string, string = fd:read("*a") + fd:close() + if not str then + return nil, read_err, "open" + end + str = str:gsub("^#![^\n]*\n", "") + local chunk, ran, err: function(any):(any), boolean, any + if _VERSION == "Lua 5.1" then -- Lua 5.1 + chunk, err = loadstring(str, filename) --! + if chunk then + setfenv(chunk, env) --! + ran, err = pcall(chunk) + end + else -- Lua 5.2 + chunk, err = load(str, filename, "t", env) + if chunk then + ran, err = pcall(chunk) + end + end + if not chunk then + return nil, "Error loading file: "..tostring(err), "load" --? tostring + end + if not ran then + return nil, "Error running file: "..tostring(err), "run" --? tostring + end + return true, err +end + +--- Load a Lua file containing assignments, storing them in a table. +-- The global environment is not propagated to the loaded file. +-- @param filename string: the name of the file. +-- @param tbl table or nil: if given, this table is used to store +-- loaded values. +-- @return (table, table) or (nil, string, string): a table with the file's assignments +-- as fields and set of undefined globals accessed in file, +-- or nil, an error message and an error code ("open"; couldn't open the file, +-- "load"; compile-time error, or "run"; run-time error) +-- in case of errors. +function persist.load_into_table(filename: string, tbl: {string:any}) : table, table | nil, string, string + assert(type(filename) == "string") --? needed + assert(type(tbl) == "table" or not tbl) --? needed + + local result: {string:any} = tbl or {} + local globals: table = {} --? {string:boolean} + local globals_mt = { + __index = function(_, k: string) + globals[k] = true + end + } + local save_mt = getmetatable(result) + setmetatable(result, globals_mt) + + local ok, err, errcode = persist.run_file(filename, result) --? boolean, string, string + + setmetatable(result, save_mt) + + if not ok then + return nil, err, errcode --! table | nil???? + end + return result, globals +end + +return persist + 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 @@ +-- Detect the operating system and architecture without forking a subprocess. +-- +-- We are not going for exhaustive list of every historical system here, +-- but aiming to cover every platform where LuaRocks is known to run. +-- If your system is not detected, patches are welcome! + +local sysdetect = {} + +local function hex(s) + return s:gsub("$(..)", function(x) + return string.char(tonumber(x, 16)) + end) +end + +local function read_int8(fd) + if io.type(fd) == "closed file" then + return nil + end + local s = fd:read(1) + if not s then + fd:close() + return nil + end + return s:byte() +end + +local LITTLE = 1 +-- local BIG = 2 + +local function bytes2number(s, endian) + local r = 0 + if endian == LITTLE then + for i = #s, 1, -1 do + r = r*256 + s:byte(i,i) + end + else + for i = 1, #s do + r = r*256 + s:byte(i,i) + end + end + return r +end + +local function read(fd, bytes, endian) + if io.type(fd) == "closed file" then + return nil + end + local s = fd:read(bytes) + if not s + then fd:close() + return nil + end + return bytes2number(s, endian) +end + +local function read_int32le(fd) + return read(fd, 4, LITTLE) +end + +-------------------------------------------------------------------------------- +-- @section ELF +-------------------------------------------------------------------------------- + +local e_osabi = { + [0x00] = "sysv", + [0x01] = "hpux", + [0x02] = "netbsd", + [0x03] = "linux", + [0x04] = "hurd", + [0x06] = "solaris", + [0x07] = "aix", + [0x08] = "irix", + [0x09] = "freebsd", + [0x0c] = "openbsd", +} + +local e_machines = { + [0x02] = "sparc", + [0x03] = "x86", + [0x08] = "mips", + [0x0f] = "hppa", + [0x12] = "sparcv8", + [0x14] = "ppc", + [0x15] = "ppc64", + [0x16] = "s390", + [0x28] = "arm", + [0x2a] = "superh", + [0x2b] = "sparcv9", + [0x32] = "ia_64", + [0x3E] = "x86_64", + [0xB6] = "alpha", + [0xB7] = "aarch64", + [0xF3] = "riscv64", + [0x9026] = "alpha", +} + +local SHT_NOTE = 7 + +local function read_elf_section_headers(fd, hdr) + local endian = hdr.endian + local word = hdr.word + + local strtab_offset + local sections = {} + for i = 0, hdr.e_shnum - 1 do + fd:seek("set", hdr.e_shoff + (i * hdr.e_shentsize)) + local section = {} + section.sh_name_off = read(fd, 4, endian) + section.sh_type = read(fd, 4, endian) + section.sh_flags = read(fd, word, endian) + section.sh_addr = read(fd, word, endian) + section.sh_offset = read(fd, word, endian) + section.sh_size = read(fd, word, endian) + section.sh_link = read(fd, 4, endian) + section.sh_info = read(fd, 4, endian) + if section.sh_type == SHT_NOTE then + fd:seek("set", section.sh_offset) + section.namesz = read(fd, 4, endian) + section.descsz = read(fd, 4, endian) + section.type = read(fd, 4, endian) + section.namedata = fd:read(section.namesz):gsub("%z.*", "") + section.descdata = fd:read(section.descsz) + elseif i == hdr.e_shstrndx then + strtab_offset = section.sh_offset + end + table.insert(sections, section) + end + if strtab_offset then + for _, section in ipairs(sections) do + fd:seek("set", strtab_offset + section.sh_name_off) + section.name = fd:read(32):gsub("%z.*", "") + sections[section.name] = section + end + end + return sections +end + +local function detect_elf_system(fd, hdr, sections) + local system = e_osabi[hdr.osabi] + local endian = hdr.endian + + if system == "sysv" then + local abitag = sections[".note.ABI-tag"] + if abitag then + if abitag.namedata == "GNU" and abitag.type == 1 + and abitag.descdata:sub(0, 4) == "\0\0\0\0" then + return "linux" + end + elseif sections[".SUNW_version"] + or sections[".SUNW_signature"] then + return "solaris" + elseif sections[".note.netbsd.ident"] then + return "netbsd" + elseif sections[".note.openbsd.ident"] then + return "openbsd" + elseif sections[".note.tag"] and + sections[".note.tag"].namedata == "DragonFly" then + return "dragonfly" + end + + local gnu_version_r = sections[".gnu.version_r"] + if gnu_version_r then + + local dynstr = sections[".dynstr"].sh_offset + + local idx = 0 + for _ = 0, gnu_version_r.sh_info - 1 do + fd:seek("set", gnu_version_r.sh_offset + idx) + assert(read(fd, 2, endian)) -- vn_version + local vn_cnt = read(fd, 2, endian) + local vn_file = read(fd, 4, endian) + local vn_next = read(fd, 2, endian) + + fd:seek("set", dynstr + vn_file) + local libname = fd:read(64):gsub("%z.*", "") + + if hdr.e_type == 0x03 and libname == "libroot.so" then + return "haiku" + elseif libname:match("linux") then + return "linux" + end + + idx = idx + (vn_next * (vn_cnt + 1)) + end + end + + local procfile = io.open("/proc/sys/kernel/ostype") + if procfile then + local version = procfile:read(6) + procfile:close() + if version == "Linux\n" then + return "linux" + end + end + end + + return system +end + +local function read_elf_header(fd) + local hdr = {} + + hdr.bits = read_int8(fd) + hdr.endian = read_int8(fd) + hdr.elf_version = read_int8(fd) + if hdr.elf_version ~= 1 then + return nil + end + hdr.osabi = read_int8(fd) + if not hdr.osabi then + return nil + end + + local endian = hdr.endian + fd:seek("set", 0x10) + hdr.e_type = read(fd, 2, endian) + local machine = read(fd, 2, endian) + local processor = e_machines[machine] or "unknown" + if endian == 1 and processor == "ppc64" then + processor = "ppc64le" + end + + local elfversion = read(fd, 4, endian) + if elfversion ~= 1 then + return nil + end + + local word = (hdr.bits == 1) and 4 or 8 + hdr.word = word + + hdr.e_entry = read(fd, word, endian) + hdr.e_phoff = read(fd, word, endian) + hdr.e_shoff = read(fd, word, endian) + hdr.e_flags = read(fd, 4, endian) + hdr.e_ehsize = read(fd, 2, endian) + hdr.e_phentsize = read(fd, 2, endian) + hdr.e_phnum = read(fd, 2, endian) + hdr.e_shentsize = read(fd, 2, endian) + hdr.e_shnum = read(fd, 2, endian) + hdr.e_shstrndx = read(fd, 2, endian) + + return hdr, processor +end + +local function detect_elf(fd) + local hdr, processor = read_elf_header(fd) + if not hdr then + return nil + end + local sections = read_elf_section_headers(fd, hdr) + local system = detect_elf_system(fd, hdr, sections) + return system, processor +end + +-------------------------------------------------------------------------------- +-- @section Mach Objects (Apple) +-------------------------------------------------------------------------------- + +local mach_l64 = { + [7] = "x86_64", + [12] = "aarch64", +} + +local mach_b64 = { + [0] = "ppc64", +} + +local mach_l32 = { + [7] = "x86", + [12] = "arm", +} + +local mach_b32 = { + [0] = "ppc", +} + +local function detect_mach(magic, fd) + if not magic then + return nil + end + + if magic == hex("$CA$FE$BA$BE") then + -- fat binary, go for the first one + fd:seek("set", 0x12) + local offs = read_int8(fd) + if not offs then + return nil + end + fd:seek("set", offs * 256) + magic = fd:read(4) + return detect_mach(magic, fd) + end + + local cputype = read_int8(fd) + + if magic == hex("$CF$FA$ED$FE") then + return "macosx", mach_l64[cputype] or "unknown" + elseif magic == hex("$FE$ED$CF$FA") then + return "macosx", mach_b64[cputype] or "unknown" + elseif magic == hex("$CE$FA$ED$FE") then + return "macosx", mach_l32[cputype] or "unknown" + elseif magic == hex("$FE$ED$FA$CE") then + return "macosx", mach_b32[cputype] or "unknown" + end +end + +-------------------------------------------------------------------------------- +-- @section PE (Windows) +-------------------------------------------------------------------------------- + +local pe_machine = { + [0x8664] = "x86_64", + [0x01c0] = "arm", + [0x01c4] = "armv7l", + [0xaa64] = "arm64", + [0x014c] = "x86", +} + +local function detect_pe(fd) + fd:seek("set", 60) -- position of PE header position + local peoffset = read_int32le(fd) -- read position of PE header + if not peoffset then + return nil + end + local system = "windows" + fd:seek("set", peoffset + 4) -- move to position of Machine section + local machine = read(fd, 2, LITTLE) + local processor = pe_machine[machine] + + local rdata_pos = fd:read(736):match(".rdata%z%z............(....)") + if rdata_pos then + rdata_pos = bytes2number(rdata_pos, LITTLE) + fd:seek("set", rdata_pos) + local data = fd:read(512) + if data:match("cygwin") or data:match("cyggcc") then + system = "cygwin" + end + end + + return system, processor or "unknown" +end + +-------------------------------------------------------------------------------- +-- @section API +-------------------------------------------------------------------------------- + +function sysdetect.detect_file(file) + assert(type(file) == "string") + local fd = io.open(file, "rb") + if not fd then + return nil + end + local magic = fd:read(4) + if magic == hex("$7FELF") then + return detect_elf(fd) + end + if magic == hex("MZ$90$00") then + return detect_pe(fd) + end + return detect_mach(magic, fd) +end + +local cache_system +local cache_processor + +function sysdetect.detect(input_file) + local dirsep = package.config:sub(1,1) + local files + + if input_file then + files = { input_file } + else + if cache_system then + return cache_system, cache_processor + end + + local PATHsep + local interp = arg and arg[-1] + if dirsep == "/" then + -- Unix + files = { + "/bin/sh", -- Unix: well-known POSIX path + "/proc/self/exe", -- Linux: this should always have a working binary + } + PATHsep = ":" + else + -- Windows + local systemroot = os.getenv("SystemRoot") + files = { + systemroot .. "\\system32\\notepad.exe", -- well-known Windows path + systemroot .. "\\explorer.exe", -- well-known Windows path + } + if interp and not interp:lower():match("exe$") then + interp = interp .. ".exe" + end + PATHsep = ";" + end + if interp then + if interp:match(dirsep) then + -- interpreter path is absolute + table.insert(files, 1, interp) + else + for d in (os.getenv("PATH") or ""):gmatch("[^"..PATHsep.."]+") do + table.insert(files, d .. dirsep .. interp) + end + end + end + end + for _, f in ipairs(files) do + local system, processor = sysdetect.detect_file(f) + if system then + cache_system = system + cache_processor = processor + return system, processor + end + end +end + +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 @@ --- Detect the operating system and architecture without forking a subprocess. --- --- We are not going for exhaustive list of every historical system here, --- but aiming to cover every platform where LuaRocks is known to run. --- If your system is not detected, patches are welcome! +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 + + + + local sysdetect = {} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + local function hex(s) - return s:gsub("$(..)", function(x) + return (s:gsub("$(..)", function(x) return string.char(tonumber(x, 16)) - end) + end)) end local function read_int8(fd) @@ -24,18 +74,15 @@ local function read_int8(fd) return s:byte() end -local LITTLE = 1 --- local BIG = 2 - local function bytes2number(s, endian) local r = 0 - if endian == LITTLE then + if endian == "little" then for i = #s, 1, -1 do - r = r*256 + s:byte(i,i) + r = r * 256 + s:byte(i, i) end else for i = 1, #s do - r = r*256 + s:byte(i,i) + r = r * 256 + s:byte(i, i) end end return r @@ -46,20 +93,62 @@ local function read(fd, bytes, endian) return nil end local s = fd:read(bytes) - if not s - then fd:close() + if not s then + fd:close() return nil end return bytes2number(s, endian) end local function read_int32le(fd) - return read(fd, 4, LITTLE) + return read(fd, 4, "little") end --------------------------------------------------------------------------------- --- @section ELF --------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +local endians = { + [0x01] = "little", + [0x02] = "big", +} local e_osabi = { [0x00] = "sysv", @@ -97,11 +186,12 @@ local e_machines = { local SHT_NOTE = 7 local function read_elf_section_headers(fd, hdr) - local endian = hdr.endian + local endian = endians[hdr.endian] local word = hdr.word local strtab_offset local sections = {} + local secarray = {} for i = 0, hdr.e_shnum - 1 do fd:seek("set", hdr.e_shoff + (i * hdr.e_shentsize)) local section = {} @@ -123,10 +213,10 @@ local function read_elf_section_headers(fd, hdr) elseif i == hdr.e_shstrndx then strtab_offset = section.sh_offset end - table.insert(sections, section) + table.insert(secarray, section) end if strtab_offset then - for _, section in ipairs(sections) do + for _, section in ipairs(secarray) do fd:seek("set", strtab_offset + section.sh_name_off) section.name = fd:read(32):gsub("%z.*", "") sections[section.name] = section @@ -137,24 +227,24 @@ end local function detect_elf_system(fd, hdr, sections) local system = e_osabi[hdr.osabi] - local endian = hdr.endian + local endian = endians[hdr.endian] if system == "sysv" then local abitag = sections[".note.ABI-tag"] if abitag then - if abitag.namedata == "GNU" and abitag.type == 1 - and abitag.descdata:sub(0, 4) == "\0\0\0\0" then + if abitag.namedata == "GNU" and abitag.type == 1 and + abitag.descdata:sub(0, 4) == "\0\0\0\0" then return "linux" end - elseif sections[".SUNW_version"] - or sections[".SUNW_signature"] then + elseif sections[".SUNW_version"] or + sections[".SUNW_signature"] then return "solaris" elseif sections[".note.netbsd.ident"] then return "netbsd" elseif sections[".note.openbsd.ident"] then return "openbsd" elseif sections[".note.tag"] and - sections[".note.tag"].namedata == "DragonFly" then + sections[".note.tag"].namedata == "DragonFly" then return "dragonfly" end @@ -166,7 +256,7 @@ local function detect_elf_system(fd, hdr, sections) local idx = 0 for _ = 0, gnu_version_r.sh_info - 1 do fd:seek("set", gnu_version_r.sh_offset + idx) - assert(read(fd, 2, endian)) -- vn_version + assert(read(fd, 2, endian)) local vn_cnt = read(fd, 2, endian) local vn_file = read(fd, 4, endian) local vn_next = read(fd, 2, endian) @@ -211,12 +301,12 @@ local function read_elf_header(fd) return nil end - local endian = hdr.endian + local endian = endians[hdr.endian] fd:seek("set", 0x10) hdr.e_type = read(fd, 2, endian) local machine = read(fd, 2, endian) local processor = e_machines[machine] or "unknown" - if endian == 1 and processor == "ppc64" then + if endian == "little" and processor == "ppc64" then processor = "ppc64le" end @@ -252,9 +342,9 @@ local function detect_elf(fd) return system, processor end --------------------------------------------------------------------------------- --- @section Mach Objects (Apple) --------------------------------------------------------------------------------- + + + local mach_l64 = { [7] = "x86_64", @@ -280,7 +370,7 @@ local function detect_mach(magic, fd) end if magic == hex("$CA$FE$BA$BE") then - -- fat binary, go for the first one + fd:seek("set", 0x12) local offs = read_int8(fd) if not offs then @@ -304,32 +394,32 @@ local function detect_mach(magic, fd) end end --------------------------------------------------------------------------------- --- @section PE (Windows) --------------------------------------------------------------------------------- + + + local pe_machine = { - [0x8664] = "x86_64", - [0x01c0] = "arm", - [0x01c4] = "armv7l", - [0xaa64] = "arm64", - [0x014c] = "x86", + [0x8664] = "x86_64", + [0x01c0] = "arm", + [0x01c4] = "armv7l", + [0xaa64] = "arm64", + [0x014c] = "x86", } local function detect_pe(fd) - fd:seek("set", 60) -- position of PE header position - local peoffset = read_int32le(fd) -- read position of PE header + fd:seek("set", 60) + local peoffset = read_int32le(fd) if not peoffset then return nil end local system = "windows" - fd:seek("set", peoffset + 4) -- move to position of Machine section - local machine = read(fd, 2, LITTLE) + fd:seek("set", peoffset + 4) + local machine = read(fd, 2, "little") local processor = pe_machine[machine] - local rdata_pos = fd:read(736):match(".rdata%z%z............(....)") - if rdata_pos then - rdata_pos = bytes2number(rdata_pos, LITTLE) + local rdata_pos_s = fd:read(736):match(".rdata%z%z............(....)") + if rdata_pos_s then + local rdata_pos = bytes2number(rdata_pos_s, "little") fd:seek("set", rdata_pos) local data = fd:read(512) if data:match("cygwin") or data:match("cyggcc") then @@ -340,9 +430,9 @@ local function detect_pe(fd) return system, processor or "unknown" end --------------------------------------------------------------------------------- --- @section API --------------------------------------------------------------------------------- + + + function sysdetect.detect_file(file) assert(type(file) == "string") @@ -364,7 +454,7 @@ local cache_system local cache_processor function sysdetect.detect(input_file) - local dirsep = package.config:sub(1,1) + local dirsep = package.config:sub(1, 1) local files if input_file then @@ -377,18 +467,18 @@ function sysdetect.detect(input_file) local PATHsep local interp = arg and arg[-1] if dirsep == "/" then - -- Unix + files = { - "/bin/sh", -- Unix: well-known POSIX path - "/proc/self/exe", -- Linux: this should always have a working binary + "/bin/sh", + "/proc/self/exe", } PATHsep = ":" else - -- Windows + local systemroot = os.getenv("SystemRoot") files = { - systemroot .. "\\system32\\notepad.exe", -- well-known Windows path - systemroot .. "\\explorer.exe", -- well-known Windows path + systemroot .. "\\system32\\notepad.exe", + systemroot .. "\\explorer.exe", } if interp and not interp:lower():match("exe$") then interp = interp .. ".exe" @@ -397,10 +487,10 @@ function sysdetect.detect(input_file) end if interp then if interp:match(dirsep) then - -- interpreter path is absolute + table.insert(files, 1, interp) else - for d in (os.getenv("PATH") or ""):gmatch("[^"..PATHsep.."]+") do + for d in (os.getenv("PATH") or ""):gmatch("[^" .. PATHsep .. "]+") do table.insert(files, d .. dirsep .. interp) end 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 @@ +-- Detect the operating system and architecture without forking a subprocess. +-- +-- We are not going for exhaustive list of every historical system here, +-- but aiming to cover every platform where LuaRocks is known to run. +-- If your system is not detected, patches are welcome! + +local record sysdetect + enum Processor + "unknown" + "sparc" + "x86" + "mips" + "hppa" + "sparcv8" + "ppc" + "ppc64" + "ppc64le" + "s390" + "arm" + "armv7l" + "arm64" + "superh" + "sparcv9" + "ia_64" + "x86_64" + "alpha" + "aarch64" + "riscv64" + "alpha" + end + + enum System + "sysv" + "hpux" + "netbsd" + "linux" + "hurd" + "solaris" + "aix" + "irix" + "freebsd" + "openbsd" + "dragonfly" + "haiku" + "windows" + "cygwin" + "macosx" + end +end + +local type System = sysdetect.System --? ? +local type Processor = sysdetect.Processor --? ? + +local enum Endian + "little" + "big" +end + +local function hex(s: string): string + return (s:gsub("$(..)", function(x: string): string + return string.char(tonumber(x, 16)) + end)) +end + +local function read_int8(fd: FILE): integer + if io.type(fd) == "closed file" then + return nil + end + local s = fd:read(1) + if not s then + fd:close() + return nil + end + return s:byte() +end + +local function bytes2number(s: string, endian: Endian): integer + local r: integer = 0 + if endian == "little" then + for i = #s, 1, -1 do + r = r*256 + s:byte(i,i) + end + else + for i = 1, #s do + r = r*256 + s:byte(i,i) + end + end + return r +end + +local function read(fd: FILE, bytes: integer, endian: Endian): integer + if io.type(fd) == "closed file" then + return nil + end + local s: string = fd:read(bytes) + if not s + then fd:close() + return nil + end + return bytes2number(s, endian) +end + +local function read_int32le(fd: FILE): integer + return read(fd, 4, "little") +end + +-------------------------------------------------------------------------------- +-- @section ELF +-------------------------------------------------------------------------------- + +local record ElfHeader + osabi: integer + bits: integer + endian: integer + elf_version: integer + word: integer + e_type: integer + e_entry: integer + e_phoff: integer + e_shoff: integer + e_flags: integer + e_ehsize: integer + e_phentsize: integer + e_phnum: integer + e_shentsize: integer + e_shnum: integer + e_shstrndx: integer +end + +local record ElfSection + sh_name_off: integer + sh_type: integer + sh_flags: integer + sh_addr: integer + sh_offset: integer + sh_size: integer + sh_link: integer + sh_info: integer + + name: string + namesz: integer + namedata: string + descsz: integer + descdata: string + type: integer +end + +local endians: {integer:Endian} = { + [0x01] = "little", + [0x02] = "big", +} + +local e_osabi: {integer:System} = { + [0x00] = "sysv", + [0x01] = "hpux", + [0x02] = "netbsd", + [0x03] = "linux", + [0x04] = "hurd", + [0x06] = "solaris", + [0x07] = "aix", + [0x08] = "irix", + [0x09] = "freebsd", + [0x0c] = "openbsd", +} + +local e_machines: {integer:Processor} = { + [0x02] = "sparc", + [0x03] = "x86", + [0x08] = "mips", + [0x0f] = "hppa", + [0x12] = "sparcv8", + [0x14] = "ppc", + [0x15] = "ppc64", + [0x16] = "s390", + [0x28] = "arm", + [0x2a] = "superh", + [0x2b] = "sparcv9", + [0x32] = "ia_64", + [0x3E] = "x86_64", + [0xB6] = "alpha", + [0xB7] = "aarch64", + [0xF3] = "riscv64", + [0x9026] = "alpha", +} + +local SHT_NOTE = 7 + +local function read_elf_section_headers(fd: FILE, hdr: ElfHeader): {string: ElfSection} + local endian = endians[hdr.endian] + local word: integer = hdr.word + + local strtab_offset: integer + local sections: {string:ElfSection} = {} + local secarray: {ElfSection} = {} + for i = 0, hdr.e_shnum - 1 do + fd:seek("set", hdr.e_shoff + (i * hdr.e_shentsize)) + local section: ElfSection = {} + section.sh_name_off = read(fd, 4, endian) + section.sh_type = read(fd, 4, endian) + section.sh_flags = read(fd, word, endian) + section.sh_addr = read(fd, word, endian) + section.sh_offset = read(fd, word, endian) + section.sh_size = read(fd, word, endian) + section.sh_link = read(fd, 4, endian) + section.sh_info = read(fd, 4, endian) + if section.sh_type == SHT_NOTE then + fd:seek("set", section.sh_offset) + section.namesz = read(fd, 4, endian) + section.descsz = read(fd, 4, endian) + section.type = read(fd, 4, endian) + section.namedata = fd:read(section.namesz):gsub("%z.*", "") + section.descdata = fd:read(section.descsz) + elseif i == hdr.e_shstrndx then + strtab_offset = section.sh_offset + end + table.insert(secarray, section) + end + if strtab_offset then + for _, section in ipairs(secarray) do + fd:seek("set", strtab_offset + section.sh_name_off) + section.name = fd:read(32):gsub("%z.*", "") + sections[section.name] = section + end + end + return sections +end + +local function detect_elf_system(fd: FILE, hdr: ElfHeader, sections: {string:ElfSection}): System + local system = e_osabi[hdr.osabi] + local endian = endians[hdr.endian] + + if system == "sysv" then + local abitag = sections[".note.ABI-tag"] + if abitag then + if abitag.namedata == "GNU" and abitag.type == 1 + and abitag.descdata:sub(0, 4) == "\0\0\0\0" then + return "linux" + end + elseif sections[".SUNW_version"] + or sections[".SUNW_signature"] then + return "solaris" + elseif sections[".note.netbsd.ident"] then + return "netbsd" + elseif sections[".note.openbsd.ident"] then + return "openbsd" + elseif sections[".note.tag"] and + sections[".note.tag"].namedata == "DragonFly" then + return "dragonfly" + end + + local gnu_version_r = sections[".gnu.version_r"] + if gnu_version_r then + + local dynstr = sections[".dynstr"].sh_offset + + local idx = 0 + for _ = 0, gnu_version_r.sh_info - 1 do + fd:seek("set", gnu_version_r.sh_offset + idx) + assert(read(fd, 2, endian)) -- vn_version --? + local vn_cnt = read(fd, 2, endian) + local vn_file = read(fd, 4, endian) + local vn_next = read(fd, 2, endian) + + fd:seek("set", dynstr + vn_file) + local libname = fd:read(64):gsub("%z.*", "") + + if hdr.e_type == 0x03 and libname == "libroot.so" then + return "haiku" + elseif libname:match("linux") then + return "linux" + end + + idx = idx + (vn_next * (vn_cnt + 1)) + end + end + + local procfile = io.open("/proc/sys/kernel/ostype") + if procfile then + local version = procfile:read(6) + procfile:close() + if version == "Linux\n" then + return "linux" + end + end + end + + return system +end + +local function read_elf_header(fd: FILE): ElfHeader, Processor + local hdr: ElfHeader = {} + + hdr.bits = read_int8(fd) + hdr.endian = read_int8(fd) + hdr.elf_version = read_int8(fd) + if hdr.elf_version ~= 1 then + return nil + end + hdr.osabi = read_int8(fd) + if not hdr.osabi then + return nil + end + + local endian = endians[hdr.endian] + fd:seek("set", 0x10) + hdr.e_type = read(fd, 2, endian) + local machine = read(fd, 2, endian) + local processor = e_machines[machine] or "unknown" + if endian == "little" and processor == "ppc64" then + processor = "ppc64le" + end + + local elfversion = read(fd, 4, endian) + if elfversion ~= 1 then + return nil + end + + local word = (hdr.bits == 1) and 4 or 8 + hdr.word = word + + hdr.e_entry = read(fd, word, endian) + hdr.e_phoff = read(fd, word, endian) + hdr.e_shoff = read(fd, word, endian) + hdr.e_flags = read(fd, 4, endian) + hdr.e_ehsize = read(fd, 2, endian) + hdr.e_phentsize = read(fd, 2, endian) + hdr.e_phnum = read(fd, 2, endian) + hdr.e_shentsize = read(fd, 2, endian) + hdr.e_shnum = read(fd, 2, endian) + hdr.e_shstrndx = read(fd, 2, endian) + + return hdr, processor +end + +local function detect_elf(fd: FILE): System, Processor + local hdr, processor = read_elf_header(fd) + if not hdr then + return nil + end + local sections = read_elf_section_headers(fd, hdr) + local system = detect_elf_system(fd, hdr, sections) + return system, processor +end + +-------------------------------------------------------------------------------- +-- @section Mach Objects (Apple) +-------------------------------------------------------------------------------- + +local mach_l64: {integer:Processor} = { + [7] = "x86_64", + [12] = "aarch64", +} + +local mach_b64: {integer:Processor} = { + [0] = "ppc64", +} + +local mach_l32: {integer:Processor} = { + [7] = "x86", + [12] = "arm", +} + +local mach_b32: {integer:Processor} = { + [0] = "ppc", +} + +local function detect_mach(magic: string, fd: FILE): System, Processor + if not magic then + return nil + end + + if magic == hex("$CA$FE$BA$BE") then + -- fat binary, go for the first one + fd:seek("set", 0x12) + local offs = read_int8(fd) + if not offs then + return nil + end + fd:seek("set", offs * 256) + magic = fd:read(4) + return detect_mach(magic, fd) + end + + local cputype = read_int8(fd) + + if magic == hex("$CF$FA$ED$FE") then + return "macosx", mach_l64[cputype] or "unknown" + elseif magic == hex("$FE$ED$CF$FA") then + return "macosx", mach_b64[cputype] or "unknown" + elseif magic == hex("$CE$FA$ED$FE") then + return "macosx", mach_l32[cputype] or "unknown" + elseif magic == hex("$FE$ED$FA$CE") then + return "macosx", mach_b32[cputype] or "unknown" + end +end + +-------------------------------------------------------------------------------- +-- @section PE (Windows) +-------------------------------------------------------------------------------- + +local pe_machine = { + [0x8664] = "x86_64", + [0x01c0] = "arm", + [0x01c4] = "armv7l", + [0xaa64] = "arm64", + [0x014c] = "x86", +} + +local function detect_pe(fd: FILE): System, Processor + fd:seek("set", 60) -- position of PE header position + local peoffset = read_int32le(fd) -- read position of PE header + if not peoffset then + return nil + end + local system: System = "windows" + fd:seek("set", peoffset + 4) -- move to position of Machine section + local machine = read(fd, 2, "little") + local processor: Processor = pe_machine[machine] + + local rdata_pos_s = fd:read(736):match(".rdata%z%z............(....)") + if rdata_pos_s then + local rdata_pos = bytes2number(rdata_pos_s, "little") + fd:seek("set", rdata_pos) + local data = fd:read(512) + if data:match("cygwin") or data:match("cyggcc") then + system = "cygwin" + end + end + + return system, processor or "unknown" +end + +-------------------------------------------------------------------------------- +-- @section API +-------------------------------------------------------------------------------- + +function sysdetect.detect_file(file: string): System, Processor + assert(type(file) == "string") + local fd = io.open(file, "rb") + if not fd then + return nil + end + local magic = fd:read(4) + if magic == hex("$7FELF") then + return detect_elf(fd) + end + if magic == hex("MZ$90$00") then + return detect_pe(fd) + end + return detect_mach(magic, fd) +end + +local cache_system: System +local cache_processor: Processor + +function sysdetect.detect(input_file: string): System, Processor + local dirsep = package.config:sub(1,1) + local files: {string} + + if input_file then + files = { input_file } + else + if cache_system then + return cache_system, cache_processor + end + + local PATHsep: string + local interp = arg and arg[-1] + if dirsep == "/" then + -- Unix + files = { + "/bin/sh", -- Unix: well-known POSIX path + "/proc/self/exe", -- Linux: this should always have a working binary + } + PATHsep = ":" + else + -- Windows + local systemroot = os.getenv("SystemRoot") + files = { + systemroot .. "\\system32\\notepad.exe", -- well-known Windows path + systemroot .. "\\explorer.exe", -- well-known Windows path + } + if interp and not interp:lower():match("exe$") then + interp = interp .. ".exe" + end + PATHsep = ";" + end + if interp then + if interp:match(dirsep) then + -- interpreter path is absolute + table.insert(files, 1, interp) + else + for d in (os.getenv("PATH") or ""):gmatch("[^"..PATHsep.."]+") do + table.insert(files, d .. dirsep .. interp) + end + end + end + end + for _, f in ipairs(files) do + local system, processor = sysdetect.detect_file(f) + if system then + cache_system = system + cache_processor = processor + return system, processor + end + end +end + +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 @@ + +local util = {} + +local require = nil +-------------------------------------------------------------------------------- + +local dir_sep = package.config:sub(1, 1) + +--- Run a process and read a its output. +-- Equivalent to io.popen(cmd):read("*l"), except that it +-- closes the fd right away. +-- @param cmd string: The command to execute +-- @param spec string: "*l" by default, to read a single line. +-- May be used to read more, passing, for instance, "*a". +-- @return string: the output of the program. +function util.popen_read(cmd, spec) + local tmpfile = (dir_sep == "\\") + and (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000))) + or os.tmpname() + os.execute(cmd .. " > " .. tmpfile) + local fd = io.open(tmpfile, "rb") + if not fd then + os.remove(tmpfile) + return "" + end + local out = fd:read(spec or "*l") + fd:close() + os.remove(tmpfile) + return out or "" +end + +--- +-- Formats tables with cycles recursively to any depth. +-- References to other tables are shown as values. +-- Self references are indicated. +-- The string returned is "Lua code", which can be processed +-- (in the case in which indent is composed by spaces or "--"). +-- Userdata and function keys and values are shown as strings, +-- which logically are exactly not equivalent to the original code. +-- This routine can serve for pretty formating tables with +-- proper indentations, apart from printing them: +-- io.write(table.show(t, "t")) -- a typical use +-- Written by Julio Manuel Fernandez-Diaz, +-- Heavily based on "Saving tables with cycles", PIL2, p. 113. +-- @param t table: is the table. +-- @param tname string: is the name of the table (optional) +-- @param top_indent string: is a first indentation (optional). +-- @return string: the pretty-printed table +function util.show_table(t, tname, top_indent) + local cart -- a container + local autoref -- for self references + + local function is_empty_table(tbl) return next(tbl) == nil end + + local function basic_serialize(o) + local so = tostring(o) + if type(o) == "function" then + local info = debug and debug.getinfo(o, "S") + if not info then + return ("%q"):format(so) + end + -- info.name is nil because o is not a calling level + if info.what == "C" then + return ("%q"):format(so .. ", C function") + else + -- the information is defined through lines + return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source) + end + elseif type(o) == "number" then + return so + else + return ("%q"):format(so) + end + end + + local function add_to_cart(value, name, indent, saved, field) + indent = indent or "" + saved = saved or {} + field = field or name + + cart = cart .. indent .. field + + if type(value) ~= "table" then + cart = cart .. " = " .. basic_serialize(value) .. ";\n" + else + if saved[value] then + cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n" + autoref = autoref .. name .. " = " .. saved[value] .. ";\n" + else + saved[value] = name + if is_empty_table(value) then + cart = cart .. " = {};\n" + else + cart = cart .. " = {\n" + for k, v in pairs(value) do + k = basic_serialize(k) + local fname = ("%s[%s]"):format(name, k) + field = ("[%s]"):format(k) + -- three spaces between levels + add_to_cart(v, fname, indent .. " ", saved, field) + end + cart = cart .. indent .. "};\n" + end + end + end + end + + tname = tname or "__unnamed__" + if type(t) ~= "table" then + return tname .. " = " .. basic_serialize(t) + end + cart, autoref = "", "" + add_to_cart(t, tname, top_indent) + return cart .. autoref +end + +--- Merges contents of src on top of dst's contents +-- (i.e. if an key from src already exists in dst, replace it). +-- @param dst Destination table, which will receive src's contents. +-- @param src Table which provides new contents to dst. +function util.deep_merge(dst, src) + for k, v in pairs(src) do + if type(v) == "table" then + if dst[k] == nil then + dst[k] = {} + end + if type(dst[k]) == "table" then + util.deep_merge(dst[k], v) + else + dst[k] = v + end + else + dst[k] = v + end + end +end + +--- Merges contents of src below those of dst's contents +-- (i.e. if an key from src already exists in dst, do not replace it). +-- @param dst Destination table, which will receive src's contents. +-- @param src Table which provides new contents to dst. +function util.deep_merge_under(dst, src) + for k, v in pairs(src) do + if type(v) == "table" then + if dst[k] == nil then + dst[k] = {} + end + if type(dst[k]) == "table" then + util.deep_merge_under(dst[k], v) + end + elseif dst[k] == nil then + dst[k] = v + end + end +end + +--- Clean up a path-style string ($PATH, $LUA_PATH/package.path, etc.), +-- removing repeated entries and making sure only the relevant +-- Lua version is used. +-- Example: given ("a;b;c;a;b;d", ";"), returns "a;b;c;d". +-- @param list string: A path string (from $PATH or package.path) +-- @param sep string: The separator +-- @param lua_version (optional) string: The Lua version to use. +-- @param keep_first (optional) if true, keep first occurrence in case +-- of duplicates; otherwise keep last occurrence. The default is false. +function util.cleanup_path(list, sep, lua_version, keep_first) + assert(type(list) == "string") + assert(type(sep) == "string") + + list = list:gsub(dir_sep, "/") + + local parts = util.split_string(list, sep) + local final, entries = {}, {} + local start, stop, step + + if keep_first then + start, stop, step = 1, #parts, 1 + else + start, stop, step = #parts, 1, -1 + end + + for i = start, stop, step do + local part = parts[i]:gsub("//", "/") + if lua_version then + part = part:gsub("/lua/([%d.]+)/", function(part_version) + if part_version:sub(1, #lua_version) ~= lua_version then + return "/lua/"..lua_version.."/" + end + end) + end + if not entries[part] then + local at = keep_first and #final+1 or 1 + table.insert(final, at, part) + entries[part] = true + end + end + + return (table.concat(final, sep):gsub("/", dir_sep)) +end + +-- from http://lua-users.org/wiki/SplitJoin +-- by Philippe Lhoste +function util.split_string(str, delim, maxNb) + -- Eliminate bad cases... + if string.find(str, delim) == nil then + return { str } + end + if maxNb == nil or maxNb < 1 then + maxNb = 0 -- No limit + end + local result = {} + local pat = "(.-)" .. delim .. "()" + local nb = 0 + local lastPos + for part, pos in string.gmatch(str, pat) do + nb = nb + 1 + result[nb] = part + lastPos = pos + if nb == maxNb then break end + end + -- Handle the last field + if nb ~= maxNb then + result[nb + 1] = string.sub(str, lastPos) + end + return result +end + +--- Return an array of keys of a table. +-- @param tbl table: The input table. +-- @return table: The array of keys. +function util.keys(tbl) + local ks = {} + for k,_ in pairs(tbl) do + table.insert(ks, k) + end + return ks +end + +--- Print a line to standard error +function util.printerr(...) + io.stderr:write(table.concat({...},"\t")) + io.stderr:write("\n") +end + +--- Display a warning message. +-- @param msg string: the warning message +function util.warning(msg) + util.printerr("Warning: "..msg) +end + +--- Simple sort function used as a default for util.sortedpairs. +local function default_sort(a, b) + local ta = type(a) + local tb = type(b) + if ta == "number" and tb == "number" then + return a < b + elseif ta == "number" then + return true + elseif tb == "number" then + return false + else + return tostring(a) < tostring(b) + end +end + +--- A table iterator generator that returns elements sorted by key, +-- to be used in "for" loops. +-- @param tbl table: The table to be iterated. +-- @param sort_function function or table or nil: An optional comparison function +-- to be used by table.sort when sorting keys, or an array listing an explicit order +-- for keys. If a value itself is an array, it is taken so that the first element +-- is a string representing the field name, and the second element is a priority table +-- for that key, which is returned by the iterator as the third value after the key +-- and the value. +-- @return function: the iterator function. +function util.sortedpairs(tbl, sort_function) + sort_function = sort_function or default_sort + local keys = util.keys(tbl) + local sub_orders = {} + + if type(sort_function) == "function" then + table.sort(keys, sort_function) + else + local order = sort_function + local ordered_keys = {} + local all_keys = keys + keys = {} + + for _, order_entry in ipairs(order) do + local key, sub_order + if type(order_entry) == "table" then + key = order_entry[1] + sub_order = order_entry[2] + else + key = order_entry + end + + if tbl[key] then + ordered_keys[key] = true + sub_orders[key] = sub_order + table.insert(keys, key) + end + end + + table.sort(all_keys, default_sort) + for _, key in ipairs(all_keys) do + if not ordered_keys[key] then + table.insert(keys, key) + end + end + end + + local i = 1 + return function() + local key = keys[i] + i = i + 1 + return key, tbl[key], sub_orders[key] + end +end + +return util + 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 @@ - +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 local util = {} local require = nil --------------------------------------------------------------------------------- + local dir_sep = package.config:sub(1, 1) ---- Run a process and read a its output. --- Equivalent to io.popen(cmd):read("*l"), except that it --- closes the fd right away. --- @param cmd string: The command to execute --- @param spec string: "*l" by default, to read a single line. --- May be used to read more, passing, for instance, "*a". --- @return string: the output of the program. + + + + + + + function util.popen_read(cmd, spec) - local tmpfile = (dir_sep == "\\") - and (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000))) - or os.tmpname() + local tmpfile = (dir_sep == "\\") and + (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000))) or + os.tmpname() os.execute(cmd .. " > " .. tmpfile) local fd = io.open(tmpfile, "rb") if not fd then @@ -29,26 +29,26 @@ function util.popen_read(cmd, spec) return out or "" end ---- --- Formats tables with cycles recursively to any depth. --- References to other tables are shown as values. --- Self references are indicated. --- The string returned is "Lua code", which can be processed --- (in the case in which indent is composed by spaces or "--"). --- Userdata and function keys and values are shown as strings, --- which logically are exactly not equivalent to the original code. --- This routine can serve for pretty formating tables with --- proper indentations, apart from printing them: --- io.write(table.show(t, "t")) -- a typical use --- Written by Julio Manuel Fernandez-Diaz, --- Heavily based on "Saving tables with cycles", PIL2, p. 113. --- @param t table: is the table. --- @param tname string: is the name of the table (optional) --- @param top_indent string: is a first indentation (optional). --- @return string: the pretty-printed table + + + + + + + + + + + + + + + + + function util.show_table(t, tname, top_indent) - local cart -- a container - local autoref -- for self references + local cart + local autoref local function is_empty_table(tbl) return next(tbl) == nil end @@ -59,11 +59,11 @@ function util.show_table(t, tname, top_indent) if not info then return ("%q"):format(so) end - -- info.name is nil because o is not a calling level + if info.what == "C" then return ("%q"):format(so .. ", C function") else - -- the information is defined through lines + return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source) end elseif type(o) == "number" then @@ -85,7 +85,7 @@ function util.show_table(t, tname, top_indent) else if saved[value] then cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n" - autoref = autoref .. name .. " = " .. saved[value] .. ";\n" + autoref = autoref .. name .. " = " .. saved[value] .. ";\n" else saved[value] = name if is_empty_table(value) then @@ -96,7 +96,7 @@ function util.show_table(t, tname, top_indent) k = basic_serialize(k) local fname = ("%s[%s]"):format(name, k) field = ("[%s]"):format(k) - -- three spaces between levels + add_to_cart(v, fname, indent .. " ", saved, field) end cart = cart .. indent .. "};\n" @@ -114,10 +114,10 @@ function util.show_table(t, tname, top_indent) return cart .. autoref end ---- Merges contents of src on top of dst's contents --- (i.e. if an key from src already exists in dst, replace it). --- @param dst Destination table, which will receive src's contents. --- @param src Table which provides new contents to dst. + + + + function util.deep_merge(dst, src) for k, v in pairs(src) do if type(v) == "table" then @@ -135,10 +135,10 @@ function util.deep_merge(dst, src) end end ---- Merges contents of src below those of dst's contents --- (i.e. if an key from src already exists in dst, do not replace it). --- @param dst Destination table, which will receive src's contents. --- @param src Table which provides new contents to dst. + + + + function util.deep_merge_under(dst, src) for k, v in pairs(src) do if type(v) == "table" then @@ -154,15 +154,15 @@ function util.deep_merge_under(dst, src) end end ---- Clean up a path-style string ($PATH, $LUA_PATH/package.path, etc.), --- removing repeated entries and making sure only the relevant --- Lua version is used. --- Example: given ("a;b;c;a;b;d", ";"), returns "a;b;c;d". --- @param list string: A path string (from $PATH or package.path) --- @param sep string: The separator --- @param lua_version (optional) string: The Lua version to use. --- @param keep_first (optional) if true, keep first occurrence in case --- of duplicates; otherwise keep last occurrence. The default is false. + + + + + + + + + function util.cleanup_path(list, sep, lua_version, keep_first) assert(type(list) == "string") assert(type(sep) == "string") @@ -184,12 +184,12 @@ function util.cleanup_path(list, sep, lua_version, keep_first) if lua_version then part = part:gsub("/lua/([%d.]+)/", function(part_version) if part_version:sub(1, #lua_version) ~= lua_version then - return "/lua/"..lua_version.."/" + return "/lua/" .. lua_version .. "/" end end) end if not entries[part] then - local at = keep_first and #final+1 or 1 + local at = keep_first and #final + 1 or 1 table.insert(final, at, part) entries[part] = true end @@ -198,15 +198,15 @@ function util.cleanup_path(list, sep, lua_version, keep_first) return (table.concat(final, sep):gsub("/", dir_sep)) end --- from http://lua-users.org/wiki/SplitJoin --- by Philippe Lhoste + + function util.split_string(str, delim, maxNb) - -- Eliminate bad cases... + if string.find(str, delim) == nil then return { str } end if maxNb == nil or maxNb < 1 then - maxNb = 0 -- No limit + maxNb = 0 end local result = {} local pat = "(.-)" .. delim .. "()" @@ -218,42 +218,42 @@ function util.split_string(str, delim, maxNb) lastPos = pos if nb == maxNb then break end end - -- Handle the last field + if nb ~= maxNb then result[nb + 1] = string.sub(str, lastPos) end return result end ---- Return an array of keys of a table. --- @param tbl table: The input table. --- @return table: The array of keys. + + + function util.keys(tbl) local ks = {} - for k,_ in pairs(tbl) do + for k, _ in pairs(tbl) do table.insert(ks, k) end return ks end ---- Print a line to standard error + function util.printerr(...) - io.stderr:write(table.concat({...},"\t")) + io.stderr:write(table.concat({ ... }, "\t")) io.stderr:write("\n") end ---- Display a warning message. --- @param msg string: the warning message + + function util.warning(msg) - util.printerr("Warning: "..msg) + util.printerr("Warning: " .. msg) end ---- Simple sort function used as a default for util.sortedpairs. + local function default_sort(a, b) local ta = type(a) local tb = type(b) if ta == "number" and tb == "number" then - return a < b + return tonumber(a) < tonumber(b) elseif ta == "number" then return true elseif tb == "number" then @@ -263,16 +263,16 @@ local function default_sort(a, b) end end ---- A table iterator generator that returns elements sorted by key, --- to be used in "for" loops. --- @param tbl table: The table to be iterated. --- @param sort_function function or table or nil: An optional comparison function --- to be used by table.sort when sorting keys, or an array listing an explicit order --- for keys. If a value itself is an array, it is taken so that the first element --- is a string representing the field name, and the second element is a priority table --- for that key, which is returned by the iterator as the third value after the key --- and the value. --- @return function: the iterator function. + + + + + + + + + + function util.sortedpairs(tbl, sort_function) sort_function = sort_function or default_sort local keys = util.keys(tbl) @@ -319,4 +319,3 @@ function util.sortedpairs(tbl, sort_function) end return util - 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 @@ + +local util = {} + +local require = nil --! +-------------------------------------------------------------------------------- + +local dir_sep = package.config:sub(1, 1) + +--- Run a process and read a its output. +-- Equivalent to io.popen(cmd):read("*l"), except that it +-- closes the fd right away. +-- @param cmd string: The command to execute +-- @param spec string: "*l" by default, to read a single line. +-- May be used to read more, passing, for instance, "*a". +-- @return string: the output of the program. +function util.popen_read(cmd: string, spec: string): string + local tmpfile: string = (dir_sep == "\\") + and (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000))) + or os.tmpname() + os.execute(cmd .. " > " .. tmpfile) + local fd = io.open(tmpfile, "rb") + if not fd then + os.remove(tmpfile) + return "" + end + local out = fd:read(spec or "*l") + fd:close() + os.remove(tmpfile) + return out or "" +end + +--- +-- Formats tables with cycles recursively to any depth. +-- References to other tables are shown as values. +-- Self references are indicated. +-- The string returned is "Lua code", which can be processed +-- (in the case in which indent is composed by spaces or "--"). +-- Userdata and function keys and values are shown as strings, +-- which logically are exactly not equivalent to the original code. +-- This routine can serve for pretty formating tables with +-- proper indentations, apart from printing them: +-- io.write(table.show(t, "t")) -- a typical use +-- Written by Julio Manuel Fernandez-Diaz, +-- Heavily based on "Saving tables with cycles", PIL2, p. 113. +-- @param t table: is the table. +-- @param tname string: is the name of the table (optional) +-- @param top_indent string: is a first indentation (optional). +-- @return string: the pretty-printed table +function util.show_table(t: {any:any}, tname: string, top_indent: string): string + local cart: string -- a container + local autoref: string -- for self references + + local function is_empty_table(tbl: {any:any}): boolean return next(tbl) == nil end + + local function basic_serialize(o: any): string + local so = tostring(o) + if type(o) == "function" then + local info = debug and debug.getinfo(o, "S") + if not info then + return ("%q"):format(so) + end + -- info.name is nil because o is not a calling level + if info.what == "C" then + return ("%q"):format(so .. ", C function") + else + -- the information is defined through lines + return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source) + end + elseif type(o) == "number" then + return so + else + return ("%q"):format(so) + end + end + + local function add_to_cart (value: {any:any}, name: string, indent: string, saved: {any: string}, field: string) --? value: {A:B} + indent = indent or "" + saved = saved or {} + field = field or name + + cart = cart .. indent .. field + + if type(value) ~= "table" then --? 1 + cart = cart .. " = " .. basic_serialize(value) .. ";\n" + else + if saved[value] then + cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n" + autoref = autoref .. name .. " = " .. saved[value] .. ";\n" + else + saved[value] = name + if is_empty_table(value) then --? 1 + cart = cart .. " = {};\n" + else + cart = cart .. " = {\n" + for k, v in pairs(value) do + k = basic_serialize(k) + local fname = ("%s[%s]"):format(name, k) + field = ("[%s]"):format(k) + -- three spaces between levels + add_to_cart(v, fname, indent .. " ", saved, field) --! + end + cart = cart .. indent .. "};\n" + end + end + end + end + + tname = tname or "__unnamed__" + if type(t) ~= "table" then + return tname .. " = " .. basic_serialize(t) + end + cart, autoref = "", "" + add_to_cart(t, tname, top_indent) + return cart .. autoref +end + +--- Merges contents of src on top of dst's contents +-- (i.e. if an key from src already exists in dst, replace it). +-- @param dst Destination table, which will receive src's contents. +-- @param src Table which provides new contents to dst. +function util.deep_merge(dst: {integer: any}, src: {integer: any}) --? {integer: {any:any}}, {integer: {any:any}} + for k, v in pairs(src) do + if type(v) == "table" then + if dst[k] == nil then + dst[k] = {} + end + if type(dst[k]) == "table" then + util.deep_merge(dst[k], v) --! + else + dst[k] = v + end + else + dst[k] = v + end + end +end + +--- Merges contents of src below those of dst's contents +-- (i.e. if an key from src already exists in dst, do not replace it). +-- @param dst Destination table, which will receive src's contents. +-- @param src Table which provides new contents to dst. +function util.deep_merge_under(dst: {integer: any}, src: {integer: any}) --? {integer: {any:any}}, {integer: {any:any}} + for k, v in pairs(src) do + if type(v) == "table" then + if dst[k] == nil then + dst[k] = {} + end + if type(dst[k]) == "table" then + util.deep_merge_under(dst[k], v) + end + elseif dst[k] == nil then + dst[k] = v + end + end +end + +--- Clean up a path-style string ($PATH, $LUA_PATH/package.path, etc.), +-- removing repeated entries and making sure only the relevant +-- Lua version is used. +-- Example: given ("a;b;c;a;b;d", ";"), returns "a;b;c;d". +-- @param list string: A path string (from $PATH or package.path) +-- @param sep string: The separator +-- @param lua_version (optional) string: The Lua version to use. +-- @param keep_first (optional) if true, keep first occurrence in case +-- of duplicates; otherwise keep last occurrence. The default is false. +function util.cleanup_path(list: string, sep: string, lua_version: string, keep_first: boolean): string + assert(type(list) == "string") --? + assert(type(sep) == "string") --? + + list = list:gsub(dir_sep, "/") + + local parts = util.split_string(list, sep) --! + local final, entries = {}, {} + local start, stop, step: number, number, number + + if keep_first then + start, stop, step = 1, #parts, 1 + else + start, stop, step = #parts, 1, -1 + end + + for i = start, stop, step do + local part = parts[i]:gsub("//", "/") + if lua_version then + part = part:gsub("/lua/([%d.]+)/", function(part_version) + if part_version:sub(1, #lua_version) ~= lua_version then --! + return "/lua/"..lua_version.."/" --! + end + end) + end + if not entries[part] then + local at = keep_first and #final+1 or 1 + table.insert(final, at, part) + entries[part] = true + end + end + + return (table.concat(final, sep):gsub("/", dir_sep)) --! +end + +-- from http://lua-users.org/wiki/SplitJoin +-- by Philippe Lhoste +function util.split_string(str: string, delim: string, maxNb: number): {string} + -- Eliminate bad cases... + if string.find(str, delim) == nil then + return { str } + end + if maxNb == nil or maxNb < 1 then + maxNb = 0 -- No limit + end + local result = {} + local pat = "(.-)" .. delim .. "()" + local nb = 0 + local lastPos: any + for part, pos in string.gmatch(str, pat) do --! pos: number or string + nb = nb + 1 + result[nb] = part + lastPos = pos --! number or string + if nb == maxNb then break end + end + -- Handle the last field + if nb ~= maxNb then + result[nb + 1] = string.sub(str, lastPos) + end + return result +end + +--- Return an array of keys of a table. +-- @param tbl table: The input table. +-- @return table: The array of keys. +function util.keys(tbl: {any:any}): {any} + local ks = {} + for k,_ in pairs(tbl) do + table.insert(ks, k) + end + return ks +end + +--- Print a line to standard error +function util.printerr(...: string | number) + io.stderr:write(table.concat({...},"\t")) + io.stderr:write("\n") +end + +--- Display a warning message. +-- @param msg string: the warning message +function util.warning(msg: string) + util.printerr("Warning: "..msg) +end + +--- Simple sort function used as a default for util.sortedpairs. +local function default_sort(a: any, b: any): boolean + local ta = type(a) + local tb = type(b) + if ta == "number" and tb == "number" then + return tonumber(a) < tonumber(b) + elseif ta == "number" then + return true + elseif tb == "number" then + return false + else + return tostring(a) < tostring(b) + end +end + +--- A table iterator generator that returns elements sorted by key, +-- to be used in "for" loops. +-- @param tbl table: The table to be iterated. +-- @param sort_function function or table or nil: An optional comparison function +-- to be used by table.sort when sorting keys, or an array listing an explicit order +-- for keys. If a value itself is an array, it is taken so that the first element +-- is a string representing the field name, and the second element is a priority table +-- for that key, which is returned by the iterator as the third value after the key +-- and the value. +-- @return function: the iterator function. +function util.sortedpairs(tbl: {any: any}, sort_function: function(any, any): boolean | {any:any} | nil): function --? function(A, A): boolean | {any:integer} | nil + sort_function = sort_function or default_sort + local keys = util.keys(tbl) + local sub_orders = {} + + if type(sort_function) == "function" then + table.sort(keys, sort_function) --! + else + local order = sort_function + local ordered_keys = {} + local all_keys = keys + keys = {} + + for _, order_entry in ipairs(order) do --! + local key, sub_order: any, any --! + if type(order_entry) == "table" then + key = order_entry[1] --! + sub_order = order_entry[2] --! + else + key = order_entry + end + + if tbl[key] then + ordered_keys[key] = true + sub_orders[key] = sub_order + table.insert(keys, key) + end + end + + table.sort(all_keys, default_sort) + for _, key in ipairs(all_keys) do + if not ordered_keys[key] then + table.insert(keys, key) + end + end + end + + local i = 1 + return function() + local key = keys[i] + i = i + 1 + return key, tbl[key], sub_orders[key] --! + end +end + +return util + 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 @@ + +local vers = {} + +local util = require("luarocks.core.util") +local require = nil +-------------------------------------------------------------------------------- + +local deltas = { + dev = 120000000, + scm = 110000000, + cvs = 100000000, + rc = -1000, + pre = -10000, + beta = -100000, + alpha = -1000000 +} + +local version_mt = { + --- Equality comparison for versions. + -- All version numbers must be equal. + -- If both versions have revision numbers, they must be equal; + -- otherwise the revision number is ignored. + -- @param v1 table: version table to compare. + -- @param v2 table: version table to compare. + -- @return boolean: true if they are considered equivalent. + __eq = function(v1, v2) + if #v1 ~= #v2 then + return false + end + for i = 1, #v1 do + if v1[i] ~= v2[i] then + return false + end + end + if v1.revision and v2.revision then + return (v1.revision == v2.revision) + end + return true + end, + --- Size comparison for versions. + -- All version numbers are compared. + -- If both versions have revision numbers, they are compared; + -- otherwise the revision number is ignored. + -- @param v1 table: version table to compare. + -- @param v2 table: version table to compare. + -- @return boolean: true if v1 is considered lower than v2. + __lt = function(v1, v2) + for i = 1, math.max(#v1, #v2) do + local v1i, v2i = v1[i] or 0, v2[i] or 0 + if v1i ~= v2i then + return (v1i < v2i) + end + end + if v1.revision and v2.revision then + return (v1.revision < v2.revision) + end + return false + end, + -- @param v1 table: version table to compare. + -- @param v2 table: version table to compare. + -- @return boolean: true if v1 is considered lower than or equal to v2. + __le = function(v1, v2) + return not (v2 < v1) -- luacheck: ignore + end, + --- Return version as a string. + -- @param v The version table. + -- @return The string representation. + __tostring = function(v) + return v.string + end, +} + +local version_cache = {} +setmetatable(version_cache, { + __mode = "kv" +}) + +--- Parse a version string, converting to table format. +-- A version table contains all components of the version string +-- converted to numeric format, stored in the array part of the table. +-- If the version contains a revision, it is stored numerically +-- in the 'revision' field. The original string representation of +-- the string is preserved in the 'string' field. +-- Returned version tables use a metatable +-- allowing later comparison through relational operators. +-- @param vstring string: A version number in string format. +-- @return table or nil: A version table or nil +-- if the input string contains invalid characters. +function vers.parse_version(vstring) + if not vstring then return nil end + assert(type(vstring) == "string") + + local cached = version_cache[vstring] + if cached then + return cached + end + + local version = {} + local i = 1 + + local function add_token(number) + version[i] = version[i] and version[i] + number/100000 or number + i = i + 1 + end + + -- trim leading and trailing spaces + local v = vstring:match("^%s*(.*)%s*$") + version.string = v + -- store revision separately if any + local main, revision = v:match("(.*)%-(%d+)$") + if revision then + v = main + version.revision = tonumber(revision) + end + while #v > 0 do + -- extract a number + local token, rest = v:match("^(%d+)[%.%-%_]*(.*)") + if token then + add_token(tonumber(token)) + else + -- extract a word + token, rest = v:match("^(%a+)[%.%-%_]*(.*)") + if not token then + util.warning("version number '"..v.."' could not be parsed.") + version[i] = 0 + break + end + version[i] = deltas[token] or (token:byte() / 1000) + end + v = rest + end + setmetatable(version, version_mt) + version_cache[vstring] = version + return version +end + +--- Utility function to compare version numbers given as strings. +-- @param a string: one version. +-- @param b string: another version. +-- @return boolean: True if a > b. +function vers.compare_versions(a, b) + if a == b then + return false + end + return vers.parse_version(a) > vers.parse_version(b) +end + +--- A more lenient check for equivalence between versions. +-- This returns true if the requested components of a version +-- match and ignore the ones that were not given. For example, +-- when requesting "2", then "2", "2.1", "2.3.5-9"... all match. +-- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2" +-- doesn't. +-- @param version string or table: Version to be tested; may be +-- in string format or already parsed into a table. +-- @param requested string or table: Version requested; may be +-- in string format or already parsed into a table. +-- @return boolean: True if the tested version matches the requested +-- version, false otherwise. +local function partial_match(version, requested) + assert(type(version) == "string" or type(version) == "table") + assert(type(requested) == "string" or type(version) == "table") + + if type(version) ~= "table" then version = vers.parse_version(version) end + if type(requested) ~= "table" then requested = vers.parse_version(requested) end + if not version or not requested then return false end + + for i, ri in ipairs(requested) do + local vi = version[i] or 0 + if ri ~= vi then return false end + end + if requested.revision then + return requested.revision == version.revision + end + return true +end + +--- Check if a version satisfies a set of constraints. +-- @param version table: A version in table format +-- @param constraints table: An array of constraints in table format. +-- @return boolean: True if version satisfies all constraints, +-- false otherwise. +function vers.match_constraints(version, constraints) + assert(type(version) == "table") + assert(type(constraints) == "table") + local ok = true + setmetatable(version, version_mt) + for _, constr in pairs(constraints) do + if type(constr.version) == "string" then + constr.version = vers.parse_version(constr.version) + end + local constr_version, constr_op = constr.version, constr.op + setmetatable(constr_version, version_mt) + if constr_op == "==" then ok = version == constr_version + elseif constr_op == "~=" then ok = version ~= constr_version + elseif constr_op == ">" then ok = version > constr_version + elseif constr_op == "<" then ok = version < constr_version + elseif constr_op == ">=" then ok = version >= constr_version + elseif constr_op == "<=" then ok = version <= constr_version + elseif constr_op == "~>" then ok = partial_match(version, constr_version) + end + if not ok then break end + end + return ok +end + +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 @@ - +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 local vers = {} local util = require("luarocks.core.util") local require = nil --------------------------------------------------------------------------------- + local deltas = { - dev = 120000000, - scm = 110000000, - cvs = 100000000, - rc = -1000, - pre = -10000, - beta = -100000, - alpha = -1000000 + dev = 120000000, + scm = 110000000, + cvs = 100000000, + rc = -1000, + pre = -10000, + beta = -100000, + alpha = -1000000, } + + + + + + local version_mt = { - --- Equality comparison for versions. - -- All version numbers must be equal. - -- If both versions have revision numbers, they must be equal; - -- otherwise the revision number is ignored. - -- @param v1 table: version table to compare. - -- @param v2 table: version table to compare. - -- @return boolean: true if they are considered equivalent. + + + + + + + __eq = function(v1, v2) if #v1 ~= #v2 then return false @@ -37,13 +43,13 @@ local version_mt = { end return true end, - --- Size comparison for versions. - -- All version numbers are compared. - -- If both versions have revision numbers, they are compared; - -- otherwise the revision number is ignored. - -- @param v1 table: version table to compare. - -- @param v2 table: version table to compare. - -- @return boolean: true if v1 is considered lower than v2. + + + + + + + __lt = function(v1, v2) for i = 1, math.max(#v1, #v2) do local v1i, v2i = v1[i] or 0, v2[i] or 0 @@ -56,15 +62,15 @@ local version_mt = { end return false end, - -- @param v1 table: version table to compare. - -- @param v2 table: version table to compare. - -- @return boolean: true if v1 is considered lower than or equal to v2. + + + __le = function(v1, v2) - return not (v2 < v1) -- luacheck: ignore + return not (v2 < v1) end, - --- Return version as a string. - -- @param v The version table. - -- @return The string representation. + + + __tostring = function(v) return v.string end, @@ -72,20 +78,20 @@ local version_mt = { local version_cache = {} setmetatable(version_cache, { - __mode = "kv" + __mode = "kv", }) ---- Parse a version string, converting to table format. --- A version table contains all components of the version string --- converted to numeric format, stored in the array part of the table. --- If the version contains a revision, it is stored numerically --- in the 'revision' field. The original string representation of --- the string is preserved in the 'string' field. --- Returned version tables use a metatable --- allowing later comparison through relational operators. --- @param vstring string: A version number in string format. --- @return table or nil: A version table or nil --- if the input string contains invalid characters. + + + + + + + + + + + function vers.parse_version(vstring) if not vstring then return nil end assert(type(vstring) == "string") @@ -99,29 +105,29 @@ function vers.parse_version(vstring) local i = 1 local function add_token(number) - version[i] = version[i] and version[i] + number/100000 or number + version[i] = version[i] and version[i] + number / 100000 or number i = i + 1 end - -- trim leading and trailing spaces + local v = vstring:match("^%s*(.*)%s*$") version.string = v - -- store revision separately if any + local main, revision = v:match("(.*)%-(%d+)$") if revision then v = main version.revision = tonumber(revision) end while #v > 0 do - -- extract a number + local token, rest = v:match("^(%d+)[%.%-%_]*(.*)") if token then add_token(tonumber(token)) else - -- extract a word + token, rest = v:match("^(%a+)[%.%-%_]*(.*)") if not token then - util.warning("version number '"..v.."' could not be parsed.") + util.warning("version number '" .. v .. "' could not be parsed.") version[i] = 0 break end @@ -134,10 +140,10 @@ function vers.parse_version(vstring) return version end ---- Utility function to compare version numbers given as strings. --- @param a string: one version. --- @param b string: another version. --- @return boolean: True if a > b. + + + + function vers.compare_versions(a, b) if a == b then return false @@ -145,24 +151,24 @@ function vers.compare_versions(a, b) return vers.parse_version(a) > vers.parse_version(b) end ---- A more lenient check for equivalence between versions. --- This returns true if the requested components of a version --- match and ignore the ones that were not given. For example, --- when requesting "2", then "2", "2.1", "2.3.5-9"... all match. --- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2" --- doesn't. --- @param version string or table: Version to be tested; may be --- in string format or already parsed into a table. --- @param requested string or table: Version requested; may be --- in string format or already parsed into a table. --- @return boolean: True if the tested version matches the requested --- version, false otherwise. -local function partial_match(version, requested) - assert(type(version) == "string" or type(version) == "table") - assert(type(requested) == "string" or type(version) == "table") - - if type(version) ~= "table" then version = vers.parse_version(version) end - if type(requested) ~= "table" then requested = vers.parse_version(requested) end + + + + + + + + + + + + +local function partial_match(version_for_parse, requested_for_parce) + assert(type(version_for_parse) == "string" or type(version_for_parse) == "table") + assert(type(requested_for_parce) == "string" or type(requested_for_parce) == "table") + + if type(version_for_parse) ~= "table" then local version = vers.parse_version(version_for_parse) end + if type(requested_for_parce) ~= "table" then local requested = vers.parse_version(requested_for_parce) end if not version or not requested then return false end for i, ri in ipairs(requested) do @@ -175,11 +181,11 @@ local function partial_match(version, requested) return true end ---- Check if a version satisfies a set of constraints. --- @param version table: A version in table format --- @param constraints table: An array of constraints in table format. --- @return boolean: True if version satisfies all constraints, --- false otherwise. + + + + + function vers.match_constraints(version, constraints) assert(type(version) == "table") assert(type(constraints) == "table") @@ -191,10 +197,10 @@ function vers.match_constraints(version, constraints) end local constr_version, constr_op = constr.version, constr.op setmetatable(constr_version, version_mt) - if constr_op == "==" then ok = version == constr_version + if constr_op == "==" then ok = version == constr_version elseif constr_op == "~=" then ok = version ~= constr_version - elseif constr_op == ">" then ok = version > constr_version - elseif constr_op == "<" then ok = version < constr_version + elseif constr_op == ">" then ok = version > constr_version + elseif constr_op == "<" then ok = version < constr_version elseif constr_op == ">=" then ok = version >= constr_version elseif constr_op == "<=" then ok = version <= constr_version 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 @@ + +local vers = {} + +local util = require("luarocks.core.util") --! +local require = nil --! +-------------------------------------------------------------------------------- + +local deltas = { + dev = 120000000, + scm = 110000000, + cvs = 100000000, + rc = -1000, + pre = -10000, + beta = -100000, + alpha = -1000000 +} + +local record Version --? + string: string + revision: number + number: {number} +end + +local version_mt = { + --- Equality comparison for versions. + -- All version numbers must be equal. + -- If both versions have revision numbers, they must be equal; + -- otherwise the revision number is ignored. + -- @param v1 table: version table to compare. + -- @param v2 table: version table to compare. + -- @return boolean: true if they are considered equivalent. + __eq = function(v1: Version, v2: Version): boolean + if #v1 ~= #v2 then + return false + end + for i = 1, #v1 do + if v1[i] ~= v2[i] then + return false + end + end + if v1.revision and v2.revision then + return (v1.revision == v2.revision) + end + return true + end, + --- Size comparison for versions. + -- All version numbers are compared. + -- If both versions have revision numbers, they are compared; + -- otherwise the revision number is ignored. + -- @param v1 table: version table to compare. + -- @param v2 table: version table to compare. + -- @return boolean: true if v1 is considered lower than v2. + __lt = function(v1: Version, v2: Version): boolean + for i = 1, math.max(#v1, #v2) do + local v1i, v2i = v1[i] or 0, v2[i] or 0 + if v1i ~= v2i then + return (v1i < v2i) + end + end + if v1.revision and v2.revision then + return (v1.revision < v2.revision) + end + return false + end, + -- @param v1 table: version table to compare. + -- @param v2 table: version table to compare. + -- @return boolean: true if v1 is considered lower than or equal to v2. + __le = function(v1: Version, v2: Version): boolean + return not (v2 < v1) -- luacheck: ignore + end, + --- Return version as a string. + -- @param v The version table. + -- @return The string representation. + __tostring = function(v: Version): string + return v.string + end, +} + +local version_cache: {string: Version} = {} +setmetatable(version_cache, { + __mode = "kv" +}) + +--- Parse a version string, converting to table format. +-- A version table contains all components of the version string +-- converted to numeric format, stored in the array part of the table. +-- If the version contains a revision, it is stored numerically +-- in the 'revision' field. The original string representation of +-- the string is preserved in the 'string' field. +-- Returned version tables use a metatable +-- allowing later comparison through relational operators. +-- @param vstring string: A version number in string format. +-- @return table or nil: A version table or nil +-- if the input string contains invalid characters. +function vers.parse_version(vstring: string): Version | nil + if not vstring then return nil end --? + assert(type(vstring) == "string") --? + + local cached = version_cache[vstring] + if cached then + return cached + end + + local version: Version = {} + local i = 1 + + local function add_token(number: number) + version[i] = version[i] and version[i] + number/100000 or number --? + i = i + 1 + end + + -- trim leading and trailing spaces + local v = vstring:match("^%s*(.*)%s*$") + version.string = v + -- store revision separately if any + local main, revision = v:match("(.*)%-(%d+)$") + if revision then + v = main + version.revision = tonumber(revision) + end + while #v > 0 do + -- extract a number + local token, rest = v:match("^(%d+)[%.%-%_]*(.*)") + if token then + add_token(tonumber(token)) + else + -- extract a word + token, rest = v:match("^(%a+)[%.%-%_]*(.*)") + if not token then + util.warning("version number '"..v.."' could not be parsed.") + version[i] = 0 + break + end + version[i] = deltas[token] or (token:byte() / 1000) + end + v = rest + end + setmetatable(version, version_mt) + version_cache[vstring] = version + return version +end + +--- Utility function to compare version numbers given as strings. +-- @param a string: one version. +-- @param b string: another version. +-- @return boolean: True if a > b. +function vers.compare_versions(a: string, b: string): boolean + if a == b then + return false + end + return vers.parse_version(a) > vers.parse_version(b) +end + +--- A more lenient check for equivalence between versions. +-- This returns true if the requested components of a version +-- match and ignore the ones that were not given. For example, +-- when requesting "2", then "2", "2.1", "2.3.5-9"... all match. +-- When requesting "2.1", then "2.1", "2.1.3" match, but "2.2" +-- doesn't. +-- @param version string or table: Version to be tested; may be +-- in string format or already parsed into a table. +-- @param requested string or table: Version requested; may be +-- in string format or already parsed into a table. +-- @return boolean: True if the tested version matches the requested +-- version, false otherwise. +local function partial_match(version_for_parse: string | table, requested_for_parce: string | table): boolean + assert(type(version_for_parse) == "string" or type(version_for_parse) == "table") + 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") + + 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 + 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 + if not version or not requested then return false end + + for i, ri in ipairs(requested) do --! I am lost here + local vi = version[i] or 0 + if ri ~= vi then return false end + end + if requested.revision then + return requested.revision == version.revision + end + return true +end + +--- Check if a version satisfies a set of constraints. +-- @param version table: A version in table format +-- @param constraints table: An array of constraints in table format. +-- @return boolean: True if version satisfies all constraints, +-- false otherwise. +function vers.match_constraints(version: Version, constraints: Version): boolean + assert(type(version) == "table") + assert(type(constraints) == "table") + local ok = true + setmetatable(version, version_mt) + for _, constr in pairs(constraints) do + if type(constr.version) == "string" then + constr.version = vers.parse_version(constr.version) + end + local constr_version, constr_op = constr.version, constr.op --! op?? + setmetatable(constr_version, version_mt) + if constr_op == "==" then ok = version == constr_version + elseif constr_op == "~=" then ok = version ~= constr_version + elseif constr_op == ">" then ok = version > constr_version + elseif constr_op == "<" then ok = version < constr_version + elseif constr_op == ">=" then ok = version >= constr_version + elseif constr_op == "<=" then ok = version <= constr_version + elseif constr_op == "~>" then ok = partial_match(version, constr_version) + end + if not ok then break end + end + return ok +end + +return vers -- cgit v1.2.3-55-g6feb