aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorV1K1NGbg <victor@ilchev.com>2024-07-09 22:25:26 +0300
committerV1K1NGbg <victor@ilchev.com>2024-08-05 20:49:17 +0300
commit0efd878f6435c4d929c101f7c1388801096c6004 (patch)
tree2020b4024d05f57d73c27adbb1ffd28580a09b01
parent9094232f4de7827f464370d4bfae65f4dab96d98 (diff)
downloadluarocks-0efd878f6435c4d929c101f7c1388801096c6004.tar.gz
luarocks-0efd878f6435c4d929c101f7c1388801096c6004.tar.bz2
luarocks-0efd878f6435c4d929c101f7c1388801096c6004.zip
halfway commit
-rw-r--r--src/luarocks/core/path.tl2
-rw-r--r--src/luarocks/core/vers.tl2
-rw-r--r--src/luarocks/dir-original.lua (renamed from src/luarocks/dir.lua)0
-rw-r--r--src/luarocks/dir.tl64
-rw-r--r--src/luarocks/fs-original.lua (renamed from src/luarocks/fs.lua)0
-rw-r--r--src/luarocks/fs.tl151
-rw-r--r--src/luarocks/util-original.lua (renamed from src/luarocks/util.lua)0
-rw-r--r--src/luarocks/util.tl635
8 files changed, 852 insertions, 2 deletions
diff --git a/src/luarocks/core/path.tl b/src/luarocks/core/path.tl
index 35f66e0c..e1bdc8de 100644
--- a/src/luarocks/core/path.tl
+++ b/src/luarocks/core/path.tl
@@ -28,7 +28,7 @@ end
28-- @param version string: Rock version 28-- @param version string: Rock version
29-- @return string: a pathname with the same directory parts and a versioned basename. 29-- @return string: a pathname with the same directory parts and a versioned basename.
30function path.versioned_name(file: string, prefix: string, name: string, version: string): string 30function path.versioned_name(file: string, prefix: string, name: string, version: string): string
31 assert(not name:match(dir_sep)) --! 31 assert(not name:match(dir_sep))
32 32
33 local rest = file:sub(#prefix+1):gsub("^" .. dir_sep .. "*", "") 33 local rest = file:sub(#prefix+1):gsub("^" .. dir_sep .. "*", "")
34 local name_version = (name.."_"..version):gsub("%-", "_"):gsub("%.", "_") 34 local name_version = (name.."_"..version):gsub("%-", "_"):gsub("%.", "_")
diff --git a/src/luarocks/core/vers.tl b/src/luarocks/core/vers.tl
index b1dcd795..554296af 100644
--- a/src/luarocks/core/vers.tl
+++ b/src/luarocks/core/vers.tl
@@ -196,7 +196,7 @@ end
196-- @param constraints table: An array of constraints in table format. 196-- @param constraints table: An array of constraints in table format.
197-- @return boolean: True if version satisfies all constraints, 197-- @return boolean: True if version satisfies all constraints,
198-- false otherwise. 198-- false otherwise.
199function vers.match_constraints(version: Version, constraints: {Constraints}): boolean --! 199function vers.match_constraints(version: Version, constraints: {Constraints}): boolean
200 local ok = true 200 local ok = true
201 setmetatable(version, version_mt) 201 setmetatable(version, version_mt)
202 for _, constr in ipairs(constraints) do 202 for _, constr in ipairs(constraints) do
diff --git a/src/luarocks/dir.lua b/src/luarocks/dir-original.lua
index be89e37b..be89e37b 100644
--- a/src/luarocks/dir.lua
+++ b/src/luarocks/dir-original.lua
diff --git a/src/luarocks/dir.tl b/src/luarocks/dir.tl
new file mode 100644
index 00000000..86098413
--- /dev/null
+++ b/src/luarocks/dir.tl
@@ -0,0 +1,64 @@
1
2--- Generic utilities for handling pathnames.
3local record dir
4end
5
6local core = require("luarocks.core.dir")
7
8dir.path = core.path --!
9dir.split_url = core.split_url
10dir.normalize = core.normalize
11
12local dir_sep = package.config:sub(1, 1)
13
14--- Strip the path off a path+filename.
15-- @param pathname string: A path+name, such as "/a/b/c"
16-- or "\a\b\c".
17-- @return string: The filename without its path, such as "c".
18function dir.base_name(pathname: string): string
19 assert(type(pathname) == "string")
20
21 local b: string
22 b = pathname:gsub("[/\\]", "/") -- canonicalize to forward slashes
23 b = b:gsub("/*$", "") -- drop trailing slashes
24 b = b:match(".*[/\\]([^/\\]*)") -- match last component
25 b = b or pathname -- fallback to original if no slashes
26
27 return b
28end
29
30--- Strip the name off a path+filename.
31-- @param pathname string: A path+name, such as "/a/b/c".
32-- @return string: The filename without its path, such as "/a/b".
33-- For entries such as "/a/b/", "/a" is returned. If there are
34-- no directory separators in input, "" is returned.
35function dir.dir_name(pathname: string): string
36 assert(type(pathname) == "string")
37
38 local d: string
39 d = pathname:gsub("[/\\]", "/") -- canonicalize to forward slashes
40 d = d:gsub("/*$", "") -- drop trailing slashes
41 d = d:match("(.*)[/]+[^/]*") -- match all components but the last
42 d = d or "" -- switch to "" if there's no match
43 d = d:gsub("/", dir_sep) -- decanonicalize to native slashes
44
45 return d
46end
47
48--- Returns true if protocol does not require additional tools.
49-- @param protocol The protocol name
50function dir.is_basic_protocol(protocol: string): boolean --? extra type (enum)
51 return protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file"
52end
53
54function dir.deduce_base_dir(url: string): string
55 -- for extensions like foo.tar.gz, "gz" is stripped first
56 local known_exts = {}
57 for _, ext in ipairs{"zip", "git", "tgz", "tar", "gz", "bz2"} do
58 known_exts[ext] = ""
59 end
60 local base = dir.base_name(url)
61 return (base:gsub("%.([^.]*)$", known_exts):gsub("%.tar", ""))
62end
63
64return dir
diff --git a/src/luarocks/fs.lua b/src/luarocks/fs-original.lua
index a8156a21..a8156a21 100644
--- a/src/luarocks/fs.lua
+++ b/src/luarocks/fs-original.lua
diff --git a/src/luarocks/fs.tl b/src/luarocks/fs.tl
new file mode 100644
index 00000000..c31d3b5a
--- /dev/null
+++ b/src/luarocks/fs.tl
@@ -0,0 +1,151 @@
1
2--- Proxy module for filesystem and platform abstractions.
3-- All code using "fs" code should require "luarocks.fs",
4-- and not the various platform-specific implementations.
5-- However, see the documentation of the implementation
6-- for the API reference.
7
8local pairs = pairs --?
9
10local record fs
11
12end
13
14-- To avoid a loop when loading the other fs modules.
15package.loaded["luarocks.fs"] = fs
16
17local cfg = require("luarocks.core.cfg")
18
19local pack = table.pack or function(...) return { n = select("#", ...), ... } end --!
20
21math.randomseed(os.time())
22
23local fs_is_verbose = false
24
25do
26 local old_popen, old_execute: function(string, string): (FILE, string), function(string): (boolean, string, integer) --! () around output to seperate
27
28 -- patch io.popen and os.execute to display commands in verbose mode
29 function fs.verbose()
30 fs_is_verbose = true
31
32 if old_popen or old_execute then return end --? == nil!
33 old_popen = io.popen
34 -- luacheck: push globals io os
35 io.popen = function(one: string, two: string): FILE, string
36 if two == nil then
37 print("\nio.popen: ", one)
38 else
39 print("\nio.popen: ", one, "Mode:", two)
40 end
41 return old_popen(one, two)
42 end
43
44 old_execute = os.execute
45 os.execute = function(cmd: string): boolean, string, integer
46 -- redact api keys if present
47 print("\nos.execute: ", (cmd:gsub("(/api/[^/]+/)([^/]+)/", function(cap: string, key): string return cap.."<redacted>/" end)) ) --! key is unused
48 local a, b, c = old_execute(cmd)
49 if type(a) == "boolean" then
50 print((a and ".........." or "##########") .. ": " .. tostring(c) .. (b == "exit" and "" or " (" .. tostring(b) .. ")"))
51 elseif type(a) == "number" then --! "a" needs to be a boolean
52 print(((a == 0) and ".........." or "##########") .. ": " .. tostring(a))
53 end
54 return a, b, c
55 end
56 -- luacheck: pop
57 end
58end
59
60do
61 local skip_verbose_wrap = {
62 ["current_dir"] = true,
63 }
64
65 local function load_fns(fs_table, inits)
66 for name, fn in pairs(fs_table) do
67 if name ~= "init" and not fs[name] then
68 if skip_verbose_wrap[name] then
69 fs[name] = fn
70 else
71 fs[name] = function(...)
72 if fs_is_verbose then
73 local args = pack(...)
74 for i=1, args.n do
75 local arg = args[i]
76 local pok, v = pcall(string.format, "%q", arg)
77 args[i] = pok and v or tostring(arg)
78 end
79 print("fs." .. name .. "(" .. table.concat(args, ", ") .. ")")
80 end
81 return fn(...)
82 end
83 end
84 end
85 end
86 if fs_table.init then
87 table.insert(inits, fs_table.init)
88 end
89 end
90
91 local function load_platform_fns(patt, inits)
92 local each_platform = cfg.each_platform
93
94 -- FIXME A quick hack for the experimental Windows build
95 if os.getenv("LUAROCKS_CROSS_COMPILING") then
96 each_platform = function()
97 local i = 0
98 local plats = { "linux", "unix" }
99 return function()
100 i = i + 1
101 return plats[i]
102 end
103 end
104 end
105
106 for platform in each_platform("most-specific-first") do
107 local ok, fs_plat = pcall(require, patt:format(platform))
108 if ok and fs_plat then
109 load_fns(fs_plat, inits)
110 end
111 end
112 end
113
114 function fs.init()
115 local inits = {}
116
117 if fs.current_dir then
118 -- unload luarocks fs so it can be reloaded using all modules
119 -- providing extra functionality in the current package paths
120 for k, _ in pairs(fs) do
121 if k ~= "init" and k ~= "verbose" then
122 fs[k] = nil
123 end
124 end
125 for m, _ in pairs(package.loaded) do
126 if m:match("luarocks%.fs%.") then
127 package.loaded[m] = nil
128 end
129 end
130 end
131
132 -- Load platform-specific functions
133 load_platform_fns("luarocks.fs.%s", inits)
134
135 -- Load platform-independent pure-Lua functionality
136 load_fns(require("luarocks.fs.lua"), inits)
137
138 -- Load platform-specific fallbacks for missing Lua modules
139 load_platform_fns("luarocks.fs.%s.tools", inits)
140
141 -- Load platform-independent external tool functionality
142 load_fns(require("luarocks.fs.tools"), inits)
143
144 -- Run platform-specific initializations after everything is loaded
145 for _, init in ipairs(inits) do
146 init()
147 end
148 end
149end
150
151return fs
diff --git a/src/luarocks/util.lua b/src/luarocks/util-original.lua
index de9157fc..de9157fc 100644
--- a/src/luarocks/util.lua
+++ b/src/luarocks/util-original.lua
diff --git a/src/luarocks/util.tl b/src/luarocks/util.tl
new file mode 100644
index 00000000..41855aae
--- /dev/null
+++ b/src/luarocks/util.tl
@@ -0,0 +1,635 @@
1
2--- Assorted utilities for managing tables, plus a scheduler for rollback functions.
3-- Does not requires modules directly (only as locals
4-- inside specific functions) to avoid interdependencies,
5-- as this is used in the bootstrapping stage of luarocks.core.cfg.
6
7local record util
8end
9
10local core = require("luarocks.core.util")
11
12util.cleanup_path = core.cleanup_path --!
13util.split_string = core.split_string
14util.sortedpairs = core.sortedpairs
15util.deep_merge = core.deep_merge
16util.deep_merge_under = core.deep_merge_under
17util.popen_read = core.popen_read
18util.show_table = core.show_table
19util.printerr = core.printerr
20util.warning = core.warning
21util.keys = core.keys
22
23local unpack = unpack or table.unpack --!
24local pack = table.pack or function(...) return { n = select("#", ...), ... } end --!
25
26local scheduled_functions: {integer: {string: any}} = {} --? infered from line 48-51
27
28--- Schedule a function to be executed upon program termination.
29-- This is useful for actions such as deleting temporary directories
30-- or failure rollbacks.
31-- @param f function: Function to be executed.
32-- @param ... arguments to be passed to function.
33-- @return table: A token representing the scheduled execution, --? table -> map
34-- which can be used to remove the item later from the list.
35function util.schedule_function(f: function(), ...:any): {string:any}
36 assert(type(f) == "function")
37
38 local item = { fn = f, args = pack(...) }
39 table.insert(scheduled_functions, item)
40 return item
41end
42
43--- Unschedule a function.
44-- This is useful for cancelling a rollback of a completed operation.
45-- @param item table: The token representing the scheduled function that was --? table -> map
46-- returned from the schedule_function call.
47function util.remove_scheduled_function(item: {string: any})
48 for k, v in pairs(scheduled_functions) do
49 if v == item then
50 table.remove(scheduled_functions, k)
51 return
52 end
53 end
54end
55
56--- Execute scheduled functions.
57-- Some calls create temporary files and/or directories and register
58-- corresponding cleanup functions. Calling this function will run
59-- these function, erasing temporaries.
60-- Functions are executed in the inverse order they were scheduled.
61function util.run_scheduled_functions()
62 local fs = require("luarocks.fs")
63 if fs.change_dir_to_root then
64 fs.change_dir_to_root()
65 end
66 for i = #scheduled_functions, 1, -1 do
67 local item = scheduled_functions[i]
68 item.fn(unpack(item.args, 1, item.args.n))
69 end
70end
71
72--- Produce a Lua pattern that matches precisely the given string
73-- (this is suitable to be concatenating to other patterns,
74-- so it does not include beginning- and end-of-string markers (^$)
75-- @param s string: The input string
76-- @return string: The equivalent pattern
77function util.matchquote(s)
78 return (s:gsub("[?%-+*%[%].%%()$^]","%%%1"))
79end
80
81local var_format_pattern = "%$%((%a[%a%d_]+)%)"
82
83-- Check if a set of needed variables are referenced
84-- somewhere in a list of definitions, warning the user
85-- about any unused ones. Each key in needed_set should
86-- appear as a $(XYZ) variable at least once as a
87-- substring of some value of var_defs.
88-- @param var_defs: a table with string keys and string
89-- values, containing variable definitions.
90-- @param needed_set: a set where keys are the names of
91-- needed variables.
92-- @param msg string: the warning message to display.
93function util.warn_if_not_used(var_defs, needed_set, msg)
94 local seen = {}
95 for _, val in pairs(var_defs) do
96 for used in val:gmatch(var_format_pattern) do
97 seen[used] = true
98 end
99 end
100 for var, _ in pairs(needed_set) do
101 if not seen[var] then
102 util.warning(msg:format(var))
103 end
104 end
105end
106
107-- Output any entries that might remain in $(XYZ) format,
108-- warning the user that substitutions have failed.
109-- @param line string: the input string
110local function warn_failed_matches(line)
111 local any_failed = false
112 if line:match(var_format_pattern) then
113 for unmatched in line:gmatch(var_format_pattern) do
114 util.warning("unmatched variable " .. unmatched)
115 any_failed = true
116 end
117 end
118 return any_failed
119end
120
121--- Perform make-style variable substitutions on string values of a table.
122-- For every string value tbl.x which contains a substring of the format
123-- "$(XYZ)" will have this substring replaced by vars["XYZ"], if that field
124-- exists in vars. Only string values are processed; this function
125-- does not scan subtables recursively.
126-- @param tbl table: Table to have its string values modified.
127-- @param vars table: Table containing string-string key-value pairs
128-- representing variables to replace in the strings values of tbl.
129function util.variable_substitutions(tbl, vars)
130 assert(type(tbl) == "table")
131 assert(type(vars) == "table")
132
133 local updated = {}
134 for k, v in pairs(tbl) do
135 if type(v) == "string" then
136 updated[k] = v:gsub(var_format_pattern, vars)
137 if warn_failed_matches(updated[k]) then
138 updated[k] = updated[k]:gsub(var_format_pattern, "")
139 end
140 end
141 end
142 for k, v in pairs(updated) do
143 tbl[k] = v
144 end
145end
146
147function util.lua_versions(sort)
148 local versions = { "5.1", "5.2", "5.3", "5.4" }
149 local i = 0
150 if sort == "descending" then
151 i = #versions + 1
152 return function()
153 i = i - 1
154 return versions[i]
155 end
156 else
157 return function()
158 i = i + 1
159 return versions[i]
160 end
161 end
162end
163
164function util.lua_path_variables()
165 local cfg = require("luarocks.core.cfg")
166 local lpath_var = "LUA_PATH"
167 local lcpath_var = "LUA_CPATH"
168
169 local lv = cfg.lua_version:gsub("%.", "_")
170 if lv ~= "5_1" then
171 if os.getenv("LUA_PATH_" .. lv) then
172 lpath_var = "LUA_PATH_" .. lv
173 end
174 if os.getenv("LUA_CPATH_" .. lv) then
175 lcpath_var = "LUA_CPATH_" .. lv
176 end
177 end
178 return lpath_var, lcpath_var
179end
180
181function util.starts_with(s, prefix)
182 return s:sub(1,#prefix) == prefix
183end
184
185--- Print a line to standard output
186function util.printout(...)
187 io.stdout:write(table.concat({...},"\t"))
188 io.stdout:write("\n")
189end
190
191function util.title(msg, porcelain, underline)
192 if porcelain then return end
193 util.printout()
194 util.printout(msg)
195 util.printout((underline or "-"):rep(#msg))
196 util.printout()
197end
198
199function util.this_program(default)
200 local i = 1
201 local last, cur = default, default
202 while i do
203 local dbg = debug and debug.getinfo(i,"S")
204 if not dbg then break end
205 last = cur
206 cur = dbg.source
207 i=i+1
208 end
209 local prog = last:sub(1,1) == "@" and last:sub(2) or last
210
211 -- Check if we found the true path of a script that has a wrapper
212 local lrdir, binpath = prog:match("^(.*)/lib/luarocks/rocks%-[0-9.]*/[^/]+/[^/]+(/bin/[^/]+)$")
213 if lrdir then
214 -- Return the wrapper instead
215 return lrdir .. binpath
216 end
217
218 return prog
219end
220
221function util.format_rock_name(name, namespace, version)
222 return (namespace and namespace.."/" or "")..name..(version and " "..version or "")
223end
224
225function util.deps_mode_option(parser, program)
226 local cfg = require("luarocks.core.cfg")
227
228 parser:option("--deps-mode", "How to handle dependencies. Four modes are supported:\n"..
229 "* all - use all trees from the rocks_trees list for finding dependencies\n"..
230 "* one - use only the current tree (possibly set with --tree)\n"..
231 "* order - use trees based on order (use the current tree and all "..
232 "trees below it on the rocks_trees list)\n"..
233 "* none - ignore dependencies altogether.\n"..
234 "The default mode may be set with the deps_mode entry in the configuration file.\n"..
235 'The current default is "'..cfg.deps_mode..'".\n'..
236 "Type '"..util.this_program(program or "luarocks").."' with no "..
237 "arguments to see your list of rocks trees.")
238 :argname("<mode>")
239 :choices({"all", "one", "order", "none"})
240 parser:flag("--nodeps"):hidden(true)
241end
242
243function util.see_help(command, program)
244 return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'."
245end
246
247function util.see_also(text)
248 local see_also = "See also:\n"
249 if text then
250 see_also = see_also..text.."\n"
251 end
252 return see_also.." '"..util.this_program("luarocks").." help' for general options and configuration."
253end
254
255function util.announce_install(rockspec)
256 local cfg = require("luarocks.core.cfg")
257 local path = require("luarocks.path")
258
259 local suffix = ""
260 if rockspec.description and rockspec.description.license then
261 suffix = " (license: "..rockspec.description.license..")"
262 end
263
264 util.printout(rockspec.name.." "..rockspec.version.." is now installed in "..path.root_dir(cfg.root_dir)..suffix)
265 util.printout()
266end
267
268--- Collect rockspecs located in a subdirectory.
269-- @param versions table: A table mapping rock names to newest rockspec versions.
270-- @param paths table: A table mapping rock names to newest rockspec paths.
271-- @param unnamed_paths table: An array of rockspec paths that don't contain rock
272-- name and version in regular format.
273-- @param subdir string: path to subdirectory.
274local function collect_rockspecs(versions, paths, unnamed_paths, subdir)
275 local fs = require("luarocks.fs")
276 local dir = require("luarocks.dir")
277 local path = require("luarocks.path")
278 local vers = require("luarocks.core.vers")
279
280 if fs.is_dir(subdir) then
281 for file in fs.dir(subdir) do
282 file = dir.path(subdir, file)
283
284 if file:match("rockspec$") and fs.is_file(file) then
285 local rock, version = path.parse_name(file)
286
287 if rock then
288 if not versions[rock] or vers.compare_versions(version, versions[rock]) then
289 versions[rock] = version
290 paths[rock] = file
291 end
292 else
293 table.insert(unnamed_paths, file)
294 end
295 end
296 end
297 end
298end
299
300--- Get default rockspec name for commands that take optional rockspec name.
301-- @return string or (nil, string): path to the rockspec or nil and error message.
302function util.get_default_rockspec()
303 local versions, paths, unnamed_paths = {}, {}, {}
304 -- Look for rockspecs in some common locations.
305 collect_rockspecs(versions, paths, unnamed_paths, ".")
306 collect_rockspecs(versions, paths, unnamed_paths, "rockspec")
307 collect_rockspecs(versions, paths, unnamed_paths, "rockspecs")
308
309 if #unnamed_paths > 0 then
310 -- There are rockspecs not following "name-version.rockspec" format.
311 -- More than one are ambiguous.
312 if #unnamed_paths > 1 then
313 return nil, "Please specify which rockspec file to use."
314 else
315 return unnamed_paths[1]
316 end
317 else
318 local fs = require("luarocks.fs")
319 local dir = require("luarocks.dir")
320 local basename = dir.base_name(fs.current_dir())
321
322 if paths[basename] then
323 return paths[basename]
324 end
325
326 local rock = next(versions)
327
328 if rock then
329 -- If there are rockspecs for multiple rocks it's ambiguous.
330 if next(versions, rock) then
331 return nil, "Please specify which rockspec file to use."
332 else
333 return paths[rock]
334 end
335 else
336 return nil, "Argument missing: please specify a rockspec to use on current directory."
337 end
338 end
339end
340
341-- Quote Lua string, analogous to fs.Q.
342-- @param s A string, such as "hello"
343-- @return string: A quoted string, such as '"hello"'
344function util.LQ(s)
345 return ("%q"):format(s)
346end
347
348-- Split name and namespace of a package name.
349-- @param ns_name a name that may be in "namespace/name" format
350-- @return string, string? - name and optionally a namespace
351function util.split_namespace(ns_name)
352 local p1, p2 = ns_name:match("^([^/]+)/([^/]+)$")
353 if p1 then
354 return p2, p1
355 end
356 return ns_name
357end
358
359--- Argparse action callback for namespaced rock arguments.
360function util.namespaced_name_action(args, target, ns_name)
361 assert(type(args) == "table")
362 assert(type(target) == "string")
363 assert(type(ns_name) == "string" or not ns_name)
364
365 if not ns_name then
366 return
367 end
368
369 if ns_name:match("%.rockspec$") or ns_name:match("%.rock$") then
370 args[target] = ns_name
371 else
372 local name, namespace = util.split_namespace(ns_name)
373 args[target] = name:lower()
374 if namespace then
375 args.namespace = namespace:lower()
376 end
377 end
378end
379
380function util.deep_copy(tbl)
381 local copy = {}
382 for k, v in pairs(tbl) do
383 if type(v) == "table" then
384 copy[k] = util.deep_copy(v)
385 else
386 copy[k] = v
387 end
388 end
389 return copy
390end
391
392-- A portable version of fs.exists that can be used at early startup,
393-- before the platform has been determined and luarocks.fs has been
394-- initialized.
395function util.exists(file)
396 local fd, _, code = io.open(file, "r")
397 if code == 13 then
398 -- code 13 means "Permission denied" on both Unix and Windows
399 -- io.open on folders always fails with code 13 on Windows
400 return true
401 end
402 if fd then
403 fd:close()
404 return true
405 end
406 return false
407end
408
409do
410 local function Q(pathname)
411 if pathname:match("^.:") then
412 return pathname:sub(1, 2) .. '"' .. pathname:sub(3) .. '"'
413 end
414 return '"' .. pathname .. '"'
415 end
416
417 function util.check_lua_version(lua, luaver)
418 if not util.exists(lua) then
419 return nil
420 end
421 local lv, err = util.popen_read(Q(lua) .. ' -e "io.write(_VERSION:sub(5))"')
422 if lv == "" then
423 return nil
424 end
425 if luaver and luaver ~= lv then
426 return nil
427 end
428 return lv
429 end
430
431 function util.get_luajit_version()
432 local cfg = require("luarocks.core.cfg")
433 if cfg.cache.luajit_version_checked then
434 return cfg.cache.luajit_version
435 end
436 cfg.cache.luajit_version_checked = true
437
438 if not cfg.variables.LUA then
439 return nil
440 end
441
442 local ljv
443 if cfg.lua_version == "5.1" then
444 -- Ignores extra version info for custom builds, e.g. "LuaJIT 2.1.0-beta3 some-other-version-info"
445 ljv = util.popen_read(Q(cfg.variables.LUA) .. ' -e "io.write(tostring(jit and jit.version:gsub([[^%S+ (%S+).*]], [[%1]])))"')
446 if ljv == "nil" then
447 ljv = nil
448 end
449 end
450 cfg.cache.luajit_version = ljv
451 return ljv
452 end
453
454 local find_lua_bindir
455 do
456 local exe_suffix = (package.config:sub(1, 1) == "\\" and ".exe" or "")
457
458 local function insert_lua_variants(names, luaver)
459 local variants = {
460 "lua" .. luaver .. exe_suffix,
461 "lua" .. luaver:gsub("%.", "") .. exe_suffix,
462 "lua-" .. luaver .. exe_suffix,
463 "lua-" .. luaver:gsub("%.", "") .. exe_suffix,
464 }
465 for _, name in ipairs(variants) do
466 names[name] = luaver
467 table.insert(names, name)
468 end
469 end
470
471 find_lua_bindir = function(prefix, luaver, verbose)
472 local names = {}
473 if luaver then
474 insert_lua_variants(names, luaver)
475 else
476 for v in util.lua_versions("descending") do
477 insert_lua_variants(names, v)
478 end
479 end
480 if luaver == "5.1" or not luaver then
481 table.insert(names, "luajit" .. exe_suffix)
482 end
483 table.insert(names, "lua" .. exe_suffix)
484
485 local tried = {}
486 local dir_sep = package.config:sub(1, 1)
487 for _, d in ipairs({ prefix .. dir_sep .. "bin", prefix }) do
488 for _, name in ipairs(names) do
489 local lua = d .. dir_sep .. name
490 local is_wrapper, err = util.lua_is_wrapper(lua)
491 if is_wrapper == false then
492 local lv = util.check_lua_version(lua, luaver)
493 if lv then
494 return lua, d, lv
495 end
496 elseif is_wrapper == true or err == nil then
497 table.insert(tried, lua)
498 else
499 table.insert(tried, string.format("%-13s (%s)", lua, err))
500 end
501 end
502 end
503 local interp = luaver
504 and ("Lua " .. luaver .. " interpreter")
505 or "Lua interpreter"
506 return nil, interp .. " not found at " .. prefix .. "\n" ..
507 (verbose and "Tried:\t" .. table.concat(tried, "\n\t") or "")
508 end
509 end
510
511 function util.find_lua(prefix, luaver, verbose)
512 local lua, bindir
513 lua, bindir, luaver = find_lua_bindir(prefix, luaver, verbose)
514 if not lua then
515 return nil, bindir
516 end
517
518 return {
519 lua_version = luaver,
520 lua = lua,
521 lua_dir = prefix,
522 lua_bindir = bindir,
523 }
524 end
525end
526
527function util.lua_is_wrapper(interp)
528 local fd, err = io.open(interp, "r")
529 if not fd then
530 return nil, err
531 end
532 local data, err = fd:read(1000)
533 fd:close()
534 if not data then
535 return nil, err
536 end
537 return not not data:match("LUAROCKS_SYSCONFDIR")
538end
539
540function util.opts_table(type_name, valid_opts)
541 local opts_mt = {}
542
543 opts_mt.__index = opts_mt
544
545 function opts_mt.type()
546 return type_name
547 end
548
549 return function(opts)
550 for k, v in pairs(opts) do
551 local tv = type(v)
552 if not valid_opts[k] then
553 error("invalid option: "..k)
554 end
555 local vo, optional = valid_opts[k]:match("^(.-)(%??)$")
556 if not (tv == vo or (optional == "?" and tv == nil)) then
557 error("invalid type option: "..k.." - got "..tv..", expected "..vo)
558 end
559 end
560 for k, v in pairs(valid_opts) do
561 if (not v:find("?", 1, true)) and opts[k] == nil then
562 error("missing option: "..k)
563 end
564 end
565 return setmetatable(opts, opts_mt)
566 end
567end
568
569--- Return a table of modules that are already provided by the VM, which
570-- can be specified as dependencies without having to install an actual rock.
571-- @param rockspec (optional) a rockspec table, so that rockspec format
572-- version compatibility can be checked. If not given, maximum compatibility
573-- is assumed.
574-- @return a table with rock names as keys and versions and values,
575-- specifying modules that are already provided by the VM (including
576-- "lua" for the Lua version and, for format 3.0+, "luajit" if detected).
577function util.get_rocks_provided(rockspec)
578 local cfg = require("luarocks.core.cfg")
579
580 if not rockspec and cfg.cache.rocks_provided then
581 return cfg.cache.rocks_provided
582 end
583
584 local rocks_provided = {}
585
586 local lv = cfg.lua_version
587
588 rocks_provided["lua"] = lv.."-1"
589
590 if lv == "5.2" then
591 rocks_provided["bit32"] = lv.."-1"
592 end
593
594 if lv == "5.3" or lv == "5.4" then
595 rocks_provided["utf8"] = lv.."-1"
596 end
597
598 if lv == "5.1" then
599 local ljv = util.get_luajit_version()
600 if ljv then
601 rocks_provided["luabitop"] = ljv.."-1"
602 if (not rockspec) or rockspec:format_is_at_least("3.0") then
603 rocks_provided["luajit"] = ljv.."-1"
604 end
605 end
606 end
607
608 if cfg.rocks_provided then
609 util.deep_merge_under(rocks_provided, cfg.rocks_provided)
610 end
611
612 if not rockspec then
613 cfg.cache.rocks_provided = rocks_provided
614 end
615
616 return rocks_provided
617end
618
619function util.remove_doc_dir(name, version)
620 local path = require("luarocks.path")
621 local fs = require("luarocks.fs")
622 local dir = require("luarocks.dir")
623
624 local install_dir = path.install_dir(name, version)
625 for _, f in ipairs(fs.list_dir(install_dir)) do
626 local doc_dirs = { "doc", "docs" }
627 for _, d in ipairs(doc_dirs) do
628 if f == d then
629 fs.delete(dir.path(install_dir, f))
630 end
631 end
632 end
633end
634
635return util