From a21fcdc434f7135b3be215bbab5ad08f1873d300 Mon Sep 17 00:00:00 2001 From: Hisham Muhammad Date: Thu, 14 Mar 2019 23:18:53 -0300 Subject: config: add modes for reading, writing and unsetting individual entries Includes special config keys `lua_dir` and `lua_version`, which are essentially persistent versions of --lua-dir and --lua-version: * `lua_dir` writes a number of LUA_* variables and cfg.lua_interpreter in the current scope's config file. * `lua_version` writes default-lua-version.lua to the given scope. Also deprecates the "flag" versions for various getters: * `--lua-incdir`: use `luarocks config variables.LUA_INCDIR` * `--lua-libdir`: use `luarocks config variables.LUA_LIBDIR` * `--lua-ver`: use `luarocks config lua_version` * `--system-config`: use `luarocks config config_files.system.file` * `--user-config`: use `luarocks config config_files.user.file` * `--rock-trees`: use `luarocks config rocks_trees` --- src/luarocks/cmd.lua | 58 ++++++-- src/luarocks/cmd/config.lua | 330 +++++++++++++++++++++++++++++++++----------- src/luarocks/cmd/help.lua | 6 +- src/luarocks/cmd/init.lua | 9 +- src/luarocks/core/cfg.lua | 42 +++--- src/luarocks/fs/lua.lua | 2 +- src/luarocks/persist.lua | 35 ++++- src/luarocks/upload/api.lua | 5 +- src/luarocks/util.lua | 3 + 9 files changed, 357 insertions(+), 133 deletions(-) (limited to 'src') diff --git a/src/luarocks/cmd.lua b/src/luarocks/cmd.lua index eb120c0b..d038852b 100644 --- a/src/luarocks/cmd.lua +++ b/src/luarocks/cmd.lua @@ -152,6 +152,33 @@ local function process_server_flags(flags) return true end +local function get_lua_version(flags) + if flags["lua-version"] then + return flags["lua-version"] + end + local dirs = { + cfg.home_tree, + cfg.sysconfdir, + } + if flags["project-tree"] then + table.insert(dirs, 1, dir.path(flags["project-tree"], "..")) + end + for _, d in ipairs(dirs) do + local f = dir.path(d, ".luarocks", "default-lua-version.lua") + local mod, err = loadfile(f, "t") + if mod then + local pok, ver = pcall(mod) + if pok and type(ver) == "string" and ver:match("%d+.%d+") then + if flags["verbose"] then + util.printout("Defaulting to Lua " .. ver .. " based on " .. f .. " ...") + end + return ver + end + end + end + return nil +end + --- Main command-line processor. -- Parses input arguments and calls the appropriate driver function -- to execute the action requested on the command-line, forwarding @@ -249,6 +276,16 @@ function cmd.run_command(description, commands, external_namespace, ...) end command = command:gsub("-", "_") + if command == "config" then + if nonflags[1] == "lua_version" and nonflags[2] then + flags["lua-version"] = nonflags[2] + elseif nonflags[1] == "lua_dir" and nonflags[2] then + flags["lua-dir"] = nonflags[2] + end + end + + local lua_version = get_lua_version(flags) + if flags["deps-mode"] and not deps.check_deps_mode_flag(flags["deps-mode"]) then die("Invalid entry for --deps-mode.") end @@ -256,47 +293,44 @@ function cmd.run_command(description, commands, external_namespace, ...) local detected if flags["lua-dir"] then local err - detected, err = util.find_lua(flags["lua-dir"], flags["lua-version"]) + detected, err = util.find_lua(flags["lua-dir"], lua_version) if not detected then die(err) end assert(detected.lua_version) assert(detected.lua_dir) - elseif flags["lua-version"] then + elseif lua_version then local path_sep = (package.config:sub(1, 1) == "\\" and ";" or ":") for bindir in os.getenv("PATH"):gmatch("[^"..path_sep.."]+") do local parentdir = bindir:gsub("[\\/][^\\/]+[\\/]?$", "") - detected = util.find_lua(dir.path(parentdir), flags["lua-version"]) + detected = util.find_lua(dir.path(parentdir), lua_version) if detected then break end - detected = util.find_lua(bindir, flags["lua-version"]) + detected = util.find_lua(bindir, lua_version) if detected then break end end if not detected then util.warning("Could not find a Lua interpreter for version " .. - flags["lua-version"] .. " in your PATH. " .. + lua_version .. " in your PATH. " .. "Modules may not install with the correct configurations. " .. "You may want to specify to the path prefix to your build " .. - "of Lua " .. flags["lua-version"] .. " using --lua-dir") + "of Lua " .. lua_version .. " using --lua-dir") detected = { - lua_version = flags["lua-version"], + lua_version = lua_version, } end end if flags["project-tree"] then local project_tree = flags["project-tree"]:gsub("[/\\][^/\\]+$", "") - detected = detected or { - project_dir = project_tree - } + detected = detected or {} + detected.project_dir = project_tree local d = check_if_config_is_present(detected, project_tree) if d then detected = d - else - detected.project_dir = nil end else detected = detected or {} diff --git a/src/luarocks/cmd/config.lua b/src/luarocks/cmd/config.lua index 794cc989..5c7cc6c9 100644 --- a/src/luarocks/cmd/config.lua +++ b/src/luarocks/cmd/config.lua @@ -2,26 +2,61 @@ -- Queries information about the LuaRocks configuration. local config_cmd = {} +local persist = require("luarocks.persist") local cfg = require("luarocks.core.cfg") local util = require("luarocks.util") local deps = require("luarocks.deps") local dir = require("luarocks.dir") -local fun = require("luarocks.fun") +local fs = require("luarocks.fs") config_cmd.help_summary = "Query information about the LuaRocks configuration." -config_cmd.help_arguments = "" +config_cmd.help_arguments = "( | --scope= | --unset --scope= | )" config_cmd.help = [[ ---lua-incdir Path to Lua header files. +* When given a configuration key, it prints the value of that key + according to the currently active configuration (taking into account + all config files and any command-line flags passed) ---lua-libdir Path to Lua library files. + Examples: + luarocks config lua_interpreter + luarocks config variables.LUA_INCDIR + luarocks config lua_version ---lua-ver Lua version (in major.minor format). e.g. 5.1 +* When given a configuration key and a value, + it overwrites the config file (see the --scope option below to determine which) + and replaces the value of the given key with the given value. ---system-config Location of the system config file. + * `lua_dir` is a special key as it checks for a valid Lua installation + (equivalent to --lua-dir) and sets several keys at once. + * `lua_version` is a special key as it changes the default Lua version + used by LuaRocks commands (eqivalent to passing --lua-version). ---user-config Location of the user config file. + Examples: + luarocks config variables.OPENSSL_DIR /usr/local/openssl + luarocks config lua_dir /usr/local + luarocks config lua_version 5.3 ---rock-trees Rocks trees in use. First the user tree, then the system tree. +* When given a configuration key and --unset, + it overwrites the config file (see the --scope option below to determine which) + and deletes that key from the file. + + Example: luarocks config variables.OPENSSL_DIR --unset + +* When given no arguments, it prints the entire currently active + configuration, resulting from reading the config files from + all scopes. + + Example: luarocks config + +OPTIONS +--scope= The scope indicates which config file should be rewritten. + Accepted values are "system", "user" or "project". + * Using a wrapper created with `luarocks init`, + the default is "project". + * Using --local (or when `local_by_default` is `true`), + the default is "user". + * Otherwise, the default is "system". + +--json Output as JSON ]] config_cmd.help_see_also = [[ https://github.com/luarocks/luarocks/wiki/Config-file-format @@ -30,35 +65,13 @@ config_cmd.help_see_also = [[ local function config_file(conf) print(dir.normalize(conf.file)) - if conf.ok then + if conf.found then return true else return nil, "file not found" end end -local function printf(fmt, ...) - print((fmt):format(...)) -end - -local cfg_maps = { - external_deps_patterns = true, - external_deps_subdirs = true, - rocks_provided = true, - rocks_provided_3_0 = true, - runtime_external_deps_patterns = true, - runtime_external_deps_subdirs = true, - upload = true, - variables = true, -} - -local cfg_arrays = { - disabled_servers = true, - external_deps_dirs = true, - rocks_trees = true, - rocks_servers = true, -} - local cfg_skip = { errorcodes = true, flags = true, @@ -67,60 +80,163 @@ local cfg_skip = { upload_servers = true, } -local function print_config(cfg) - for k, v in util.sortedpairs(cfg) do - k = tostring(k) - if type(v) == "string" or type(v) == "number" then - printf("%s = %q", k, v) - elseif type(v) == "boolean" then - printf("%s = %s", k, tostring(v)) - elseif type(v) == "function" or cfg_skip[k] then - -- skip - elseif cfg_maps[k] then - printf("%s = {", k) - for kk, vv in util.sortedpairs(v) do - local keyfmt = kk:match("^[a-zA-Z_][a-zA-Z0-9_]*$") and "%s" or "[%q]" - if type(vv) == "table" then - local qvs = fun.map(vv, function(e) return string.format("%q", e) end) - printf(" "..keyfmt.." = {%s},", kk, table.concat(qvs, ", ")) +local function should_skip(k, v) + return type(v) == "function" or cfg_skip[k] +end + +local function cleanup(tbl) + local copy = {} + for k, v in pairs(tbl) do + if not should_skip(k, v) then + copy[k] = v + end + end + return copy +end + +local function traverse_varstring(var, tbl, fn, missing_parent) + local k, r = var:match("^%[([0-9]+)%]%.(.*)$") + if k then + k = tonumber(k) + else + k, r = var:match("^([^.[]+)%.(.*)$") + if not k then + k, r = var:match("^([^[]+)(%[.*)$") + end + end + + if k then + if not tbl[k] and missing_parent then + missing_parent(tbl, k) + end + + if tbl[k] then + return traverse_varstring(r, tbl[k], fn, missing_parent) + else + return nil, "Unknown entry " .. k + end + end + + local i = var:match("^%[([0-9]+)%]$") + if i then + var = tonumber(i) + end + + return fn(tbl, var) +end + +local function print_json(value) + local json_ok, json = util.require_json() + if not json_ok then + return nil, "A JSON library is required for this command. "..json + end + + print(json.encode(value)) + return true +end + +local function print_entry(var, tbl, is_json) + return traverse_varstring(var, tbl, function(t, k) + if not t[k] then + return nil, "Unknown entry " .. k + end + local val = t[k] + + if not should_skip(var, val) then + if is_json then + return print_json(val) + elseif type(val) == "string" then + print(val) + else + persist.write_value(io.stdout, val) + end + end + return true + end) +end + +local function infer_type(var) + local typ + traverse_varstring(var, cfg, function(t, k) + if t[k] ~= nil then + typ = type(t[k]) + end + end) + return typ +end + +local function write_entries(keys, scope, do_unset) + if scope == "project" and not cfg.config_files.project then + return nil, "Current directory is not part of a project. You may want to run `luarocks init`." + end + + local tbl, err = persist.load_config_file_if_basic(cfg.config_files[scope].file, cfg) + if not tbl then + return nil, err + end + + for var, val in util.sortedpairs(keys) do + traverse_varstring(var, tbl, function(t, k) + if do_unset then + t[k] = nil + else + local typ = infer_type(var) + local v + if typ == "number" and tonumber(val) then + v = tonumber(val) + elseif typ == "boolean" and val == "true" then + v = true + elseif typ == "boolean" and val == "false" then + v = false else - printf(" "..keyfmt.." = %q,", kk, vv) + v = val end + t[k] = v + keys[var] = v end - printf("}") - elseif cfg_arrays[k] then - if #v == 0 then - printf("%s = {}", k) + return true + end, function(p, k) + p[k] = {} + end) + end + + local ok, err = persist.save_from_table(cfg.config_files[scope].file, tbl) + if ok then + print(do_unset and "Removed" or "Wrote") + for var, val in util.sortedpairs(keys) do + if do_unset then + print(("\t%s"):format(var)) else - printf("%s = {", k) - for _, vv in ipairs(v) do - if type(vv) == "string" then - printf(" %q,", vv) - elseif type(vv) == "table" then - printf(" {") - if next(vv) == 1 then - for _, v3 in ipairs(vv) do - printf(" %q,", v3) - end - else - for k3, v3 in util.sortedpairs(vv) do - local keyfmt = tostring(k3):match("^[a-zA-Z_][a-zA-Z0-9_]*$") and "%s" or "[%q]" - printf(" "..keyfmt.." = %q,", k3, v3) - end - end - printf(" },") - end - end - printf("}") + print(("\t%s = %q"):format(var, val)) end end + print(do_unset and "from" or "to") + print("\t" .. cfg.config_files[scope].file) + return true + else + return nil, err + end +end + +local function check_scope(flags) + local scope = flags["scope"] + or (flags["local"] and "user") + or (flags["project-tree"] and "project") + or (cfg.local_by_default and "user") + or "system" + if scope ~= "system" and scope ~= "user" and scope ~= "project" then + return nil, "Valid values for scope are: system, user, project" end + + return scope end --- Driver function for "config" command. -- @return boolean: True if succeeded, nil on errors. -function config_cmd.command(flags) +function config_cmd.command(flags, var, val) deps.check_lua(cfg.variables) + + -- deprecated flags if flags["lua-incdir"] then print(cfg.variables.LUA_INCDIR) return true @@ -133,12 +249,11 @@ function config_cmd.command(flags) print(cfg.lua_version) return true end - local conf = cfg.which_config() if flags["system-config"] then - return config_file(conf.system) + return config_file(cfg.config_files.system) end if flags["user-config"] then - return config_file(conf.user) + return config_file(cfg.config_files.user) end if flags["rock-trees"] then for _, tree in ipairs(cfg.rocks_trees) do @@ -151,9 +266,68 @@ function config_cmd.command(flags) end return true end + + if var == "lua_version" and val then + local scope, err = check_scope(flags) + if not scope then + return nil, err + end + + if scope == "project" and not cfg.config_files.project then + return nil, "Current directory is not part of a project. You may want to run `luarocks init`." + end + + local prefix = dir.dir_name(cfg.config_files[scope].file) + local ok, err = fs.make_dir(prefix) + if not ok then + return nil, "could not set default Lua version: " .. err + end + local fd, err = io.open(dir.path(prefix, "default-lua-version.lua"), "w") + if not fd then + return nil, "could not set default Lua version: " .. err + end + + fd:write('return "' .. val .. '"\n') + fd:close() + print("Lua version will default to " .. val .. " in " .. prefix) + end - print_config(cfg) - return true + if var == "lua_dir" and val then + local scope, err = check_scope(flags) + if not scope then + return nil, err + end + local keys = { + ["variables.LUA_DIR"] = cfg.variables.LUA_DIR, + ["variables.LUA_BINDIR"] = cfg.variables.LUA_BINDIR, + ["variables.LUA_INCDIR"] = cfg.variables.LUA_INCDIR, + ["variables.LUA_LIBDIR"] = cfg.variables.LUA_LIBDIR, + ["lua_interpreter"] = cfg.lua_interpreter, + } + return write_entries(keys, scope, flags["unset"]) + end + + if var then + if val or flags["unset"] then + local scope, err = check_scope(flags) + if not scope then + return nil, err + end + + return write_entries({ [var] = val }, scope, flags["unset"]) + else + return print_entry(var, cfg, flags["json"]) + end + end + + local cleancfg = cleanup(cfg) + + if flags["json"] then + return print_json(cleancfg) + else + print(persist.save_from_table_to_string(cleancfg)) + return true + end end return config_cmd diff --git a/src/luarocks/cmd/help.lua b/src/luarocks/cmd/help.lua index e5f1b799..dcc9e358 100644 --- a/src/luarocks/cmd/help.lua +++ b/src/luarocks/cmd/help.lua @@ -90,14 +90,14 @@ function help.command(description, commands, command) util.printout() util.printout("\tConfiguration files:") local conf = cfg.config_files - util.printout("\t\tSystem : ".. dir.normalize(conf.system.file) .. " (" .. get_status(conf.system.ok) ..")") + util.printout("\t\tSystem : ".. dir.normalize(conf.system.file) .. " (" .. get_status(conf.system.found) ..")") if conf.user.file then - util.printout("\t\tUser : ".. dir.normalize(conf.user.file) .. " (" .. get_status(conf.user.ok) ..")") + util.printout("\t\tUser : ".. dir.normalize(conf.user.file) .. " (" .. get_status(conf.user.found) ..")") else util.printout("\t\tUser : disabled in this LuaRocks installation.") end if conf.project then - util.printout("\t\tProject : ".. dir.normalize(conf.project.file) .. " (" .. get_status(conf.project.ok) ..")") + util.printout("\t\tProject : ".. dir.normalize(conf.project.file) .. " (" .. get_status(conf.project.found) ..")") end util.printout() util.printout("\tRocks trees in use: ") diff --git a/src/luarocks/cmd/init.lua b/src/luarocks/cmd/init.lua index b1bb3b51..3b8c9fea 100644 --- a/src/luarocks/cmd/init.lua +++ b/src/luarocks/cmd/init.lua @@ -92,14 +92,7 @@ function init.command(flags, name, version) if flags["reset"] then fs.delete(lua_wrapper) - for v in util.lua_versions() do - local config_file = dir.path(".luarocks", "config-"..v..".lua") - if v ~= cfg.lua_version then - fs.move(config_file, config_file .. "~") - else - fs.delete(config_file) - end - end + fs.delete(dir.path(".luarocks", "default_lua_version.lua")) end util.printout("Adding entries to .gitignore ...") diff --git a/src/luarocks/core/cfg.lua b/src/luarocks/core/cfg.lua index 0c2e3f67..af4327c6 100644 --- a/src/luarocks/core/cfg.lua +++ b/src/luarocks/core/cfg.lua @@ -728,33 +728,31 @@ function cfg.init(detected, warning) cfg.variables.LUA = cfg.variables.LUA or (cfg.variables.LUA_BINDIR and (cfg.variables.LUA_BINDIR .. "/" .. cfg.lua_interpreter):gsub("//", "/")) cfg.user_agent = "LuaRocks/"..cfg.program_version.." "..cfg.arch + cfg.config_files = { + project = detected.project_dir and { + file = project_config_file, + found = not not project_config_ok, + }, + system = { + file = sys_config_file, + found = not not sys_config_ok, + }, + user = { + file = home_config_file, + found = not not home_config_ok, + }, + nearest = project_config_ok + and project_config_file + or (home_config_ok + and home_config_file + or sys_config_file), + } + ---------------------------------------- -- Attributes of cfg are set. -- Let's add some methods. ---------------------------------------- - function cfg.which_config() - return { - project = detected.project_dir and { - file = project_config_file, - ok = project_config_ok, - }, - system = { - file = sys_config_file, - ok = sys_config_ok, - }, - user = { - file = home_config_file, - ok = home_config_ok, - }, - nearest = project_config_ok - and project_config_file - or (home_config_ok - and home_config_file - or sys_config_file), - } - end - do local function make_paths_from_tree(tree) local lua_path, lib_path, bin_path diff --git a/src/luarocks/fs/lua.lua b/src/luarocks/fs/lua.lua index 0273cd53..71529cc0 100644 --- a/src/luarocks/fs/lua.lua +++ b/src/luarocks/fs/lua.lua @@ -114,7 +114,7 @@ function fs_lua.is_tool_available(tool_cmd, tool_name, arg) if not fs.execute_quiet(tool_cmd, arg) then local msg = "'%s' program not found. Make sure %s is installed and is available in your PATH " .. "(or you may want to edit the 'variables.%s' value in file '%s')" - return nil, msg:format(tool_cmd, tool_name, tool_name:upper(), cfg.which_config().nearest) + return nil, msg:format(tool_cmd, tool_name, tool_name:upper(), cfg.config_files.nearest) else return true end diff --git a/src/luarocks/persist.lua b/src/luarocks/persist.lua index 73f92bea..b8060ea0 100644 --- a/src/luarocks/persist.lua +++ b/src/luarocks/persist.lua @@ -20,8 +20,9 @@ local write_table -- @param level number: the indentation level -- @param sub_order table: optional prioritization table -- @see write_table -local function write_value(out, v, level, sub_order) +function persist.write_value(out, v, level, sub_order) if type(v) == "table" then + level = level or 0 write_table(out, v, level + 1, sub_order) elseif type(v) == "string" then if v:match("[\r\n]") then @@ -80,7 +81,7 @@ local function write_table_key_assignment(out, k, level) out:write(k) else out:write("[") - write_value(out, k, level) + persist.write_value(out, k, level) out:write("]") end @@ -112,7 +113,7 @@ write_table = function(out, tbl, level, field_order) write_table_key_assignment(out, k, level) end - write_value(out, v, level, sub_order) + persist.write_value(out, v, level, sub_order) if type(v) == "number" then sep = ", " indent = false @@ -139,13 +140,13 @@ local function write_table_as_assignments(out, tbl, field_order) return nil, "cannot store '"..tostring(k).."' as a plain key." end out:write(k.." = ") - write_value(out, v, 0, sub_order) + persist.write_value(out, v, 0, sub_order) out:write("\n") end return true end ---- Write a table as series of assignments to a writer object. +--- Write a table using Lua table syntax to a writer object. -- @param out table or userdata: a writer object supporting :write() method. -- @param tbl table: the table to be written. local function write_table_as_table(out, tbl) @@ -153,7 +154,7 @@ local function write_table_as_table(out, tbl) for k, v, sub_order in util.sortedpairs(tbl) do out:write(" ") write_table_key_assignment(out, k, 1) - write_value(out, v, 1, sub_order) + persist.write_value(out, v, 1, sub_order) out:write(",\n") end out:write("}\n") @@ -216,4 +217,26 @@ function persist.save_as_module(filename, tbl) return true end +function persist.load_config_file_if_basic(filename, cfg) + local env = { + home = cfg.home + } + local result, err, errcode = persist.load_into_table(filename, env) + if errcode == "load" or errcode == "run" then + -- bad config file or depends on env, so error out + return nil, "Could not read existing config file " .. filename + end + + local tbl + if errcode == "open" then + -- could not open, maybe file does not exist + tbl = {} + else + tbl = result + tbl.home = nil + end + + return tbl +end + return persist diff --git a/src/luarocks/upload/api.lua b/src/luarocks/upload/api.lua index bb2197ee..0e71432f 100644 --- a/src/luarocks/upload/api.lua +++ b/src/luarocks/upload/api.lua @@ -11,11 +11,10 @@ local multipart = require("luarocks.upload.multipart") local Api = {} local function upload_config_file() - local conf = cfg.which_config() - if not conf.user.file then + if not cfg.config_files.user.file then return nil end - return (conf.user.file:gsub("/[^/]+$", "/upload_config.lua")) + return (cfg.config_files.user.file:gsub("/[^/]+$", "/upload_config.lua")) end function Api:load_config() diff --git a/src/luarocks/util.lua b/src/luarocks/util.lua index aac52344..1714952e 100644 --- a/src/luarocks/util.lua +++ b/src/luarocks/util.lua @@ -106,6 +106,7 @@ local supported_flags = { ["homepage"] = "\"\"", ["index"] = true, ["issues"] = true, + ["json"] = true, ["keep"] = true, ["labels"] = true, ["lib"] = "", @@ -149,6 +150,7 @@ local supported_flags = { ["rock-trees"] = true, ["rockspec"] = true, ["rockspec-format"] = "", + ["scope"] = "", ["server"] = "", ["sign"] = true, ["skip-pack"] = true, @@ -161,6 +163,7 @@ local supported_flags = { ["timeout"] = "", ["to"] = "", ["tree"] = "", + ["unset"] = true, ["user-config"] = true, ["verbose"] = true, ["verify"] = true, -- cgit v1.2.3-55-g6feb