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