1
2
3
4
5
6
7
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
|
--- Assorted utilities for managing tables, plus a scheduler for rollback functions.
-- Does not requires modules directly (only as locals
-- inside specific functions) to avoid interdependencies,
-- as this is used in the bootstrapping stage of luarocks.core.cfg.
local util = {}
local core = require("luarocks.core.util")
util.cleanup_path = core.cleanup_path
util.split_string = core.split_string
util.sortedpairs = core.sortedpairs
util.deep_merge = core.deep_merge
util.deep_merge_under = core.deep_merge_under
util.popen_read = core.popen_read
util.show_table = core.show_table
util.printerr = core.printerr
util.warning = core.warning
util.keys = core.keys
local unpack = unpack or table.unpack
local pack = table.pack or function(...) return { n = select("#", ...), ... } end
local scheduled_functions = {}
--- Schedule a function to be executed upon program termination.
-- This is useful for actions such as deleting temporary directories
-- or failure rollbacks.
-- @param f function: Function to be executed.
-- @param ... arguments to be passed to function.
-- @return table: A token representing the scheduled execution,
-- which can be used to remove the item later from the list.
function util.schedule_function(f, ...)
assert(type(f) == "function")
local item = { fn = f, args = pack(...) }
table.insert(scheduled_functions, item)
return item
end
--- Unschedule a function.
-- This is useful for cancelling a rollback of a completed operation.
-- @param item table: The token representing the scheduled function that was
-- returned from the schedule_function call.
function util.remove_scheduled_function(item)
for k, v in pairs(scheduled_functions) do
if v == item then
table.remove(scheduled_functions, k)
return
end
end
end
--- Execute scheduled functions.
-- Some calls create temporary files and/or directories and register
-- corresponding cleanup functions. Calling this function will run
-- these function, erasing temporaries.
-- Functions are executed in the inverse order they were scheduled.
function util.run_scheduled_functions()
local fs = require("luarocks.fs")
if fs.change_dir_to_root then
fs.change_dir_to_root()
end
for i = #scheduled_functions, 1, -1 do
local item = scheduled_functions[i]
item.fn(unpack(item.args, 1, item.args.n))
end
end
--- Produce a Lua pattern that matches precisely the given string
-- (this is suitable to be concatenating to other patterns,
-- so it does not include beginning- and end-of-string markers (^$)
-- @param s string: The input string
-- @return string: The equivalent pattern
function util.matchquote(s)
return (s:gsub("[?%-+*%[%].%%()$^]","%%%1"))
end
local var_format_pattern = "%$%((%a[%a%d_]+)%)"
-- Check if a set of needed variables are referenced
-- somewhere in a list of definitions, warning the user
-- about any unused ones. Each key in needed_set should
-- appear as a $(XYZ) variable at least once as a
-- substring of some value of var_defs.
-- @param var_defs: a table with string keys and string
-- values, containing variable definitions.
-- @param needed_set: a set where keys are the names of
-- needed variables.
-- @param msg string: the warning message to display.
function util.warn_if_not_used(var_defs, needed_set, msg)
local seen = {}
for _, val in pairs(var_defs) do
for used in val:gmatch(var_format_pattern) do
seen[used] = true
end
end
for var, _ in pairs(needed_set) do
if not seen[var] then
util.warning(msg:format(var))
end
end
end
-- Output any entries that might remain in $(XYZ) format,
-- warning the user that substitutions have failed.
-- @param line string: the input string
local function warn_failed_matches(line)
local any_failed = false
if line:match(var_format_pattern) then
for unmatched in line:gmatch(var_format_pattern) do
util.warning("unmatched variable " .. unmatched)
any_failed = true
end
end
return any_failed
end
--- Perform make-style variable substitutions on string values of a table.
-- For every string value tbl.x which contains a substring of the format
-- "$(XYZ)" will have this substring replaced by vars["XYZ"], if that field
-- exists in vars. Only string values are processed; this function
-- does not scan subtables recursively.
-- @param tbl table: Table to have its string values modified.
-- @param vars table: Table containing string-string key-value pairs
-- representing variables to replace in the strings values of tbl.
function util.variable_substitutions(tbl, vars)
assert(type(tbl) == "table")
assert(type(vars) == "table")
local updated = {}
for k, v in pairs(tbl) do
if type(v) == "string" then
updated[k] = v:gsub(var_format_pattern, vars)
if warn_failed_matches(updated[k]) then
updated[k] = updated[k]:gsub(var_format_pattern, "")
end
end
end
for k, v in pairs(updated) do
tbl[k] = v
end
end
function util.lua_versions(sort)
local versions = { "5.1", "5.2", "5.3", "5.4" }
local i = 0
if sort == "descending" then
i = #versions + 1
return function()
i = i - 1
return versions[i]
end
else
return function()
i = i + 1
return versions[i]
end
end
end
function util.lua_path_variables()
local cfg = require("luarocks.core.cfg")
local lpath_var = "LUA_PATH"
local lcpath_var = "LUA_CPATH"
local lv = cfg.lua_version:gsub("%.", "_")
if lv ~= "5_1" then
if os.getenv("LUA_PATH_" .. lv) then
lpath_var = "LUA_PATH_" .. lv
end
if os.getenv("LUA_CPATH_" .. lv) then
lcpath_var = "LUA_CPATH_" .. lv
end
end
return lpath_var, lcpath_var
end
function util.starts_with(s, prefix)
return s:sub(1,#prefix) == prefix
end
--- Print a line to standard output
function util.printout(...)
io.stdout:write(table.concat({...},"\t"))
io.stdout:write("\n")
end
function util.title(msg, porcelain, underline)
if porcelain then return end
util.printout()
util.printout(msg)
util.printout((underline or "-"):rep(#msg))
util.printout()
end
function util.this_program(default)
local i = 1
local last, cur = default, default
while i do
local dbg = debug and debug.getinfo(i,"S")
if not dbg then break end
last = cur
cur = dbg.source
i=i+1
end
local prog = last:sub(1,1) == "@" and last:sub(2) or last
-- Check if we found the true path of a script that has a wrapper
local lrdir, binpath = prog:match("^(.*)/lib/luarocks/rocks%-[0-9.]*/[^/]+/[^/]+(/bin/[^/]+)$")
if lrdir then
-- Return the wrapper instead
return lrdir .. binpath
end
return prog
end
function util.format_rock_name(name, namespace, version)
return (namespace and namespace.."/" or "")..name..(version and " "..version or "")
end
function util.deps_mode_option(parser, program)
local cfg = require("luarocks.core.cfg")
parser:option("--deps-mode", "How to handle dependencies. Four modes are supported:\n"..
"* all - use all trees from the rocks_trees list for finding dependencies\n"..
"* one - use only the current tree (possibly set with --tree)\n"..
"* order - use trees based on order (use the current tree and all "..
"trees below it on the rocks_trees list)\n"..
"* none - ignore dependencies altogether.\n"..
"The default mode may be set with the deps_mode entry in the configuration file.\n"..
'The current default is "'..cfg.deps_mode..'".\n'..
"Type '"..util.this_program(program or "luarocks").."' with no "..
"arguments to see your list of rocks trees.")
:argname("<mode>")
:choices({"all", "one", "order", "none"})
parser:flag("--nodeps"):hidden(true)
end
function util.see_help(command, program)
return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'."
end
function util.see_also(text)
local see_also = "See also:\n"
if text then
see_also = see_also..text.."\n"
end
return see_also.." '"..util.this_program("luarocks").." help' for general options and configuration."
end
function util.announce_install(rockspec)
local cfg = require("luarocks.core.cfg")
local path = require("luarocks.path")
local suffix = ""
if rockspec.description and rockspec.description.license then
suffix = " (license: "..rockspec.description.license..")"
end
util.printout(rockspec.name.." "..rockspec.version.." is now installed in "..path.root_dir(cfg.root_dir)..suffix)
util.printout()
end
--- Collect rockspecs located in a subdirectory.
-- @param versions table: A table mapping rock names to newest rockspec versions.
-- @param paths table: A table mapping rock names to newest rockspec paths.
-- @param unnamed_paths table: An array of rockspec paths that don't contain rock
-- name and version in regular format.
-- @param subdir string: path to subdirectory.
local function collect_rockspecs(versions, paths, unnamed_paths, subdir)
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local path = require("luarocks.path")
local vers = require("luarocks.core.vers")
if fs.is_dir(subdir) then
for file in fs.dir(subdir) do
file = dir.path(subdir, file)
if file:match("rockspec$") and fs.is_file(file) then
local rock, version = path.parse_name(file)
if rock then
if not versions[rock] or vers.compare_versions(version, versions[rock]) then
versions[rock] = version
paths[rock] = file
end
else
table.insert(unnamed_paths, file)
end
end
end
end
end
--- Get default rockspec name for commands that take optional rockspec name.
-- @return string or (nil, string): path to the rockspec or nil and error message.
function util.get_default_rockspec()
local versions, paths, unnamed_paths = {}, {}, {}
-- Look for rockspecs in some common locations.
collect_rockspecs(versions, paths, unnamed_paths, ".")
collect_rockspecs(versions, paths, unnamed_paths, "rockspec")
collect_rockspecs(versions, paths, unnamed_paths, "rockspecs")
if #unnamed_paths > 0 then
-- There are rockspecs not following "name-version.rockspec" format.
-- More than one are ambiguous.
if #unnamed_paths > 1 then
return nil, "Please specify which rockspec file to use."
else
return unnamed_paths[1]
end
else
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local basename = dir.base_name(fs.current_dir())
if paths[basename] then
return paths[basename]
end
local rock = next(versions)
if rock then
-- If there are rockspecs for multiple rocks it's ambiguous.
if next(versions, rock) then
return nil, "Please specify which rockspec file to use."
else
return paths[rock]
end
else
return nil, "Argument missing: please specify a rockspec to use on current directory."
end
end
end
-- Quote Lua string, analogous to fs.Q.
-- @param s A string, such as "hello"
-- @return string: A quoted string, such as '"hello"'
function util.LQ(s)
return ("%q"):format(s)
end
-- Split name and namespace of a package name.
-- @param ns_name a name that may be in "namespace/name" format
-- @return string, string? - name and optionally a namespace
function util.split_namespace(ns_name)
local p1, p2 = ns_name:match("^([^/]+)/([^/]+)$")
if p1 then
return p2, p1
end
return ns_name
end
--- Argparse action callback for namespaced rock arguments.
function util.namespaced_name_action(args, target, ns_name)
assert(type(args) == "table")
assert(type(target) == "string")
assert(type(ns_name) == "string" or not ns_name)
if not ns_name then
return
end
if ns_name:match("%.rockspec$") or ns_name:match("%.rock$") then
args[target] = ns_name
else
local name, namespace = util.split_namespace(ns_name)
args[target] = name:lower()
if namespace then
args.namespace = namespace:lower()
end
end
end
function util.deep_copy(tbl)
local copy = {}
for k, v in pairs(tbl) do
if type(v) == "table" then
copy[k] = util.deep_copy(v)
else
copy[k] = v
end
end
return copy
end
-- A portable version of fs.exists that can be used at early startup,
-- before the platform has been determined and luarocks.fs has been
-- initialized.
function util.exists(file)
local fd, _, code = io.open(file, "r")
if code == 13 then
-- code 13 means "Permission denied" on both Unix and Windows
-- io.open on folders always fails with code 13 on Windows
return true
end
if fd then
fd:close()
return true
end
return false
end
do
local function Q(pathname)
if pathname:match("^.:") then
return pathname:sub(1, 2) .. '"' .. pathname:sub(3) .. '"'
end
return '"' .. pathname .. '"'
end
function util.check_lua_version(lua, luaver)
if not util.exists(lua) then
return nil
end
local lv, err = util.popen_read(Q(lua) .. ' -e "io.write(_VERSION:sub(5))"')
if lv == "" then
return nil
end
if luaver and luaver ~= lv then
return nil
end
return lv
end
function util.get_luajit_version()
local cfg = require("luarocks.core.cfg")
if cfg.cache.luajit_version_checked then
return cfg.cache.luajit_version
end
cfg.cache.luajit_version_checked = true
if not cfg.variables.LUA then
return nil
end
local ljv
if cfg.lua_version == "5.1" then
-- Ignores extra version info for custom builds, e.g. "LuaJIT 2.1.0-beta3 some-other-version-info"
ljv = util.popen_read(Q(cfg.variables.LUA) .. ' -e "io.write(tostring(jit and jit.version:gsub([[^%S+ (%S+).*]], [[%1]])))"')
if ljv == "nil" then
ljv = nil
end
end
cfg.cache.luajit_version = ljv
return ljv
end
local find_lua_bindir
do
local exe_suffix = (package.config:sub(1, 1) == "\\" and ".exe" or "")
local function insert_lua_variants(names, luaver)
local variants = {
"lua" .. luaver .. exe_suffix,
"lua" .. luaver:gsub("%.", "") .. exe_suffix,
"lua-" .. luaver .. exe_suffix,
"lua-" .. luaver:gsub("%.", "") .. exe_suffix,
}
for _, name in ipairs(variants) do
names[name] = luaver
table.insert(names, name)
end
end
find_lua_bindir = function(prefix, luaver, verbose)
local names = {}
if luaver then
insert_lua_variants(names, luaver)
else
for v in util.lua_versions("descending") do
insert_lua_variants(names, v)
end
end
if luaver == "5.1" or not luaver then
table.insert(names, "luajit" .. exe_suffix)
end
table.insert(names, "lua" .. exe_suffix)
local tried = {}
local dir_sep = package.config:sub(1, 1)
for _, d in ipairs({ prefix .. dir_sep .. "bin", prefix }) do
for _, name in ipairs(names) do
local lua = d .. dir_sep .. name
local is_wrapper, err = util.lua_is_wrapper(lua)
if is_wrapper == false then
local lv = util.check_lua_version(lua, luaver)
if lv then
return lua, d, lv
end
elseif is_wrapper == true or err == nil then
table.insert(tried, lua)
else
table.insert(tried, string.format("%-13s (%s)", lua, err))
end
end
end
local interp = luaver
and ("Lua " .. luaver .. " interpreter")
or "Lua interpreter"
return nil, interp .. " not found at " .. prefix .. "\n" ..
(verbose and "Tried:\t" .. table.concat(tried, "\n\t") or "")
end
end
function util.find_lua(prefix, luaver, verbose)
local lua, bindir
lua, bindir, luaver = find_lua_bindir(prefix, luaver, verbose)
if not lua then
return nil, bindir
end
return {
lua_version = luaver,
lua = lua,
lua_dir = prefix,
lua_bindir = bindir,
}
end
end
function util.lua_is_wrapper(interp)
local fd, err = io.open(interp, "r")
if not fd then
return nil, err
end
local data, err = fd:read(1000)
fd:close()
if not data then
return nil, err
end
return not not data:match("LUAROCKS_SYSCONFDIR")
end
function util.opts_table(type_name, valid_opts)
local opts_mt = {}
opts_mt.__index = opts_mt
function opts_mt.type()
return type_name
end
return function(opts)
for k, v in pairs(opts) do
local tv = type(v)
if not valid_opts[k] then
error("invalid option: "..k)
end
local vo, optional = valid_opts[k]:match("^(.-)(%??)$")
if not (tv == vo or (optional == "?" and tv == nil)) then
error("invalid type option: "..k.." - got "..tv..", expected "..vo)
end
end
for k, v in pairs(valid_opts) do
if (not v:find("?", 1, true)) and opts[k] == nil then
error("missing option: "..k)
end
end
return setmetatable(opts, opts_mt)
end
end
--- Return a table of modules that are already provided by the VM, which
-- can be specified as dependencies without having to install an actual rock.
-- @param rockspec (optional) a rockspec table, so that rockspec format
-- version compatibility can be checked. If not given, maximum compatibility
-- is assumed.
-- @return a table with rock names as keys and versions and values,
-- specifying modules that are already provided by the VM (including
-- "lua" for the Lua version and, for format 3.0+, "luajit" if detected).
function util.get_rocks_provided(rockspec)
local cfg = require("luarocks.core.cfg")
if not rockspec and cfg.cache.rocks_provided then
return cfg.cache.rocks_provided
end
local rocks_provided = {}
local lv = cfg.lua_version
rocks_provided["lua"] = lv.."-1"
if lv == "5.2" then
rocks_provided["bit32"] = lv.."-1"
end
if lv == "5.3" or lv == "5.4" then
rocks_provided["utf8"] = lv.."-1"
end
if lv == "5.1" then
local ljv = util.get_luajit_version()
if ljv then
rocks_provided["luabitop"] = ljv.."-1"
if (not rockspec) or rockspec:format_is_at_least("3.0") then
rocks_provided["luajit"] = ljv.."-1"
end
end
end
if cfg.rocks_provided then
util.deep_merge_under(rocks_provided, cfg.rocks_provided)
end
if not rockspec then
cfg.cache.rocks_provided = rocks_provided
end
return rocks_provided
end
function util.remove_doc_dir(name, version)
local path = require("luarocks.path")
local fs = require("luarocks.fs")
local dir = require("luarocks.dir")
local install_dir = path.install_dir(name, version)
for _, f in ipairs(fs.list_dir(install_dir)) do
local doc_dirs = { "doc", "docs" }
for _, d in ipairs(doc_dirs) do
if f == d then
fs.delete(dir.path(install_dir, f))
end
end
end
end
return util
|