diff options
author | V1K1NGbg <victor@ilchev.com> | 2024-07-09 22:25:26 +0300 |
---|---|---|
committer | V1K1NGbg <victor@ilchev.com> | 2024-08-05 20:49:17 +0300 |
commit | 0efd878f6435c4d929c101f7c1388801096c6004 (patch) | |
tree | 2020b4024d05f57d73c27adbb1ffd28580a09b01 | |
parent | 9094232f4de7827f464370d4bfae65f4dab96d98 (diff) | |
download | luarocks-0efd878f6435c4d929c101f7c1388801096c6004.tar.gz luarocks-0efd878f6435c4d929c101f7c1388801096c6004.tar.bz2 luarocks-0efd878f6435c4d929c101f7c1388801096c6004.zip |
halfway commit
-rw-r--r-- | src/luarocks/core/path.tl | 2 | ||||
-rw-r--r-- | src/luarocks/core/vers.tl | 2 | ||||
-rw-r--r-- | src/luarocks/dir-original.lua (renamed from src/luarocks/dir.lua) | 0 | ||||
-rw-r--r-- | src/luarocks/dir.tl | 64 | ||||
-rw-r--r-- | src/luarocks/fs-original.lua (renamed from src/luarocks/fs.lua) | 0 | ||||
-rw-r--r-- | src/luarocks/fs.tl | 151 | ||||
-rw-r--r-- | src/luarocks/util-original.lua (renamed from src/luarocks/util.lua) | 0 | ||||
-rw-r--r-- | src/luarocks/util.tl | 635 |
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. |
30 | function path.versioned_name(file: string, prefix: string, name: string, version: string): string | 30 | function 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. |
199 | function vers.match_constraints(version: Version, constraints: {Constraints}): boolean --! | 199 | function 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. | ||
3 | local record dir | ||
4 | end | ||
5 | |||
6 | local core = require("luarocks.core.dir") | ||
7 | |||
8 | dir.path = core.path --! | ||
9 | dir.split_url = core.split_url | ||
10 | dir.normalize = core.normalize | ||
11 | |||
12 | local 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". | ||
18 | function 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 | ||
28 | end | ||
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. | ||
35 | function 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 | ||
46 | end | ||
47 | |||
48 | --- Returns true if protocol does not require additional tools. | ||
49 | -- @param protocol The protocol name | ||
50 | function dir.is_basic_protocol(protocol: string): boolean --? extra type (enum) | ||
51 | return protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" | ||
52 | end | ||
53 | |||
54 | function 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", "")) | ||
62 | end | ||
63 | |||
64 | return 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 | |||
8 | local pairs = pairs --? | ||
9 | |||
10 | local record fs | ||
11 | |||
12 | end | ||
13 | |||
14 | -- To avoid a loop when loading the other fs modules. | ||
15 | package.loaded["luarocks.fs"] = fs | ||
16 | |||
17 | local cfg = require("luarocks.core.cfg") | ||
18 | |||
19 | local pack = table.pack or function(...) return { n = select("#", ...), ... } end --! | ||
20 | |||
21 | math.randomseed(os.time()) | ||
22 | |||
23 | local fs_is_verbose = false | ||
24 | |||
25 | do | ||
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 | ||
58 | end | ||
59 | |||
60 | do | ||
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 | ||
149 | end | ||
150 | |||
151 | return 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 | |||
7 | local record util | ||
8 | end | ||
9 | |||
10 | local core = require("luarocks.core.util") | ||
11 | |||
12 | util.cleanup_path = core.cleanup_path --! | ||
13 | util.split_string = core.split_string | ||
14 | util.sortedpairs = core.sortedpairs | ||
15 | util.deep_merge = core.deep_merge | ||
16 | util.deep_merge_under = core.deep_merge_under | ||
17 | util.popen_read = core.popen_read | ||
18 | util.show_table = core.show_table | ||
19 | util.printerr = core.printerr | ||
20 | util.warning = core.warning | ||
21 | util.keys = core.keys | ||
22 | |||
23 | local unpack = unpack or table.unpack --! | ||
24 | local pack = table.pack or function(...) return { n = select("#", ...), ... } end --! | ||
25 | |||
26 | local 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. | ||
35 | function 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 | ||
41 | end | ||
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. | ||
47 | function 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 | ||
54 | end | ||
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. | ||
61 | function 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 | ||
70 | end | ||
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 | ||
77 | function util.matchquote(s) | ||
78 | return (s:gsub("[?%-+*%[%].%%()$^]","%%%1")) | ||
79 | end | ||
80 | |||
81 | local 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. | ||
93 | function 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 | ||
105 | end | ||
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 | ||
110 | local 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 | ||
119 | end | ||
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. | ||
129 | function 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 | ||
145 | end | ||
146 | |||
147 | function 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 | ||
162 | end | ||
163 | |||
164 | function 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 | ||
179 | end | ||
180 | |||
181 | function util.starts_with(s, prefix) | ||
182 | return s:sub(1,#prefix) == prefix | ||
183 | end | ||
184 | |||
185 | --- Print a line to standard output | ||
186 | function util.printout(...) | ||
187 | io.stdout:write(table.concat({...},"\t")) | ||
188 | io.stdout:write("\n") | ||
189 | end | ||
190 | |||
191 | function 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() | ||
197 | end | ||
198 | |||
199 | function 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 | ||
219 | end | ||
220 | |||
221 | function util.format_rock_name(name, namespace, version) | ||
222 | return (namespace and namespace.."/" or "")..name..(version and " "..version or "") | ||
223 | end | ||
224 | |||
225 | function 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) | ||
241 | end | ||
242 | |||
243 | function util.see_help(command, program) | ||
244 | return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'." | ||
245 | end | ||
246 | |||
247 | function 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." | ||
253 | end | ||
254 | |||
255 | function 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() | ||
266 | end | ||
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. | ||
274 | local 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 | ||
298 | end | ||
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. | ||
302 | function 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 | ||
339 | end | ||
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"' | ||
344 | function util.LQ(s) | ||
345 | return ("%q"):format(s) | ||
346 | end | ||
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 | ||
351 | function 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 | ||
357 | end | ||
358 | |||
359 | --- Argparse action callback for namespaced rock arguments. | ||
360 | function 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 | ||
378 | end | ||
379 | |||
380 | function 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 | ||
390 | end | ||
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. | ||
395 | function 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 | ||
407 | end | ||
408 | |||
409 | do | ||
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 | ||
525 | end | ||
526 | |||
527 | function 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") | ||
538 | end | ||
539 | |||
540 | function 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 | ||
567 | end | ||
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). | ||
577 | function 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 | ||
617 | end | ||
618 | |||
619 | function 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 | ||
633 | end | ||
634 | |||
635 | return util | ||