aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorV1K1NGbg <victor@ilchev.com>2024-06-09 01:37:30 +0200
committerV1K1NGbg <victor@ilchev.com>2024-08-05 20:49:17 +0300
commit2377e668c424f77f90a577f42608eccfdfaca07b (patch)
tree4e57d5b059458b2b28d2111878b215fbd6e62aa0
parentd300c11c6acba4390fdde9030a45b0a31e7501d1 (diff)
downloadluarocks-2377e668c424f77f90a577f42608eccfdfaca07b.tar.gz
luarocks-2377e668c424f77f90a577f42608eccfdfaca07b.tar.bz2
luarocks-2377e668c424f77f90a577f42608eccfdfaca07b.zip
Half of core halfway there
-rw-r--r--src/luarocks/core/persist-original.lua81
-rw-r--r--src/luarocks/core/persist.lua59
-rw-r--r--src/luarocks/core/persist.tl81
-rw-r--r--src/luarocks/core/sysdetect-original.lua419
-rw-r--r--src/luarocks/core/sysdetect.lua214
-rw-r--r--src/luarocks/core/sysdetect.tl509
-rw-r--r--src/luarocks/core/util-original.lua322
-rw-r--r--src/luarocks/core/util.lua161
-rw-r--r--src/luarocks/core/util.tl322
-rw-r--r--src/luarocks/core/vers-original.lua207
-rw-r--r--src/luarocks/core/vers.lua162
-rw-r--r--src/luarocks/core/vers.tl213
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
2local persist = {}
3
4local 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.
13function 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
44end
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.
56function 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
78end
79
80return 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 1local _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
2local persist = {} 2local persist = {}
3 3
4local 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
13function persist.run_file(filename, env) 11function 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
44end 42end
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
56function persist.load_into_table(filename, tbl) 54function 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)
78end 76end
79 77
80return persist 78return 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
2local persist = {}
3
4local 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.
13function 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
44end
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.
56function 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
78end
79
80return 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
7local sysdetect = {}
8
9local function hex(s)
10 return s:gsub("$(..)", function(x)
11 return string.char(tonumber(x, 16))
12 end)
13end
14
15local 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()
25end
26
27local LITTLE = 1
28-- local BIG = 2
29
30local 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
42end
43
44local 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)
54end
55
56local function read_int32le(fd)
57 return read(fd, 4, LITTLE)
58end
59
60--------------------------------------------------------------------------------
61-- @section ELF
62--------------------------------------------------------------------------------
63
64local 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
77local 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
97local SHT_NOTE = 7
98
99local 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
136end
137
138local 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
198end
199
200local 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
243end
244
245local 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
253end
254
255--------------------------------------------------------------------------------
256-- @section Mach Objects (Apple)
257--------------------------------------------------------------------------------
258
259local mach_l64 = {
260 [7] = "x86_64",
261 [12] = "aarch64",
262}
263
264local mach_b64 = {
265 [0] = "ppc64",
266}
267
268local mach_l32 = {
269 [7] = "x86",
270 [12] = "arm",
271}
272
273local mach_b32 = {
274 [0] = "ppc",
275}
276
277local 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
305end
306
307--------------------------------------------------------------------------------
308-- @section PE (Windows)
309--------------------------------------------------------------------------------
310
311local pe_machine = {
312 [0x8664] = "x86_64",
313 [0x01c0] = "arm",
314 [0x01c4] = "armv7l",
315 [0xaa64] = "arm64",
316 [0x014c] = "x86",
317}
318
319local 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"
341end
342
343--------------------------------------------------------------------------------
344-- @section API
345--------------------------------------------------------------------------------
346
347function 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)
361end
362
363local cache_system
364local cache_processor
365
366function 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
417end
418
419return 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. 1local _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
7local sysdetect = {} 7local 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
9local function hex(s) 59local 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))
13end 63end
14 64
15local function read_int8(fd) 65local function read_int8(fd)
@@ -24,18 +74,15 @@ local function read_int8(fd)
24 return s:byte() 74 return s:byte()
25end 75end
26 76
27local LITTLE = 1
28-- local BIG = 2
29
30local function bytes2number(s, endian) 77local 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)
54end 101end
55 102
56local function read_int32le(fd) 103local function read_int32le(fd)
57 return read(fd, 4, LITTLE) 104 return read(fd, 4, "little")
58end 105end
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
148local endians = {
149 [0x01] = "little",
150 [0x02] = "big",
151}
63 152
64local e_osabi = { 153local e_osabi = {
65 [0x00] = "sysv", 154 [0x00] = "sysv",
@@ -97,11 +186,12 @@ local e_machines = {
97local SHT_NOTE = 7 186local SHT_NOTE = 7
98 187
99local function read_elf_section_headers(fd, hdr) 188local 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
138local function detect_elf_system(fd, hdr, sections) 228local 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
253end 343end
254 344
255-------------------------------------------------------------------------------- 345
256-- @section Mach Objects (Apple) 346
257-------------------------------------------------------------------------------- 347
258 348
259local mach_l64 = { 349local 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
305end 395end
306 396
307-------------------------------------------------------------------------------- 397
308-- @section PE (Windows) 398
309-------------------------------------------------------------------------------- 399
310 400
311local pe_machine = { 401local 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
319local function detect_pe(fd) 409local 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"
341end 431end
342 432
343-------------------------------------------------------------------------------- 433
344-- @section API 434
345-------------------------------------------------------------------------------- 435
346 436
347function sysdetect.detect_file(file) 437function sysdetect.detect_file(file)
348 assert(type(file) == "string") 438 assert(type(file) == "string")
@@ -364,7 +454,7 @@ local cache_system
364local cache_processor 454local cache_processor
365 455
366function sysdetect.detect(input_file) 456function 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
7local 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
49end
50
51local type System = sysdetect.System --? ?
52local type Processor = sysdetect.Processor --? ?
53
54local enum Endian
55 "little"
56 "big"
57end
58
59local function hex(s: string): string
60 return (s:gsub("$(..)", function(x: string): string
61 return string.char(tonumber(x, 16))
62 end))
63end
64
65local 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()
75end
76
77local 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
89end
90
91local 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)
101end
102
103local function read_int32le(fd: FILE): integer
104 return read(fd, 4, "little")
105end
106
107--------------------------------------------------------------------------------
108-- @section ELF
109--------------------------------------------------------------------------------
110
111local 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
128end
129
130local 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
146end
147
148local endians: {integer:Endian} = {
149 [0x01] = "little",
150 [0x02] = "big",
151}
152
153local 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
166local 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
186local SHT_NOTE <const> = 7
187
188local 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
226end
227
228local 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
288end
289
290local 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
333end
334
335local 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
343end
344
345--------------------------------------------------------------------------------
346-- @section Mach Objects (Apple)
347--------------------------------------------------------------------------------
348
349local mach_l64: {integer:Processor} = {
350 [7] = "x86_64",
351 [12] = "aarch64",
352}
353
354local mach_b64: {integer:Processor} = {
355 [0] = "ppc64",
356}
357
358local mach_l32: {integer:Processor} = {
359 [7] = "x86",
360 [12] = "arm",
361}
362
363local mach_b32: {integer:Processor} = {
364 [0] = "ppc",
365}
366
367local 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
395end
396
397--------------------------------------------------------------------------------
398-- @section PE (Windows)
399--------------------------------------------------------------------------------
400
401local pe_machine = {
402 [0x8664] = "x86_64",
403 [0x01c0] = "arm",
404 [0x01c4] = "armv7l",
405 [0xaa64] = "arm64",
406 [0x014c] = "x86",
407}
408
409local 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"
431end
432
433--------------------------------------------------------------------------------
434-- @section API
435--------------------------------------------------------------------------------
436
437function 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)
451end
452
453local cache_system: System
454local cache_processor: Processor
455
456function 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
507end
508
509return 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
2local util = {}
3
4local require = nil
5--------------------------------------------------------------------------------
6
7local 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.
16function 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 ""
30end
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
49function 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
115end
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.
121function 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
136end
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.
142function 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
155end
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.
166function 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))
199end
200
201-- from http://lua-users.org/wiki/SplitJoin
202-- by Philippe Lhoste
203function 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
226end
227
228--- Return an array of keys of a table.
229-- @param tbl table: The input table.
230-- @return table: The array of keys.
231function util.keys(tbl)
232 local ks = {}
233 for k,_ in pairs(tbl) do
234 table.insert(ks, k)
235 end
236 return ks
237end
238
239--- Print a line to standard error
240function util.printerr(...)
241 io.stderr:write(table.concat({...},"\t"))
242 io.stderr:write("\n")
243end
244
245--- Display a warning message.
246-- @param msg string: the warning message
247function util.warning(msg)
248 util.printerr("Warning: "..msg)
249end
250
251--- Simple sort function used as a default for util.sortedpairs.
252local 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
264end
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.
276function 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
319end
320
321return 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 1local _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
2local util = {} 2local util = {}
3 3
4local require = nil 4local require = nil
5-------------------------------------------------------------------------------- 5
6 6
7local dir_sep = package.config:sub(1, 1) 7local 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
16function util.popen_read(cmd, spec) 16function 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 ""
30end 30end
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
49function util.show_table(t, tname, top_indent) 49function 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
115end 115end
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
121function util.deep_merge(dst, src) 121function 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
136end 136end
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
142function util.deep_merge_under(dst, src) 142function 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
155end 155end
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
166function util.cleanup_path(list, sep, lua_version, keep_first) 166function 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))
199end 199end
200 200
201-- from http://lua-users.org/wiki/SplitJoin 201
202-- by Philippe Lhoste 202
203function util.split_string(str, delim, maxNb) 203function 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
226end 226end
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
231function util.keys(tbl) 231function 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
237end 237end
238 238
239--- Print a line to standard error 239
240function util.printerr(...) 240function 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")
243end 243end
244 244
245--- Display a warning message. 245
246-- @param msg string: the warning message 246
247function util.warning(msg) 247function util.warning(msg)
248 util.printerr("Warning: "..msg) 248 util.printerr("Warning: " .. msg)
249end 249end
250 250
251--- Simple sort function used as a default for util.sortedpairs. 251
252local function default_sort(a, b) 252local 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
264end 264end
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
276function util.sortedpairs(tbl, sort_function) 276function 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)
319end 319end
320 320
321return util 321return 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
2local util = {}
3
4local require = nil --!
5--------------------------------------------------------------------------------
6
7local 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.
16function 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 ""
30end
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
49function 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
115end
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.
121function 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
136end
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.
142function 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
155end
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.
166function 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)) --!
199end
200
201-- from http://lua-users.org/wiki/SplitJoin
202-- by Philippe Lhoste
203function 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
226end
227
228--- Return an array of keys of a table.
229-- @param tbl table: The input table.
230-- @return table: The array of keys.
231function 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
237end
238
239--- Print a line to standard error
240function util.printerr(...: string | number)
241 io.stderr:write(table.concat({...},"\t"))
242 io.stderr:write("\n")
243end
244
245--- Display a warning message.
246-- @param msg string: the warning message
247function util.warning(msg: string)
248 util.printerr("Warning: "..msg)
249end
250
251--- Simple sort function used as a default for util.sortedpairs.
252local 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
264end
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.
276function 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
319end
320
321return 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
2local vers = {}
3
4local util = require("luarocks.core.util")
5local require = nil
6--------------------------------------------------------------------------------
7
8local deltas = {
9 dev = 120000000,
10 scm = 110000000,
11 cvs = 100000000,
12 rc = -1000,
13 pre = -10000,
14 beta = -100000,
15 alpha = -1000000
16}
17
18local 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
73local version_cache = {}
74setmetatable(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.
89function 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
135end
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.
141function 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)
146end
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.
160local 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
176end
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.
183function 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
205end
206
207return 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 1local _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
2local vers = {} 2local vers = {}
3 3
4local util = require("luarocks.core.util") 4local util = require("luarocks.core.util")
5local require = nil 5local require = nil
6-------------------------------------------------------------------------------- 6
7 7
8local deltas = { 8local 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
18local version_mt = { 24local 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
73local version_cache = {} 79local version_cache = {}
74setmetatable(version_cache, { 80setmetatable(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
89function vers.parse_version(vstring) 95function 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
135end 141end
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
141function vers.compare_versions(a, b) 147function 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)
146end 152end
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
160local function partial_match(version, requested) 166local 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
176end 182end
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
183function vers.match_constraints(version, constraints) 189function 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
2local vers = {}
3
4local util <const> = require("luarocks.core.util") --!
5local require = nil --!
6--------------------------------------------------------------------------------
7
8local deltas = {
9 dev = 120000000,
10 scm = 110000000,
11 cvs = 100000000,
12 rc = -1000,
13 pre = -10000,
14 beta = -100000,
15 alpha = -1000000
16}
17
18local record Version --?
19 string: string
20 revision: number
21 number: {number}
22end
23
24local 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
79local version_cache: {string: Version} = {}
80setmetatable(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.
95function 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
141end
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.
147function 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)
152end
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.
166local 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
182end
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.
189function 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
211end
212
213return vers