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 | ||
