summaryrefslogtreecommitdiff
path: root/src/luarocks/cmd
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
committerMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
commit5155816b7b925dec5d5feb1568b1d7ceb00938b9 (patch)
treedeca28ea15e79f6f804c3d90d2ba757881638af5 /src/luarocks/cmd
fetch tarballHEADmaster
Diffstat (limited to 'src/luarocks/cmd')
-rw-r--r--src/luarocks/cmd/build.lua198
-rw-r--r--src/luarocks/cmd/config.lua392
-rw-r--r--src/luarocks/cmd/doc.lua153
-rw-r--r--src/luarocks/cmd/download.lua51
-rw-r--r--src/luarocks/cmd/init.lua219
-rw-r--r--src/luarocks/cmd/install.lua271
-rw-r--r--src/luarocks/cmd/lint.lua50
-rw-r--r--src/luarocks/cmd/list.lua96
-rw-r--r--src/luarocks/cmd/make.lua163
-rw-r--r--src/luarocks/cmd/new_version.lua228
-rw-r--r--src/luarocks/cmd/pack.lua36
-rw-r--r--src/luarocks/cmd/path.lua83
-rw-r--r--src/luarocks/cmd/purge.lua73
-rw-r--r--src/luarocks/cmd/remove.lua72
-rw-r--r--src/luarocks/cmd/search.lua84
-rw-r--r--src/luarocks/cmd/show.lua314
-rw-r--r--src/luarocks/cmd/test.lua48
-rw-r--r--src/luarocks/cmd/unpack.lua169
-rw-r--r--src/luarocks/cmd/upload.lua128
-rw-r--r--src/luarocks/cmd/which.lua40
-rw-r--r--src/luarocks/cmd/write_rockspec.lua408
21 files changed, 3276 insertions, 0 deletions
diff --git a/src/luarocks/cmd/build.lua b/src/luarocks/cmd/build.lua
new file mode 100644
index 0000000..3268041
--- /dev/null
+++ b/src/luarocks/cmd/build.lua
@@ -0,0 +1,198 @@
+
+--- Module implementing the LuaRocks "build" command.
+-- Builds a rock, compiling its C parts if any.
+local cmd_build = {}
+
+local pack = require("luarocks.pack")
+local path = require("luarocks.path")
+local util = require("luarocks.util")
+local fetch = require("luarocks.fetch")
+local fs = require("luarocks.fs")
+local deps = require("luarocks.deps")
+local remove = require("luarocks.remove")
+local cfg = require("luarocks.core.cfg")
+local build = require("luarocks.build")
+local writer = require("luarocks.manif.writer")
+local search = require("luarocks.search")
+local make = require("luarocks.cmd.make")
+local repos = require("luarocks.repos")
+
+function cmd_build.add_to_parser(parser)
+ local cmd = parser:command("build", "Build and install a rock, compiling its C parts if any.\n".. -- luacheck: ignore 431
+ "If the sources contain a luarocks.lock file, uses it as an authoritative source for "..
+ "exact version of dependencies.\n"..
+ "If no arguments are given, behaves as luarocks make.", util.see_also())
+ :summary("Build/compile a rock.")
+
+ cmd:argument("rock", "A rockspec file, a source rock file, or the name of "..
+ "a rock to be fetched from a repository.")
+ :args("?")
+ :action(util.namespaced_name_action)
+ cmd:argument("version", "Rock version.")
+ :args("?")
+
+ cmd:flag("--only-deps --deps-only", "Install only the dependencies of the rock.")
+ cmd:option("--branch", "Override the `source.branch` field in the loaded "..
+ "rockspec. Allows to specify a different branch to fetch. Particularly "..
+ 'for "dev" rocks.')
+ :argname("<name>")
+ cmd:flag("--pin", "Create a luarocks.lock file listing the exact "..
+ "versions of each dependency found for this rock (recursively), "..
+ "and store it in the rock's directory. "..
+ "Ignores any existing luarocks.lock file in the rock's sources.")
+ make.cmd_options(cmd)
+end
+
+--- Build and install a rock.
+-- @param rock_filename string: local or remote filename of a rock.
+-- @param opts table: build options
+-- @return boolean or (nil, string, [string]): True if build was successful,
+-- or false and an error message and an optional error code.
+local function build_rock(rock_filename, opts)
+ assert(type(rock_filename) == "string")
+ assert(opts:type() == "build.opts")
+
+ local ok, err, errcode
+
+ local unpack_dir
+ unpack_dir, err, errcode = fetch.fetch_and_unpack_rock(rock_filename, nil, opts.verify)
+ if not unpack_dir then
+ return nil, err, errcode
+ end
+
+ local rockspec_filename = path.rockspec_name_from_rock(rock_filename)
+
+ ok, err = fs.change_dir(unpack_dir)
+ if not ok then return nil, err end
+
+ local rockspec
+ rockspec, err, errcode = fetch.load_rockspec(rockspec_filename)
+ if not rockspec then
+ return nil, err, errcode
+ end
+
+ ok, err, errcode = build.build_rockspec(rockspec, opts)
+
+ fs.pop_dir()
+ return ok, err, errcode
+end
+
+local function do_build(name, namespace, version, opts)
+ assert(type(name) == "string")
+ assert(type(namespace) == "string" or not namespace)
+ assert(version == nil or type(version) == "string")
+ assert(opts:type() == "build.opts")
+
+ local url, err
+ if name:match("%.rockspec$") or name:match("%.rock$") then
+ url = name
+ else
+ url, err = search.find_src_or_rockspec(name, namespace, version, opts.check_lua_versions)
+ if not url then
+ return nil, err
+ end
+ end
+
+ name, version = path.parse_name(url)
+ if name and repos.is_installed(name, version) then
+ if not opts.rebuild then
+ util.printout(name .. " " .. version .. " is already installed in " .. path.root_dir(cfg.root_dir))
+ util.printout("Use --force to reinstall.")
+ return name, version, "skip"
+ end
+ end
+
+ if url:match("%.rockspec$") then
+ local rockspec, err = fetch.load_rockspec(url, nil, opts.verify)
+ if not rockspec then
+ return nil, err
+ end
+ return build.build_rockspec(rockspec, opts)
+ end
+
+ if url:match("%.src%.rock$") then
+ opts.need_to_fetch = false
+ end
+
+ return build_rock(url, opts)
+end
+
+--- Driver function for "build" command.
+-- If a package name is given, forwards the request to "search" and,
+-- if returned a result, installs the matching rock.
+-- When passing a package name, a version number may also be given.
+-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an
+-- error message otherwise. exitcode is optionally returned.
+function cmd_build.command(args)
+ if not args.rock then
+ return make.command(args)
+ end
+
+ local opts = build.opts({
+ need_to_fetch = true,
+ minimal_mode = false,
+ deps_mode = deps.get_deps_mode(args),
+ build_only_deps = not not (args.only_deps and not args.pack_binary_rock),
+ namespace = args.namespace,
+ branch = args.branch,
+ verify = not not args.verify,
+ check_lua_versions = not not args.check_lua_versions,
+ pin = not not args.pin,
+ rebuild = not not (args.force or args.force_fast),
+ no_install = false
+ })
+
+ if args.sign and not args.pack_binary_rock then
+ return nil, "In the build command, --sign is meant to be used only with --pack-binary-rock"
+ end
+
+ if args.pack_binary_rock then
+ return pack.pack_binary_rock(args.rock, args.namespace, args.version, args.sign, function()
+ local name, version = do_build(args.rock, args.namespace, args.version, opts)
+ if name and args.no_doc then
+ util.remove_doc_dir(name, version)
+ end
+ return name, version
+ end)
+ end
+
+ local name, version, skip = do_build(args.rock, args.namespace, args.version, opts)
+ if not name then
+ return nil, version
+ end
+ if skip == "skip" then
+ return name, version
+ end
+
+ if args.no_doc then
+ util.remove_doc_dir(name, version)
+ end
+
+ if opts.build_only_deps then
+ util.printout("Stopping after installing dependencies for " ..name.." "..version)
+ util.printout()
+ else
+ if (not args.keep) and not cfg.keep_other_versions then
+ local ok, err, warn = remove.remove_other_versions(name, version, args.force, args.force_fast)
+ if not ok then
+ return nil, err
+ elseif warn then
+ util.printerr(err)
+ end
+ end
+ end
+
+ if opts.deps_mode ~= "none" then
+ writer.check_dependencies(nil, deps.get_deps_mode(args))
+ end
+ return name, version
+end
+
+cmd_build.needs_lock = function(args)
+ if args.pack_binary_rock then
+ return false
+ end
+ return true
+end
+
+return cmd_build
diff --git a/src/luarocks/cmd/config.lua b/src/luarocks/cmd/config.lua
new file mode 100644
index 0000000..d67711a
--- /dev/null
+++ b/src/luarocks/cmd/config.lua
@@ -0,0 +1,392 @@
+--- Module implementing the LuaRocks "config" command.
+-- Queries information about the LuaRocks configuration.
+local config_cmd = {}
+
+local persist = require("luarocks.persist")
+local config = require("luarocks.config")
+local cfg = require("luarocks.core.cfg")
+local util = require("luarocks.util")
+local deps = require("luarocks.deps")
+local dir = require("luarocks.dir")
+local fs = require("luarocks.fs")
+local json = require("luarocks.vendor.dkjson")
+
+function config_cmd.add_to_parser(parser)
+ local cmd = parser:command("config", [[
+Query information about the LuaRocks configuration.
+
+* 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)
+
+ Examples:
+ luarocks config variables.LUA_INCDIR
+ luarocks config lua_version
+
+* 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.
+
+ * `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 (equivalent to passing --lua-version).
+
+ Examples:
+ luarocks config variables.OPENSSL_DIR /usr/local/openssl
+ luarocks config lua_dir /usr/local
+ luarocks config lua_version 5.3
+
+* 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]], util.see_also([[
+ https://github.com/luarocks/luarocks/wiki/Config-file-format
+ for detailed information on the LuaRocks config file format.
+]]))
+ :summary("Query information about the LuaRocks configuration.")
+
+ cmd:argument("key", "The configuration key.")
+ :args("?")
+ cmd:argument("value", "The configuration value.")
+ :args("?")
+
+ cmd:option("--scope", "The scope indicates which config file should be rewritten.\n"..
+ '* Using a wrapper created with `luarocks init`, the default is "project".\n'..
+ '* Using --local (or when `local_by_default` is `true`), the default is "user".\n'..
+ '* Otherwise, the default is "system".')
+ :choices({"system", "user", "project"})
+ cmd:flag("--unset", "Delete the key from the configuration file.")
+ cmd:flag("--json", "Output as JSON.")
+
+ -- Deprecated flags
+ cmd:flag("--lua-incdir"):hidden(true)
+ cmd:flag("--lua-libdir"):hidden(true)
+ cmd:flag("--lua-ver"):hidden(true)
+ cmd:flag("--system-config"):hidden(true)
+ cmd:flag("--user-config"):hidden(true)
+ cmd:flag("--rock-trees"):hidden(true)
+end
+
+local function config_file(conf)
+ print(dir.normalize(conf.file))
+ if conf.found then
+ return true
+ else
+ return nil, "file not found"
+ end
+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)
+ 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 config.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 file_name = cfg.config_files[scope].file
+
+ local tbl, err = persist.load_config_file_if_basic(file_name, 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
+ v = val
+ end
+ t[k] = v
+ keys[var] = v
+ end
+ return true
+ end, function(p, k)
+ p[k] = {}
+ end)
+ end
+
+ local ok, err = fs.make_dir(dir.dir_name(file_name))
+ if not ok then
+ return nil, err
+ end
+
+ ok, err = persist.save_from_table(file_name, 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
+ if type(val) == "string" then
+ print(("\t%s = %q"):format(var, val))
+ else
+ print(("\t%s = %s"):format(var, tostring(val)))
+ end
+ end
+ end
+ print(do_unset and "from" or "to")
+ print("\t" .. file_name)
+ return true
+ else
+ return nil, err
+ end
+end
+
+local function get_scope(args)
+ return args.scope
+ or (args["local"] and "user")
+ or (args.project_tree and "project")
+ or (cfg.local_by_default and "user")
+ or (fs.is_writable(cfg.config_files["system"].file) and "system")
+ or "user"
+end
+
+local function report_on_lua_incdir_config(value, lua_version)
+ local variables = {
+ ["LUA_DIR"] = cfg.variables.LUA_DIR,
+ ["LUA_BINDIR"] = cfg.variables.LUA_BINDIR,
+ ["LUA_INCDIR"] = value,
+ ["LUA_LIBDIR"] = cfg.variables.LUA_LIBDIR,
+ ["LUA"] = cfg.variables.LUA,
+ }
+
+ local ok, err = deps.check_lua_incdir(variables, lua_version)
+ if not ok then
+ util.printerr()
+ util.warning((err:gsub(" You can use.*", "")))
+ end
+ return ok
+end
+
+local function report_on_lua_libdir_config(value, lua_version)
+ local variables = {
+ ["LUA_DIR"] = cfg.variables.LUA_DIR,
+ ["LUA_BINDIR"] = cfg.variables.LUA_BINDIR,
+ ["LUA_INCDIR"] = cfg.variables.LUA_INCDIR,
+ ["LUA_LIBDIR"] = value,
+ ["LUA"] = cfg.variables.LUA,
+ }
+
+ local ok, err, _, err_files = deps.check_lua_libdir(variables, lua_version)
+ if not ok then
+ util.printerr()
+ util.warning((err:gsub(" You can use.*", "")))
+ util.printerr("Tried:")
+ for _, l in pairs(err_files or {}) do
+ for _, d in ipairs(l) do
+ util.printerr("\t" .. d)
+ end
+ end
+ end
+ return ok
+end
+
+local function warn_bad_c_config()
+ util.printerr()
+ util.printerr("LuaRocks may not work correctly when building C modules using this configuration.")
+ util.printerr()
+end
+
+--- Driver function for "config" command.
+-- @return boolean: True if succeeded, nil on errors.
+function config_cmd.command(args)
+ local lua_version = args.lua_version or cfg.lua_version
+
+ deps.check_lua_incdir(cfg.variables, lua_version)
+ deps.check_lua_libdir(cfg.variables, lua_version)
+
+ -- deprecated flags
+ if args.lua_incdir then
+ print(cfg.variables.LUA_INCDIR)
+ return true
+ end
+ if args.lua_libdir then
+ print(cfg.variables.LUA_LIBDIR)
+ return true
+ end
+ if args.lua_ver then
+ print(cfg.lua_version)
+ return true
+ end
+ if args.system_config then
+ return config_file(cfg.config_files.system)
+ end
+ if args.user_config then
+ return config_file(cfg.config_files.user)
+ end
+ if args.rock_trees then
+ for _, tree in ipairs(cfg.rocks_trees) do
+ if type(tree) == "string" then
+ util.printout(dir.normalize(tree))
+ else
+ local name = tree.name and "\t"..tree.name or ""
+ util.printout(dir.normalize(tree.root)..name)
+ end
+ end
+ return true
+ end
+
+ if args.key == "lua_version" and args.value then
+ local scope = get_scope(args)
+ 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 location = cfg.config_files[scope]
+ if (not location) or (not location.file) then
+ return nil, "could not get config file location for " .. tostring(scope) .. " scope"
+ end
+
+ local prefix = dir.dir_name(location.file)
+ local ok, err = persist.save_default_lua_version(prefix, args.value)
+ if not ok then
+ return nil, "could not set default Lua version: " .. err
+ end
+ print("Lua version will default to " .. args.value .. " in " .. prefix)
+ end
+
+ if args.key == "lua_dir" and args.value then
+ local scope = get_scope(args)
+ 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,
+ ["variables.LUA"] = cfg.variables.LUA,
+ }
+ if args.lua_version then
+ local prefix = dir.dir_name(cfg.config_files[scope].file)
+ persist.save_default_lua_version(prefix, args.lua_version)
+ end
+ local ok, err = write_entries(keys, scope, args.unset)
+ if ok then
+ local inc_ok = report_on_lua_incdir_config(cfg.variables.LUA_INCDIR, lua_version)
+ local lib_ok = ok and report_on_lua_libdir_config(cfg.variables.LUA_LIBDIR, lua_version)
+ if not (inc_ok and lib_ok) then
+ warn_bad_c_config()
+ end
+ end
+
+ return ok, err
+ end
+
+ if args.key then
+ if args.key:match("^[A-Z]") then
+ args.key = "variables." .. args.key
+ end
+
+ if args.value or args.unset then
+ local scope = get_scope(args)
+
+ local ok, err = write_entries({ [args.key] = args.value or args.unset }, scope, args.unset)
+
+ if ok then
+ if args.key == "variables.LUA_INCDIR" then
+ local ok = report_on_lua_incdir_config(args.value, lua_version)
+ if not ok then
+ warn_bad_c_config()
+ end
+ elseif args.key == "variables.LUA_LIBDIR" then
+ local ok = report_on_lua_libdir_config(args.value, lua_version)
+ if not ok then
+ warn_bad_c_config()
+ end
+ end
+ end
+
+ return ok, err
+ else
+ return print_entry(args.key, cfg, args.json)
+ end
+ end
+
+ if args.json then
+ return print_json(config.get_config_for_display(cfg))
+ else
+ print(config.to_string(cfg))
+ return true
+ end
+end
+
+return config_cmd
diff --git a/src/luarocks/cmd/doc.lua b/src/luarocks/cmd/doc.lua
new file mode 100644
index 0000000..a311700
--- /dev/null
+++ b/src/luarocks/cmd/doc.lua
@@ -0,0 +1,153 @@
+
+--- Module implementing the LuaRocks "doc" command.
+-- Shows documentation for an installed rock.
+local doc = {}
+
+local util = require("luarocks.util")
+local queries = require("luarocks.queries")
+local search = require("luarocks.search")
+local path = require("luarocks.path")
+local dir = require("luarocks.dir")
+local fetch = require("luarocks.fetch")
+local fs = require("luarocks.fs")
+local download = require("luarocks.download")
+
+function doc.add_to_parser(parser)
+ local cmd = parser:command("doc", "Show documentation for an installed rock.\n\n"..
+ "Without any flags, tries to load the documentation using a series of heuristics.\n"..
+ "With flags, return only the desired information.", util.see_also([[
+ For more information about a rock, see the 'show' command.
+]]))
+ :summary("Show documentation for an installed rock.")
+
+ cmd:argument("rock", "Name of the rock.")
+ :action(util.namespaced_name_action)
+ cmd:argument("version", "Version of the rock.")
+ :args("?")
+
+ cmd:flag("--home", "Open the home page of project.")
+ cmd:flag("--list", "List documentation files only.")
+ cmd:flag("--porcelain", "Produce machine-friendly output.")
+end
+
+local function show_homepage(homepage, name, namespace, version)
+ if not homepage then
+ return nil, "No 'homepage' field in rockspec for "..util.format_rock_name(name, namespace, version)
+ end
+ util.printout("Opening "..homepage.." ...")
+ fs.browser(homepage)
+ return true
+end
+
+local function try_to_open_homepage(name, namespace, version)
+ local temp_dir, err = fs.make_temp_dir("doc-"..name.."-"..(version or ""))
+ if not temp_dir then
+ return nil, "Failed creating temporary directory: "..err
+ end
+ util.schedule_function(fs.delete, temp_dir)
+ local ok, err = fs.change_dir(temp_dir)
+ if not ok then return nil, err end
+ local filename, err = download.download("rockspec", name, namespace, version)
+ if not filename then return nil, err end
+ local rockspec, err = fetch.load_local_rockspec(filename)
+ if not rockspec then return nil, err end
+ fs.pop_dir()
+ local descript = rockspec.description or {}
+ return show_homepage(descript.homepage, name, namespace, version)
+end
+
+--- Driver function for "doc" command.
+-- @return boolean: True if succeeded, nil on errors.
+function doc.command(args)
+ local query = queries.new(args.rock, args.namespace, args.version)
+ local iname, iversion, repo = search.pick_installed_rock(query, args.tree)
+ if not iname then
+ local rock = util.format_rock_name(args.rock, args.namespace, args.version)
+ util.printout(rock.." is not installed. Looking for it in the rocks servers...")
+ return try_to_open_homepage(args.rock, args.namespace, args.version)
+ end
+ local name, version = iname, iversion
+
+ local rockspec, err = fetch.load_local_rockspec(path.rockspec_file(name, version, repo))
+ if not rockspec then return nil,err end
+ local descript = rockspec.description or {}
+
+ if args.home then
+ return show_homepage(descript.homepage, name, args.namespace, version)
+ end
+
+ local directory = path.install_dir(name, version, repo)
+
+ local docdir
+ local directories = { "doc", "docs" }
+ for _, d in ipairs(directories) do
+ local dirname = dir.path(directory, d)
+ if fs.is_dir(dirname) then
+ docdir = dirname
+ break
+ end
+ end
+ if not docdir then
+ if descript.homepage and not args.list then
+ util.printout("Local documentation directory not found -- opening "..descript.homepage.." ...")
+ fs.browser(descript.homepage)
+ return true
+ end
+ return nil, "Documentation directory not found for "..name.." "..version
+ end
+
+ docdir = dir.normalize(docdir)
+ local files = fs.find(docdir)
+ local htmlpatt = "%.html?$"
+ local extensions = { htmlpatt, "%.md$", "%.txt$", "%.textile$", "" }
+ local basenames = { "index", "readme", "manual" }
+
+ local porcelain = args.porcelain
+ if #files > 0 then
+ util.title("Documentation files for "..name.." "..version, porcelain)
+ if porcelain then
+ for _, file in ipairs(files) do
+ util.printout(docdir.."/"..file)
+ end
+ else
+ util.printout(docdir.."/")
+ for _, file in ipairs(files) do
+ util.printout("\t"..file)
+ end
+ end
+ end
+
+ if args.list then
+ return true
+ end
+
+ for _, extension in ipairs(extensions) do
+ for _, basename in ipairs(basenames) do
+ local filename = basename..extension
+ local found
+ for _, file in ipairs(files) do
+ if file:lower():match(filename) and ((not found) or #file < #found) then
+ found = file
+ end
+ end
+ if found then
+ local pathname = dir.path(docdir, found)
+ util.printout()
+ util.printout("Opening "..pathname.." ...")
+ util.printout()
+ local ok = fs.browser(pathname)
+ if not ok and not pathname:match(htmlpatt) then
+ local fd = io.open(pathname, "r")
+ util.printout(fd:read("*a"))
+ fd:close()
+ end
+ return true
+ end
+ end
+ end
+
+ return true
+end
+
+
+return doc
diff --git a/src/luarocks/cmd/download.lua b/src/luarocks/cmd/download.lua
new file mode 100644
index 0000000..eae8243
--- /dev/null
+++ b/src/luarocks/cmd/download.lua
@@ -0,0 +1,51 @@
+
+--- Module implementing the luarocks "download" command.
+-- Download a rock from the repository.
+local cmd_download = {}
+
+local util = require("luarocks.util")
+local download = require("luarocks.download")
+
+function cmd_download.add_to_parser(parser)
+ local cmd = parser:command("download", "Download a specific rock file from a rocks server.", util.see_also())
+
+ cmd:argument("name", "Name of the rock.")
+ :args("?")
+ :action(util.namespaced_name_action)
+ cmd:argument("version", "Version of the rock.")
+ :args("?")
+
+ cmd:flag("--all", "Download all files if there are multiple matches.")
+ cmd:mutex(
+ cmd:flag("--source", "Download .src.rock if available."),
+ cmd:flag("--rockspec", "Download .rockspec if available."),
+ cmd:option("--arch", "Download rock for a specific architecture."))
+ cmd:flag("--check-lua-versions", "If the rock can't be found, check repository "..
+ "and report if it is available for another Lua version.")
+end
+
+--- Driver function for the "download" command.
+-- @return boolean or (nil, string): true if successful or nil followed
+-- by an error message.
+function cmd_download.command(args)
+ if not args.name and not args.all then
+ return nil, "Argument missing. "..util.see_help("download")
+ end
+
+ args.name = args.name or ""
+
+ local arch
+
+ if args.source then
+ arch = "src"
+ elseif args.rockspec then
+ arch = "rockspec"
+ elseif args.arch then
+ arch = args.arch
+ end
+
+ local dl, err = download.download(arch, args.name, args.namespace, args.version, args.all, args.check_lua_versions)
+ return dl and true, err
+end
+
+return cmd_download
diff --git a/src/luarocks/cmd/init.lua b/src/luarocks/cmd/init.lua
new file mode 100644
index 0000000..b5359c9
--- /dev/null
+++ b/src/luarocks/cmd/init.lua
@@ -0,0 +1,219 @@
+
+local init = {}
+
+local cfg = require("luarocks.core.cfg")
+local fs = require("luarocks.fs")
+local path = require("luarocks.path")
+local deps = require("luarocks.deps")
+local dir = require("luarocks.dir")
+local util = require("luarocks.util")
+local persist = require("luarocks.persist")
+local write_rockspec = require("luarocks.cmd.write_rockspec")
+
+function init.add_to_parser(parser)
+ local cmd = parser:command("init", "Initialize a directory for a Lua project using LuaRocks.", util.see_also())
+
+ cmd:argument("name", "The project name.")
+ :args("?")
+ cmd:argument("version", "An optional project version.")
+ :args("?")
+ cmd:option("--wrapper-dir", "Location where the 'lua' and 'luarocks' wrapper scripts " ..
+ "should be generated; if not given, the current directory is used as a default.")
+ cmd:flag("--reset", "Delete any .luarocks/config-5.x.lua and ./lua and generate new ones.")
+ cmd:flag("--no-wrapper-scripts", "Do not generate wrapper ./lua and ./luarocks launcher scripts.")
+ cmd:flag("--no-gitignore", "Do not generate a .gitignore file.")
+
+ cmd:group("Options for specifying rockspec data", write_rockspec.cmd_options(cmd))
+end
+
+local function gitignore_path(pwd, wrapper_dir, filename)
+ local norm_cur = fs.absolute_name(pwd)
+ local norm_file = fs.absolute_name(dir.path(wrapper_dir, filename))
+ if norm_file:sub(1, #norm_cur) == norm_cur then
+ return norm_file:sub(#norm_cur + 2)
+ else
+ return filename
+ end
+end
+
+local function write_gitignore(entries)
+ local gitignore = ""
+ local fd = io.open(".gitignore", "r")
+ if fd then
+ gitignore = fd:read("*a")
+ fd:close()
+ gitignore = "\n" .. gitignore .. "\n"
+ end
+
+ fd = io.open(".gitignore", gitignore and "a" or "w")
+ if fd then
+ for _, entry in ipairs(entries) do
+ entry = "/" .. entry
+ if not gitignore:find("\n"..entry.."\n", 1, true) then
+ fd:write(entry.."\n")
+ end
+ end
+ fd:close()
+ end
+end
+
+local function inject_tree(tree)
+ path.use_tree(tree)
+ local tree_set = false
+ for _, t in ipairs(cfg.rocks_trees) do
+ if type(t) == "table" then
+ if t.name == "project" then
+ t.root = tree
+ tree_set = true
+ end
+ end
+ end
+ if not tree_set then
+ table.insert(cfg.rocks_trees, 1, { name = "project", root = tree })
+ end
+end
+
+local function write_wrapper_scripts(wrapper_dir, luarocks_wrapper, lua_wrapper)
+ local tree = dir.path(fs.current_dir(), "lua_modules")
+
+ fs.make_dir(wrapper_dir)
+
+ luarocks_wrapper = dir.path(wrapper_dir, luarocks_wrapper)
+ if not fs.exists(luarocks_wrapper) then
+ util.printout("Preparing " .. luarocks_wrapper .. " ...")
+ fs.wrap_script(arg[0], luarocks_wrapper, "none", nil, nil, "--project-tree", tree)
+ else
+ util.printout(luarocks_wrapper .. " already exists. Not overwriting it!")
+ end
+
+ lua_wrapper = dir.path(wrapper_dir, lua_wrapper)
+ local write_lua_wrapper = true
+ if fs.exists(lua_wrapper) then
+ if not util.lua_is_wrapper(lua_wrapper) then
+ util.printout(lua_wrapper .. " already exists and does not look like a wrapper script. Not overwriting.")
+ write_lua_wrapper = false
+ end
+ end
+
+ if write_lua_wrapper then
+ if util.check_lua_version(cfg.variables.LUA, cfg.lua_version) then
+ util.printout("Preparing " .. lua_wrapper .. " for version " .. cfg.lua_version .. "...")
+
+ -- Inject tree so it shows up as a lookup path in the wrappers
+ inject_tree(tree)
+
+ fs.wrap_script(nil, lua_wrapper, "all")
+ else
+ util.warning("No Lua interpreter detected for version " .. cfg.lua_version .. ". Not creating " .. lua_wrapper)
+ end
+ end
+end
+
+--- Driver function for "init" command.
+-- @return boolean: True if succeeded, nil on errors.
+function init.command(args)
+ local do_gitignore = not args.no_gitignore
+ local do_wrapper_scripts = not args.no_wrapper_scripts
+ local wrapper_dir = args.wrapper_dir or "."
+
+ local pwd = fs.current_dir()
+
+ if not args.name then
+ args.name = dir.base_name(pwd)
+ if args.name == "/" then
+ return nil, "When running from the root directory, please specify the <name> argument"
+ end
+ end
+
+ util.title("Initializing project '" .. args.name .. "' for Lua " .. cfg.lua_version .. " ...")
+
+ local ok, err = deps.check_lua_incdir(cfg.variables)
+ if not ok then
+ return nil, err
+ end
+
+ local has_rockspec = false
+ for file in fs.dir() do
+ if file:match("%.rockspec$") then
+ has_rockspec = true
+ break
+ end
+ end
+
+ if not has_rockspec then
+ args.version = args.version or "dev"
+ args.location = pwd
+ local ok, err = write_rockspec.command(args)
+ if not ok then
+ util.printerr(err)
+ end
+ end
+
+ local ext = cfg.wrapper_suffix
+ local luarocks_wrapper = "luarocks" .. ext
+ local lua_wrapper = "lua" .. ext
+
+ if do_gitignore then
+ util.printout("Adding entries to .gitignore ...")
+ local ignores = { "lua_modules", ".luarocks" }
+ if do_wrapper_scripts then
+ table.insert(ignores, 1, gitignore_path(pwd, wrapper_dir, luarocks_wrapper))
+ table.insert(ignores, 2, gitignore_path(pwd, wrapper_dir, lua_wrapper))
+ end
+ write_gitignore(ignores)
+ end
+
+ util.printout("Preparing ./.luarocks/ ...")
+ fs.make_dir(".luarocks")
+ local config_file = ".luarocks/config-" .. cfg.lua_version .. ".lua"
+
+ if args.reset then
+ if do_wrapper_scripts then
+ fs.delete(fs.absolute_name(dir.path(wrapper_dir, lua_wrapper)))
+ end
+ fs.delete(fs.absolute_name(config_file))
+ end
+
+ local config_tbl, err = persist.load_config_file_if_basic(config_file, cfg)
+ if config_tbl then
+ local varnames = {
+ "LUA_DIR",
+ "LUA_INCDIR",
+ "LUA_LIBDIR",
+ "LUA_BINDIR",
+ "LUA",
+ }
+ for _, varname in ipairs(varnames) do
+ if cfg.variables[varname] then
+ config_tbl.variables = config_tbl.variables or {}
+ config_tbl.variables[varname] = cfg.variables[varname]
+ end
+ end
+ local ok, err = persist.save_from_table(config_file, config_tbl)
+ if ok then
+ util.printout("Wrote " .. config_file)
+ else
+ util.printout("Failed writing " .. config_file .. ": " .. err)
+ end
+ else
+ util.printout("Will not attempt to overwrite " .. config_file)
+ end
+
+ ok, err = persist.save_default_lua_version(".luarocks", cfg.lua_version)
+ if not ok then
+ util.printout("Failed setting default Lua version: " .. err)
+ end
+
+ util.printout("Preparing ./lua_modules/ ...")
+ fs.make_dir("lua_modules/lib/luarocks/rocks-" .. cfg.lua_version)
+
+ if do_wrapper_scripts then
+ write_wrapper_scripts(wrapper_dir, luarocks_wrapper, lua_wrapper)
+ end
+
+ return true
+end
+
+init.needs_lock = function() return true end
+
+return init
diff --git a/src/luarocks/cmd/install.lua b/src/luarocks/cmd/install.lua
new file mode 100644
index 0000000..05e31fe
--- /dev/null
+++ b/src/luarocks/cmd/install.lua
@@ -0,0 +1,271 @@
+--- Module implementing the LuaRocks "install" command.
+-- Installs binary rocks.
+local install = {}
+
+local dir = require("luarocks.dir")
+local path = require("luarocks.path")
+local repos = require("luarocks.repos")
+local fetch = require("luarocks.fetch")
+local util = require("luarocks.util")
+local fs = require("luarocks.fs")
+local deps = require("luarocks.deps")
+local writer = require("luarocks.manif.writer")
+local remove = require("luarocks.remove")
+local search = require("luarocks.search")
+local queries = require("luarocks.queries")
+local cfg = require("luarocks.core.cfg")
+
+function install.add_to_parser(parser)
+ local cmd = parser:command("install", "Install a rock.", util.see_also()) -- luacheck: ignore 431
+
+ cmd:argument("rock", "The name of a rock to be fetched from a repository "..
+ "or a filename of a locally available rock.")
+ :action(util.namespaced_name_action)
+ cmd:argument("version", "Version of the rock.")
+ :args("?")
+
+ cmd:flag("--keep", "Do not remove previously installed versions of the "..
+ "rock after building a new one. This behavior can be made permanent by "..
+ "setting keep_other_versions=true in the configuration file.")
+ cmd:flag("--force", "If --keep is not specified, force removal of "..
+ "previously installed versions if it would break dependencies. "..
+ "If rock is already installed, reinstall it anyway.")
+ cmd:flag("--force-fast", "Like --force, but performs a forced removal "..
+ "without reporting dependency issues.")
+ cmd:flag("--only-deps --deps-only", "Install only the dependencies of the rock.")
+ cmd:flag("--no-doc", "Install the rock without its documentation.")
+ cmd:flag("--verify", "Verify signature of the rockspec or src.rock being "..
+ "built. If the rockspec or src.rock is being downloaded, LuaRocks will "..
+ "attempt to download the signature as well. Otherwise, the signature "..
+ "file should be already available locally in the same directory.\n"..
+ "You need the signer’s public key in your local keyring for this "..
+ "option to work properly.")
+ cmd:flag("--check-lua-versions", "If the rock can't be found, check repository "..
+ "and report if it is available for another Lua version.")
+ util.deps_mode_option(cmd)
+ cmd:flag("--no-manifest", "Skip creating/updating the manifest")
+ cmd:flag("--pin", "If the installed rock is a Lua module, create a "..
+ "luarocks.lock file listing the exact versions of each dependency found for "..
+ "this rock (recursively), and store it in the rock's directory. "..
+ "Ignores any existing luarocks.lock file in the rock's sources.")
+ -- luarocks build options
+ parser:flag("--pack-binary-rock"):hidden(true)
+ parser:option("--branch"):hidden(true)
+ parser:flag("--sign"):hidden(true)
+end
+
+install.opts = util.opts_table("install.opts", {
+ namespace = "string?",
+ keep = "boolean",
+ force = "boolean",
+ force_fast = "boolean",
+ no_doc = "boolean",
+ deps_mode = "string",
+ verify = "boolean",
+})
+
+--- Install a binary rock.
+-- @param rock_file string: local or remote filename of a rock.
+-- @param opts table: installation options
+-- @return (string, string) or (nil, string, [string]): Name and version of
+-- installed rock if succeeded or nil and an error message followed by an error code.
+function install.install_binary_rock(rock_file, opts)
+ assert(type(rock_file) == "string")
+ assert(opts:type() == "install.opts")
+
+ local namespace = opts.namespace
+ local deps_mode = opts.deps_mode
+
+ local name, version, arch = path.parse_name(rock_file)
+ if not name then
+ return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'."
+ end
+
+ if arch ~= "all" and arch ~= cfg.arch then
+ return nil, "Incompatible architecture "..arch, "arch"
+ end
+ if repos.is_installed(name, version) then
+ if not (opts.force or opts.force_fast) then
+ util.printout(name .. " " .. version .. " is already installed in " .. path.root_dir(cfg.root_dir))
+ util.printout("Use --force to reinstall.")
+ return name, version
+ end
+ repos.delete_version(name, version, opts.deps_mode)
+ end
+
+ local install_dir = path.install_dir(name, version)
+
+ local rollback = util.schedule_function(function()
+ fs.delete(install_dir)
+ fs.remove_dir_if_empty(path.versions_dir(name))
+ end)
+
+ local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, install_dir, opts.verify)
+ if not ok then return nil, err, errcode end
+
+ local rockspec, err = fetch.load_rockspec(path.rockspec_file(name, version))
+ if err then
+ return nil, "Failed loading rockspec for installed package: "..err, errcode
+ end
+
+ if opts.deps_mode ~= "none" then
+ ok, err, errcode = deps.check_external_deps(rockspec, "install")
+ if err then return nil, err, errcode end
+ end
+
+ -- For compatibility with .rock files built with LuaRocks 1
+ if not fs.exists(path.rock_manifest_file(name, version)) then
+ ok, err = writer.make_rock_manifest(name, version)
+ if err then return nil, err end
+ end
+
+ if namespace then
+ ok, err = writer.make_namespace_file(name, version, namespace)
+ if err then return nil, err end
+ end
+
+ if deps_mode ~= "none" then
+ local deplock_dir = fs.exists(dir.path(".", "luarocks.lock"))
+ and "."
+ or install_dir
+ ok, err, errcode = deps.fulfill_dependencies(rockspec, "dependencies", deps_mode, opts.verify, deplock_dir)
+ if err then return nil, err, errcode end
+ end
+
+ ok, err = repos.deploy_files(name, version, repos.should_wrap_bin_scripts(rockspec), deps_mode)
+ if err then return nil, err end
+
+ util.remove_scheduled_function(rollback)
+ rollback = util.schedule_function(function()
+ repos.delete_version(name, version, deps_mode)
+ end)
+
+ ok, err = repos.run_hook(rockspec, "post_install")
+ if err then return nil, err end
+
+ util.announce_install(rockspec)
+ util.remove_scheduled_function(rollback)
+ return name, version
+end
+
+--- Installs the dependencies of a binary rock.
+-- @param rock_file string: local or remote filename of a rock.
+-- @param opts table: installation options
+-- @return (string, string) or (nil, string, [string]): Name and version of
+-- the rock whose dependencies were installed if succeeded or nil and an error message
+-- followed by an error code.
+function install.install_binary_rock_deps(rock_file, opts)
+ assert(type(rock_file) == "string")
+ assert(opts:type() == "install.opts")
+
+ local name, version, arch = path.parse_name(rock_file)
+ if not name then
+ return nil, "Filename "..rock_file.." does not match format 'name-version-revision.arch.rock'."
+ end
+
+ if arch ~= "all" and arch ~= cfg.arch then
+ return nil, "Incompatible architecture "..arch, "arch"
+ end
+
+ local install_dir = path.install_dir(name, version)
+
+ local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, install_dir, opts.verify)
+ if not ok then return nil, err, errcode end
+
+ local rockspec, err = fetch.load_rockspec(path.rockspec_file(name, version))
+ if err then
+ return nil, "Failed loading rockspec for installed package: "..err, errcode
+ end
+
+ ok, err, errcode = deps.fulfill_dependencies(rockspec, "dependencies", opts.deps_mode, opts.verify, install_dir)
+ if err then return nil, err, errcode end
+
+ util.printout()
+ util.printout("Successfully installed dependencies for " ..name.." "..version)
+
+ return name, version
+end
+
+local function install_rock_file_deps(filename, opts)
+ assert(opts:type() == "install.opts")
+
+ local name, version = install.install_binary_rock_deps(filename, opts)
+ if not name then return nil, version end
+
+ writer.check_dependencies(nil, opts.deps_mode)
+ return name, version
+end
+
+local function install_rock_file(filename, opts)
+ assert(type(filename) == "string")
+ assert(opts:type() == "install.opts")
+
+ local name, version = install.install_binary_rock(filename, opts)
+ if not name then return nil, version end
+
+ if opts.no_doc then
+ util.remove_doc_dir(name, version)
+ end
+
+ if (not opts.keep) and not cfg.keep_other_versions then
+ local ok, err, warn = remove.remove_other_versions(name, version, opts.force, opts.force_fast)
+ if not ok then
+ return nil, err
+ elseif warn then
+ util.printerr(err)
+ end
+ end
+
+ writer.check_dependencies(nil, opts.deps_mode)
+ return name, version
+end
+
+--- Driver function for the "install" command.
+-- If an URL or pathname to a binary rock is given, fetches and installs it.
+-- If a rockspec or a source rock is given, forwards the request to the "build"
+-- command.
+-- If a package name is given, forwards the request to "search" and,
+-- if returned a result, installs the matching rock.
+-- @return boolean or (nil, string, exitcode): True if installation was
+-- successful, nil and an error message otherwise. exitcode is optionally returned.
+function install.command(args)
+ if args.rock:match("%.rockspec$") or args.rock:match("%.src%.rock$") then
+ local build = require("luarocks.cmd.build")
+ return build.command(args)
+ elseif args.rock:match("%.rock$") then
+ local deps_mode = deps.get_deps_mode(args)
+ local opts = install.opts({
+ namespace = args.namespace,
+ keep = not not args.keep,
+ force = not not args.force,
+ force_fast = not not args.force_fast,
+ no_doc = not not args.no_doc,
+ deps_mode = deps_mode,
+ verify = not not args.verify,
+ })
+ if args.only_deps then
+ return install_rock_file_deps(args.rock, opts)
+ else
+ return install_rock_file(args.rock, opts)
+ end
+ else
+ local url, err = search.find_rock_checking_lua_versions(
+ queries.new(args.rock, args.namespace, args.version),
+ args.check_lua_versions)
+ if not url then
+ return nil, err
+ end
+ util.printout("Installing "..url)
+ args.rock = url
+ return install.command(args)
+ end
+end
+
+install.needs_lock = function(args)
+ if args.pack_binary_rock then
+ return false
+ end
+ return true
+end
+
+return install
diff --git a/src/luarocks/cmd/lint.lua b/src/luarocks/cmd/lint.lua
new file mode 100644
index 0000000..738503c
--- /dev/null
+++ b/src/luarocks/cmd/lint.lua
@@ -0,0 +1,50 @@
+
+--- Module implementing the LuaRocks "lint" command.
+-- Utility function that checks syntax of the rockspec.
+local lint = {}
+
+local util = require("luarocks.util")
+local download = require("luarocks.download")
+local fetch = require("luarocks.fetch")
+
+function lint.add_to_parser(parser)
+ local cmd = parser:command("lint", "Check syntax of a rockspec.\n\n"..
+ "Returns success if the text of the rockspec is syntactically correct, else failure.",
+ util.see_also())
+ :summary("Check syntax of a rockspec.")
+
+ cmd:argument("rockspec", "The rockspec to check.")
+end
+
+function lint.command(args)
+
+ local filename = args.rockspec
+ if not filename:match(".rockspec$") then
+ local err
+ filename, err = download.download("rockspec", filename:lower())
+ if not filename then
+ return nil, err
+ end
+ end
+
+ local rs, err = fetch.load_local_rockspec(filename)
+ if not rs then
+ return nil, "Failed loading rockspec: "..err
+ end
+
+ local ok = true
+
+ -- This should have been done in the type checker,
+ -- but it would break compatibility of other commands.
+ -- Making 'lint' alone be stricter shouldn't be a problem,
+ -- because extra-strict checks is what lint-type commands
+ -- are all about.
+ if not rs.description or not rs.description.license then
+ util.printerr("Rockspec has no description.license field.")
+ ok = false
+ end
+
+ return ok, ok or filename.." failed consistency checks."
+end
+
+return lint
diff --git a/src/luarocks/cmd/list.lua b/src/luarocks/cmd/list.lua
new file mode 100644
index 0000000..7b2682f
--- /dev/null
+++ b/src/luarocks/cmd/list.lua
@@ -0,0 +1,96 @@
+
+--- Module implementing the LuaRocks "list" command.
+-- Lists currently installed rocks.
+local list = {}
+
+local search = require("luarocks.search")
+local queries = require("luarocks.queries")
+local vers = require("luarocks.core.vers")
+local cfg = require("luarocks.core.cfg")
+local util = require("luarocks.util")
+local path = require("luarocks.path")
+
+function list.add_to_parser(parser)
+ local cmd = parser:command("list", "List currently installed rocks.", util.see_also())
+
+ cmd:argument("filter", "A substring of a rock name to filter by.")
+ :args("?")
+ cmd:argument("version", "Rock version to filter by.")
+ :args("?")
+
+ cmd:flag("--outdated", "List only rocks for which there is a higher "..
+ "version available in the rocks server.")
+ cmd:flag("--porcelain", "Produce machine-friendly output.")
+end
+
+local function check_outdated(trees, query)
+ local results_installed = {}
+ for _, tree in ipairs(trees) do
+ search.local_manifest_search(results_installed, path.rocks_dir(tree), query)
+ end
+ local outdated = {}
+ for name, versions in util.sortedpairs(results_installed) do
+ versions = util.keys(versions)
+ table.sort(versions, vers.compare_versions)
+ local latest_installed = versions[1]
+
+ local query_available = queries.new(name:lower())
+ local results_available, err = search.search_repos(query_available)
+
+ if results_available[name] then
+ local available_versions = util.keys(results_available[name])
+ table.sort(available_versions, vers.compare_versions)
+ local latest_available = available_versions[1]
+ local latest_available_repo = results_available[name][latest_available][1].repo
+
+ if vers.compare_versions(latest_available, latest_installed) then
+ table.insert(outdated, { name = name, installed = latest_installed, available = latest_available, repo = latest_available_repo })
+ end
+ end
+ end
+ return outdated
+end
+
+local function list_outdated(trees, query, porcelain)
+ util.title("Outdated rocks:", porcelain)
+ local outdated = check_outdated(trees, query)
+ for _, item in ipairs(outdated) do
+ if porcelain then
+ util.printout(item.name, item.installed, item.available, item.repo)
+ else
+ util.printout(item.name)
+ util.printout(" "..item.installed.." < "..item.available.." at "..item.repo)
+ util.printout()
+ end
+ end
+ return true
+end
+
+--- Driver function for "list" command.
+-- @return boolean: True if succeeded, nil on errors.
+function list.command(args)
+ local query = queries.new(args.filter and args.filter:lower() or "", args.namespace, args.version, true)
+ local trees = cfg.rocks_trees
+ local title = "Rocks installed for Lua "..cfg.lua_version
+ if args.tree then
+ trees = { args.tree }
+ title = title .. " in " .. args.tree
+ end
+
+ if args.outdated then
+ return list_outdated(trees, query, args.porcelain)
+ end
+
+ local results = {}
+ for _, tree in ipairs(trees) do
+ local ok, err, errcode = search.local_manifest_search(results, path.rocks_dir(tree), query)
+ if not ok and errcode ~= "open" then
+ util.warning(err)
+ end
+ end
+ util.title(title, args.porcelain)
+ search.print_result_tree(results, args.porcelain)
+ return true
+end
+
+return list
diff --git a/src/luarocks/cmd/make.lua b/src/luarocks/cmd/make.lua
new file mode 100644
index 0000000..0b3db27
--- /dev/null
+++ b/src/luarocks/cmd/make.lua
@@ -0,0 +1,163 @@
+
+--- Module implementing the LuaRocks "make" command.
+-- Builds sources in the current directory, but unlike "build",
+-- it does not fetch sources, etc., assuming everything is
+-- available in the current directory.
+local make = {}
+
+local build = require("luarocks.build")
+local util = require("luarocks.util")
+local cfg = require("luarocks.core.cfg")
+local fetch = require("luarocks.fetch")
+local pack = require("luarocks.pack")
+local remove = require("luarocks.remove")
+local deps = require("luarocks.deps")
+local writer = require("luarocks.manif.writer")
+
+function make.cmd_options(parser)
+ parser:flag("--no-install", "Do not install the rock.")
+ parser:flag("--no-doc", "Install the rock without its documentation.")
+ parser:flag("--pack-binary-rock", "Do not install rock. Instead, produce a "..
+ ".rock file with the contents of compilation in the current directory.")
+ parser:flag("--keep", "Do not remove previously installed versions of the "..
+ "rock after building a new one. This behavior can be made permanent by "..
+ "setting keep_other_versions=true in the configuration file.")
+ parser:flag("--force", "If --keep is not specified, force removal of "..
+ "previously installed versions if it would break dependencies. "..
+ "If rock is already installed, reinstall it anyway.")
+ parser:flag("--force-fast", "Like --force, but performs a forced removal "..
+ "without reporting dependency issues.")
+ parser:flag("--verify", "Verify signature of the rockspec or src.rock being "..
+ "built. If the rockspec or src.rock is being downloaded, LuaRocks will "..
+ "attempt to download the signature as well. Otherwise, the signature "..
+ "file should be already available locally in the same directory.\n"..
+ "You need the signer’s public key in your local keyring for this "..
+ "option to work properly.")
+ parser:flag("--sign", "To be used with --pack-binary-rock. Also produce a "..
+ "signature file for the generated .rock file.")
+ parser:flag("--check-lua-versions", "If the rock can't be found, check repository "..
+ "and report if it is available for another Lua version.")
+ parser:flag("--pin", "Pin the exact dependencies used for the rockspec"..
+ "being built into a luarocks.lock file in the current directory.")
+ parser:flag("--no-manifest", "Skip creating/updating the manifest")
+ parser:flag("--only-deps --deps-only", "Install only the dependencies of the rock.")
+ util.deps_mode_option(parser)
+end
+
+function make.add_to_parser(parser)
+ -- luacheck: push ignore 431
+ local cmd = parser:command("make", [[
+Builds sources in the current directory, but unlike "build", it does not fetch
+sources, etc., assuming everything is available in the current directory. If no
+argument is given, it looks for a rockspec in the current directory and in
+"rockspec/" and "rockspecs/" subdirectories, picking the rockspec with newest
+version or without version name. If rockspecs for different rocks are found or
+there are several rockspecs without version, you must specify which to use,
+through the command-line.
+
+This command is useful as a tool for debugging rockspecs.
+To install rocks, you'll normally want to use the "install" and "build"
+commands. See the help on those for details.
+
+If the current directory contains a luarocks.lock file, it is used as the
+authoritative source for exact version of dependencies. The --pin flag
+overrides and recreates this file scanning dependency based on ranges.
+]], util.see_also())
+ :summary("Compile package in current directory using a rockspec.")
+ -- luacheck: pop
+
+ cmd:argument("rockspec", "Rockspec for the rock to build.")
+ :args("?")
+
+ make.cmd_options(cmd)
+end
+
+--- Driver function for "make" command.
+-- @return boolean or (nil, string, exitcode): True if build was successful; nil and an
+-- error message otherwise. exitcode is optionally returned.
+function make.command(args)
+ local rockspec_filename = args.rockspec
+ if not rockspec_filename then
+ local err
+ rockspec_filename, err = util.get_default_rockspec()
+ if not rockspec_filename then
+ return nil, err
+ end
+ end
+ if not rockspec_filename:match("rockspec$") then
+ return nil, "Invalid argument: 'make' takes a rockspec as a parameter. "..util.see_help("make")
+ end
+
+ local rockspec, err, errcode = fetch.load_rockspec(rockspec_filename)
+ if not rockspec then
+ return nil, err
+ end
+
+ local name, namespace = util.split_namespace(rockspec.name)
+ namespace = namespace or args.namespace
+
+ local opts = build.opts({
+ need_to_fetch = false,
+ minimal_mode = true,
+ deps_mode = deps.get_deps_mode(args),
+ build_only_deps = not not (args.only_deps and not args.pack_binary_rock),
+ namespace = namespace,
+ branch = args.branch,
+ verify = not not args.verify,
+ check_lua_versions = not not args.check_lua_versions,
+ pin = not not args.pin,
+ rebuild = true,
+ no_install = not not args.no_install
+ })
+
+ if args.sign and not args.pack_binary_rock then
+ return nil, "In the make command, --sign is meant to be used only with --pack-binary-rock"
+ end
+
+ if args.no_install then
+ return build.build_rockspec(rockspec, opts)
+ elseif args.pack_binary_rock then
+ return pack.pack_binary_rock(name, namespace, rockspec.version, args.sign, function()
+ local name, version = build.build_rockspec(rockspec, opts) -- luacheck: ignore 431
+ if name and args.no_doc then
+ util.remove_doc_dir(name, version)
+ end
+ return name, version
+ end)
+ else
+ local ok, err = build.build_rockspec(rockspec, opts)
+ if not ok then return nil, err end
+ local name, version = ok, err -- luacheck: ignore 421
+
+ if opts.build_only_deps then
+ util.printout("Stopping after installing dependencies for " ..name.." "..version)
+ util.printout()
+ return name, version
+ end
+
+ if args.no_doc then
+ util.remove_doc_dir(name, version)
+ end
+
+ if (not args.keep) and not cfg.keep_other_versions then
+ local ok, err, warn = remove.remove_other_versions(name, version, args.force, args.force_fast)
+ if not ok then
+ return nil, err
+ elseif warn then
+ util.printerr(warn)
+ end
+ end
+
+ writer.check_dependencies(nil, deps.get_deps_mode(args))
+ return name, version
+ end
+end
+
+make.needs_lock = function(args)
+ if args.pack_binary_rock or args.no_install then
+ return false
+ end
+ return true
+end
+
+return make
diff --git a/src/luarocks/cmd/new_version.lua b/src/luarocks/cmd/new_version.lua
new file mode 100644
index 0000000..ccba933
--- /dev/null
+++ b/src/luarocks/cmd/new_version.lua
@@ -0,0 +1,228 @@
+
+--- Module implementing the LuaRocks "new_version" command.
+-- Utility function that writes a new rockspec, updating data from a previous one.
+local new_version = {}
+
+local util = require("luarocks.util")
+local download = require("luarocks.download")
+local fetch = require("luarocks.fetch")
+local persist = require("luarocks.persist")
+local fs = require("luarocks.fs")
+local dir = require("luarocks.dir")
+local type_rockspec = require("luarocks.type.rockspec")
+
+function new_version.add_to_parser(parser)
+ local cmd = parser:command("new_version", [[
+This is a utility function that writes a new rockspec, updating data from a
+previous one.
+
+If a package name is given, it downloads the latest rockspec from the default
+server. If a rockspec is given, it uses it instead. If no argument is given, it
+looks for a rockspec same way 'luarocks make' does.
+
+If the version number is not given and tag is passed using --tag, it is used as
+the version, with 'v' removed from beginning. Otherwise, it only increments the
+revision number of the given (or downloaded) rockspec.
+
+If a URL is given, it replaces the one from the old rockspec with the given URL.
+If a URL is not given and a new version is given, it tries to guess the new URL
+by replacing occurrences of the version number in the URL or tag; if the guessed
+URL is invalid, the old URL is restored. It also tries to download the new URL
+to determine the new MD5 checksum.
+
+If a tag is given, it replaces the one from the old rockspec. If there is an old
+tag but no new one passed, it is guessed in the same way URL is.
+
+If a directory is not given, it defaults to the current directory.
+
+WARNING: it writes the new rockspec to the given directory, overwriting the file
+if it already exists.]], util.see_also())
+ :summary("Auto-write a rockspec for a new version of a rock.")
+
+ cmd:argument("rock", "Package name or rockspec.")
+ :args("?")
+ cmd:argument("new_version", "New version of the rock.")
+ :args("?")
+ cmd:argument("new_url", "New URL of the rock.")
+ :args("?")
+
+ cmd:option("--dir", "Output directory for the new rockspec.")
+ cmd:option("--tag", "New SCM tag.")
+end
+
+
+local function try_replace(tbl, field, old, new)
+ if not tbl[field] then
+ return false
+ end
+ local old_field = tbl[field]
+ local new_field = tbl[field]:gsub(old, new)
+ if new_field ~= old_field then
+ util.printout("Guessing new '"..field.."' field as "..new_field)
+ tbl[field] = new_field
+ return true
+ end
+ return false
+end
+
+-- Try to download source file using URL from a rockspec.
+-- If it specified MD5, update it.
+-- @return (true, false) if MD5 was not specified or it stayed same,
+-- (true, true) if MD5 changed, (nil, string) on error.
+local function check_url_and_update_md5(out_rs, invalid_is_error)
+ local file, temp_dir = fetch.fetch_url_at_temp_dir(out_rs.source.url, "luarocks-new-version-"..out_rs.package)
+ if not file then
+ if invalid_is_error then
+ return nil, "invalid URL - "..temp_dir
+ end
+ util.warning("invalid URL - "..temp_dir)
+ return true, false
+ end
+ do
+ local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, out_rs.source.url, out_rs.source.dir)
+ if not inferred_dir then
+ return nil, found_dir
+ end
+
+ if found_dir and found_dir ~= inferred_dir then
+ out_rs.source.dir = found_dir
+ end
+ end
+ if file then
+ if out_rs.source.md5 then
+ util.printout("File successfully downloaded. Updating MD5 checksum...")
+ local new_md5, err = fs.get_md5(file)
+ if not new_md5 then
+ return nil, err
+ end
+ local old_md5 = out_rs.source.md5
+ out_rs.source.md5 = new_md5
+ return true, new_md5 ~= old_md5
+ else
+ util.printout("File successfully downloaded.")
+ return true, false
+ end
+ end
+end
+
+local function update_source_section(out_rs, url, tag, old_ver, new_ver)
+ if tag then
+ out_rs.source.tag = tag
+ end
+ if url then
+ out_rs.source.url = url
+ return check_url_and_update_md5(out_rs)
+ end
+ if new_ver == old_ver then
+ return true
+ end
+ if out_rs.source.dir then
+ try_replace(out_rs.source, "dir", old_ver, new_ver)
+ end
+ if out_rs.source.file then
+ try_replace(out_rs.source, "file", old_ver, new_ver)
+ end
+
+ local old_url = out_rs.source.url
+ if try_replace(out_rs.source, "url", old_ver, new_ver) then
+ local ok, md5_changed = check_url_and_update_md5(out_rs, true)
+ if ok then
+ return ok, md5_changed
+ end
+ out_rs.source.url = old_url
+ end
+ if tag or try_replace(out_rs.source, "tag", old_ver, new_ver) then
+ return true
+ end
+ -- Couldn't replace anything significant, use the old URL.
+ local ok, md5_changed = check_url_and_update_md5(out_rs)
+ if not ok then
+ return nil, md5_changed
+ end
+ if md5_changed then
+ util.warning("URL is the same, but MD5 has changed. Old rockspec is broken.")
+ end
+ return true
+end
+
+function new_version.command(args)
+ if not args.rock then
+ local err
+ args.rock, err = util.get_default_rockspec()
+ if not args.rock then
+ return nil, err
+ end
+ end
+
+ local filename, err
+ if args.rock:match("rockspec$") then
+ filename, err = fetch.fetch_url(args.rock)
+ if not filename then
+ return nil, err
+ end
+ else
+ filename, err = download.download("rockspec", args.rock:lower())
+ if not filename then
+ return nil, err
+ end
+ end
+
+ local valid_rs, err = fetch.load_rockspec(filename)
+ if not valid_rs then
+ return nil, err
+ end
+
+ local old_ver, old_rev = valid_rs.version:match("(.*)%-(%d+)$")
+ local new_ver, new_rev
+
+ if args.tag and not args.new_version then
+ args.new_version = args.tag:gsub("^v", "")
+ end
+
+ local out_dir
+ if args.dir then
+ out_dir = dir.normalize(args.dir)
+ end
+
+ if args.new_version then
+ new_ver, new_rev = args.new_version:match("(.*)%-(%d+)$")
+ new_rev = tonumber(new_rev)
+ if not new_rev then
+ new_ver = args.new_version
+ new_rev = 1
+ end
+ else
+ new_ver = old_ver
+ new_rev = tonumber(old_rev) + 1
+ end
+ local new_rockver = new_ver:gsub("-", "")
+
+ local out_rs, err = persist.load_into_table(filename)
+ local out_name = out_rs.package:lower()
+ out_rs.version = new_rockver.."-"..new_rev
+
+ local ok, err = update_source_section(out_rs, args.new_url, args.tag, old_ver, new_ver)
+ if not ok then return nil, err end
+
+ if out_rs.build and out_rs.build.type == "module" then
+ out_rs.build.type = "builtin"
+ end
+
+ local out_filename = out_name.."-"..new_rockver.."-"..new_rev..".rockspec"
+ if out_dir then
+ out_filename = dir.path(out_dir, out_filename)
+ fs.make_dir(out_dir)
+ end
+ persist.save_from_table(out_filename, out_rs, type_rockspec.order)
+
+ util.printout("Wrote "..out_filename)
+
+ local valid_out_rs, err = fetch.load_local_rockspec(out_filename)
+ if not valid_out_rs then
+ return nil, "Failed loading generated rockspec: "..err
+ end
+
+ return true
+end
+
+return new_version
diff --git a/src/luarocks/cmd/pack.lua b/src/luarocks/cmd/pack.lua
new file mode 100644
index 0000000..29a43e7
--- /dev/null
+++ b/src/luarocks/cmd/pack.lua
@@ -0,0 +1,36 @@
+
+--- Module implementing the LuaRocks "pack" command.
+-- Creates a rock, packing sources or binaries.
+local cmd_pack = {}
+
+local util = require("luarocks.util")
+local pack = require("luarocks.pack")
+local queries = require("luarocks.queries")
+
+function cmd_pack.add_to_parser(parser)
+ local cmd = parser:command("pack", "Create a rock, packing sources or binaries.", util.see_also())
+
+ cmd:argument("rock", "A rockspec file, for creating a source rock, or the "..
+ "name of an installed package, for creating a binary rock.")
+ :action(util.namespaced_name_action)
+ cmd:argument("version", "A version may be given if the first argument is a rock name.")
+ :args("?")
+
+ cmd:flag("--sign", "Produce a signature file as well.")
+end
+
+--- Driver function for the "pack" command.
+-- @return boolean or (nil, string): true if successful or nil followed
+-- by an error message.
+function cmd_pack.command(args)
+ local file, err
+ if args.rock:match(".*%.rockspec") then
+ file, err = pack.pack_source_rock(args.rock)
+ else
+ local query = queries.new(args.rock, args.namespace, args.version)
+ file, err = pack.pack_installed_rock(query, args.tree)
+ end
+ return pack.report_and_sign_local_file(file, err, args.sign)
+end
+
+return cmd_pack
diff --git a/src/luarocks/cmd/path.lua b/src/luarocks/cmd/path.lua
new file mode 100644
index 0000000..ba34655
--- /dev/null
+++ b/src/luarocks/cmd/path.lua
@@ -0,0 +1,83 @@
+
+--- @module luarocks.path_cmd
+-- Driver for the `luarocks path` command.
+local path_cmd = {}
+
+local util = require("luarocks.util")
+local cfg = require("luarocks.core.cfg")
+local fs = require("luarocks.fs")
+
+function path_cmd.add_to_parser(parser)
+ local cmd = parser:command("path", [[
+Returns the package path currently configured for this installation
+of LuaRocks, formatted as shell commands to update LUA_PATH and LUA_CPATH.
+
+On Unix systems, you may run:
+ eval `luarocks path`
+And on Windows:
+ luarocks path > "%temp%\_lrp.bat"
+ call "%temp%\_lrp.bat" && del "%temp%\_lrp.bat"]],
+ util.see_also())
+ :summary("Return the currently configured package path.")
+
+ cmd:flag("--no-bin", "Do not export the PATH variable.")
+ cmd:flag("--append", "Appends the paths to the existing paths. Default is "..
+ "to prefix the LR paths to the existing paths.")
+ cmd:flag("--lr-path", "Prints Lua path components defined by the configured rocks trees " ..
+ "(not formatted as a shell command)")
+ cmd:flag("--lr-cpath", "Prints Lua cpath components defined by the configured rocks trees " ..
+ "(not formatted as a shell command)")
+ cmd:flag("--full", "By default, --lr-path and --lr-cpath only include the paths " ..
+ "derived by the LuaRocks rocks_trees. Using --full includes any other components " ..
+ "defined in your system's package.(c)path, either via the running interpreter's " ..
+ "default paths or via LUA_(C)PATH(_5_x) environment variables (in short, using " ..
+ "--full produces the same lists as shown in the shell outputs of 'luarocks path').")
+ cmd:flag("--lr-bin", "Exports the system path (not formatted as shell command).")
+ cmd:flag("--bin"):hidden(true)
+end
+
+--- Driver function for "path" command.
+-- @return boolean This function always succeeds.
+function path_cmd.command(args)
+ local lr_path, lr_cpath, lr_bin = cfg.package_paths(args.tree)
+ local path_sep = cfg.export_path_separator
+
+ local full_list = ((not args.lr_path) and (not args.lr_cpath) and (not args.lr_bin))
+ or args.full
+
+ local clean_path = util.cleanup_path(os.getenv("PATH") or "", path_sep, nil, true)
+
+ if full_list then
+ if args.append then
+ lr_path = package.path .. ";" .. lr_path
+ lr_cpath = package.cpath .. ";" .. lr_cpath
+ lr_bin = clean_path .. path_sep .. lr_bin
+ else
+ lr_path = lr_path.. ";" .. package.path
+ lr_cpath = lr_cpath .. ";" .. package.cpath
+ lr_bin = lr_bin .. path_sep .. clean_path
+ end
+ end
+
+ if args.lr_path then
+ util.printout(util.cleanup_path(lr_path, ';', cfg.lua_version, true))
+ return true
+ elseif args.lr_cpath then
+ util.printout(util.cleanup_path(lr_cpath, ';', cfg.lua_version, true))
+ return true
+ elseif args.lr_bin then
+ util.printout(util.cleanup_path(lr_bin, path_sep, nil, true))
+ return true
+ end
+
+ local lpath_var, lcpath_var = util.lua_path_variables()
+
+ util.printout(fs.export_cmd(lpath_var, util.cleanup_path(lr_path, ';', cfg.lua_version, args.append)))
+ util.printout(fs.export_cmd(lcpath_var, util.cleanup_path(lr_cpath, ';', cfg.lua_version, args.append)))
+ if not args.no_bin then
+ util.printout(fs.export_cmd("PATH", util.cleanup_path(lr_bin, path_sep, nil, args.append)))
+ end
+ return true
+end
+
+return path_cmd
diff --git a/src/luarocks/cmd/purge.lua b/src/luarocks/cmd/purge.lua
new file mode 100644
index 0000000..30811dd
--- /dev/null
+++ b/src/luarocks/cmd/purge.lua
@@ -0,0 +1,73 @@
+
+--- Module implementing the LuaRocks "purge" command.
+-- Remove all rocks from a given tree.
+local purge = {}
+
+local util = require("luarocks.util")
+local path = require("luarocks.path")
+local search = require("luarocks.search")
+local vers = require("luarocks.core.vers")
+local repos = require("luarocks.repos")
+local writer = require("luarocks.manif.writer")
+local cfg = require("luarocks.core.cfg")
+local remove = require("luarocks.remove")
+local queries = require("luarocks.queries")
+
+function purge.add_to_parser(parser)
+ -- luacheck: push ignore 431
+ local cmd = parser:command("purge", [[
+This command removes rocks en masse from a given tree.
+By default, it removes all rocks from a tree.
+
+The --tree option is mandatory: luarocks purge does not assume a default tree.]],
+ util.see_also())
+ :summary("Remove all installed rocks from a tree.")
+ -- luacheck: pop
+
+ cmd:flag("--old-versions", "Keep the highest-numbered version of each "..
+ "rock and remove the other ones. By default it only removes old "..
+ "versions if they are not needed as dependencies. This can be "..
+ "overridden with the flag --force.")
+ cmd:flag("--force", "If --old-versions is specified, force removal of "..
+ "previously installed versions if it would break dependencies.")
+ cmd:flag("--force-fast", "Like --force, but performs a forced removal "..
+ "without reporting dependency issues.")
+end
+
+function purge.command(args)
+ local tree = args.tree
+
+ local results = {}
+ search.local_manifest_search(results, path.rocks_dir(tree), queries.all())
+
+ local sort = function(a,b) return vers.compare_versions(b,a) end
+ if args.old_versions then
+ sort = vers.compare_versions
+ end
+
+ for package, versions in util.sortedpairs(results) do
+ for version, _ in util.sortedpairs(versions, sort) do
+ if args.old_versions then
+ util.printout("Keeping "..package.." "..version.."...")
+ local ok, err, warn = remove.remove_other_versions(package, version, args.force, args.force_fast)
+ if not ok then
+ util.printerr(err)
+ elseif warn then
+ util.printerr(err)
+ end
+ break
+ else
+ util.printout("Removing "..package.." "..version.."...")
+ local ok, err = repos.delete_version(package, version, "none", true)
+ if not ok then
+ util.printerr(err)
+ end
+ end
+ end
+ end
+ return writer.make_manifest(cfg.rocks_dir, "one")
+end
+
+purge.needs_lock = function() return true end
+
+return purge
diff --git a/src/luarocks/cmd/remove.lua b/src/luarocks/cmd/remove.lua
new file mode 100644
index 0000000..8b11bcd
--- /dev/null
+++ b/src/luarocks/cmd/remove.lua
@@ -0,0 +1,72 @@
+
+--- Module implementing the LuaRocks "remove" command.
+-- Uninstalls rocks.
+local cmd_remove = {}
+
+local remove = require("luarocks.remove")
+local util = require("luarocks.util")
+local cfg = require("luarocks.core.cfg")
+local search = require("luarocks.search")
+local path = require("luarocks.path")
+local deps = require("luarocks.deps")
+local writer = require("luarocks.manif.writer")
+local queries = require("luarocks.queries")
+
+function cmd_remove.add_to_parser(parser)
+ -- luacheck: push ignore 431
+ local cmd = parser:command("remove", [[
+Uninstall a rock.
+
+If a version is not given, try to remove all versions at once.
+Will only perform the removal if it does not break dependencies.
+To override this check and force the removal, use --force or --force-fast.]],
+ util.see_also())
+ :summary("Uninstall a rock.")
+ -- luacheck: pop
+
+ cmd:argument("rock", "Name of the rock to be uninstalled.")
+ :action(util.namespaced_name_action)
+ cmd:argument("version", "Version of the rock to uninstall.")
+ :args("?")
+
+ cmd:flag("--force", "Force removal if it would break dependencies.")
+ cmd:flag("--force-fast", "Perform a forced removal without reporting dependency issues.")
+ util.deps_mode_option(cmd)
+end
+
+--- Driver function for the "remove" command.
+-- @return boolean or (nil, string, exitcode): True if removal was
+-- successful, nil and an error message otherwise. exitcode is optionally returned.
+function cmd_remove.command(args)
+ local name = args.rock
+ local deps_mode = deps.get_deps_mode(args)
+
+ local rock_type = name:match("%.(rock)$") or name:match("%.(rockspec)$")
+ local version = args.version
+ local filename = name
+ if rock_type then
+ name, version = path.parse_name(filename)
+ if not name then return nil, "Invalid "..rock_type.." filename: "..filename end
+ end
+
+ name = name:lower()
+
+ local results = {}
+ search.local_manifest_search(results, cfg.rocks_dir, queries.new(name, args.namespace, version))
+ if not results[name] then
+ local rock = util.format_rock_name(name, args.namespace, version)
+ return nil, "Could not find rock '"..rock.."' in "..path.rocks_tree_to_string(cfg.root_dir)
+ end
+
+ local ok, err = remove.remove_search_results(results, name, deps_mode, args.force, args.force_fast)
+ if not ok then
+ return nil, err
+ end
+
+ writer.check_dependencies(nil, deps.get_deps_mode(args))
+ return true
+end
+
+cmd_remove.needs_lock = function() return true end
+
+return cmd_remove
diff --git a/src/luarocks/cmd/search.lua b/src/luarocks/cmd/search.lua
new file mode 100644
index 0000000..6cab6d8
--- /dev/null
+++ b/src/luarocks/cmd/search.lua
@@ -0,0 +1,84 @@
+
+--- Module implementing the LuaRocks "search" command.
+-- Queries LuaRocks servers.
+local cmd_search = {}
+
+local cfg = require("luarocks.core.cfg")
+local util = require("luarocks.util")
+local search = require("luarocks.search")
+local queries = require("luarocks.queries")
+local results = require("luarocks.results")
+
+function cmd_search.add_to_parser(parser)
+ local cmd = parser:command("search", "Query the LuaRocks servers.", util.see_also())
+
+ cmd:argument("name", "Name of the rock to search for.")
+ :args("?")
+ :action(util.namespaced_name_action)
+ cmd:argument("version", "Rock version to search for.")
+ :args("?")
+
+ cmd:flag("--source", "Return only rockspecs and source rocks, to be used "..
+ 'with the "build" command.')
+ cmd:flag("--binary", "Return only pure Lua and binary rocks (rocks that "..
+ 'can be used with the "install" command without requiring a C toolchain).')
+ cmd:flag("--all", "List all contents of the server that are suitable to "..
+ "this platform, do not filter by name.")
+ cmd:flag("--porcelain", "Return a machine readable format.")
+end
+
+--- Splits a list of search results into two lists, one for "source" results
+-- to be used with the "build" command, and one for "binary" results to be
+-- used with the "install" command.
+-- @param result_tree table: A search results table.
+-- @return (table, table): Two tables, one for source and one for binary
+-- results.
+local function split_source_and_binary_results(result_tree)
+ local sources, binaries = {}, {}
+ for name, versions in pairs(result_tree) do
+ for version, repositories in pairs(versions) do
+ for _, repo in ipairs(repositories) do
+ local where = sources
+ if repo.arch == "all" or repo.arch == cfg.arch then
+ where = binaries
+ end
+ local entry = results.new(name, version, repo.repo, repo.arch)
+ search.store_result(where, entry)
+ end
+ end
+ end
+ return sources, binaries
+end
+
+--- Driver function for "search" command.
+-- @return boolean or (nil, string): True if build was successful; nil and an
+-- error message otherwise.
+function cmd_search.command(args)
+ local name = args.name
+
+ if args.all then
+ name, args.version = "", nil
+ end
+
+ if not args.name and not args.all then
+ return nil, "Enter name and version or use --all. "..util.see_help("search")
+ end
+
+ local query = queries.new(name, args.namespace, args.version, true)
+ local result_tree, err = search.search_repos(query)
+ local porcelain = args.porcelain
+ local full_name = util.format_rock_name(name, args.namespace, args.version)
+ util.title(full_name .. " - Search results for Lua "..cfg.lua_version..":", porcelain, "=")
+ local sources, binaries = split_source_and_binary_results(result_tree)
+ if next(sources) and not args.binary then
+ util.title("Rockspecs and source rocks:", porcelain)
+ search.print_result_tree(sources, porcelain)
+ end
+ if next(binaries) and not args.source then
+ util.title("Binary and pure-Lua rocks:", porcelain)
+ search.print_result_tree(binaries, porcelain)
+ end
+ return true
+end
+
+return cmd_search
diff --git a/src/luarocks/cmd/show.lua b/src/luarocks/cmd/show.lua
new file mode 100644
index 0000000..88cbbad
--- /dev/null
+++ b/src/luarocks/cmd/show.lua
@@ -0,0 +1,314 @@
+--- Module implementing the LuaRocks "show" command.
+-- Shows information about an installed rock.
+local show = {}
+
+local queries = require("luarocks.queries")
+local search = require("luarocks.search")
+local dir = require("luarocks.core.dir")
+local fs = require("luarocks.fs")
+local cfg = require("luarocks.core.cfg")
+local util = require("luarocks.util")
+local path = require("luarocks.path")
+local fetch = require("luarocks.fetch")
+local manif = require("luarocks.manif")
+local repos = require("luarocks.repos")
+
+function show.add_to_parser(parser)
+ local cmd = parser:command("show", [[
+Show information about an installed rock.
+
+Without any flags, show all module information.
+With flags, return only the desired information.]], util.see_also())
+ :summary("Show information about an installed rock.")
+
+ cmd:argument("rock", "Name of an installed rock.")
+ :action(util.namespaced_name_action)
+ cmd:argument("version", "Rock version.")
+ :args("?")
+
+ cmd:flag("--home", "Show home page of project.")
+ cmd:flag("--modules", "Show all modules provided by the package as used by require().")
+ cmd:flag("--deps", "Show packages the package depends on.")
+ cmd:flag("--build-deps", "Show build-only dependencies for the package.")
+ cmd:flag("--test-deps", "Show dependencies for testing the package.")
+ cmd:flag("--rockspec", "Show the full path of the rockspec file.")
+ cmd:flag("--mversion", "Show the package version.")
+ cmd:flag("--rock-tree", "Show local tree where rock is installed.")
+ cmd:flag("--rock-namespace", "Show rock namespace.")
+ cmd:flag("--rock-dir", "Show data directory of the installed rock.")
+ cmd:flag("--rock-license", "Show rock license.")
+ cmd:flag("--issues", "Show URL for project's issue tracker.")
+ cmd:flag("--labels", "List the labels of the rock.")
+ cmd:flag("--porcelain", "Produce machine-friendly output.")
+end
+
+local friendly_template = [[
+ :
+?namespace:${namespace}/${package} ${version} - ${summary}
+!namespace:${package} ${version} - ${summary}
+ :
+*detailed :${detailed}
+?detailed :
+?license :License: \t${license}
+?homepage :Homepage: \t${homepage}
+?issues :Issues: \t${issues}
+?labels :Labels: \t${labels}
+?location :Installed in: \t${location}
+?commands :
+?commands :Commands:
+*commands :\t${name} (${file})
+?modules :
+?modules :Modules:
+*modules :\t${name} (${file})
+?bdeps :
+?bdeps :Has build dependency on:
+*bdeps :\t${name} (${label})
+?tdeps :
+?tdeps :Tests depend on:
+*tdeps :\t${name} (${label})
+?deps :
+?deps :Depends on:
+*deps :\t${name} (${label})
+?ideps :
+?ideps :Indirectly pulling:
+*ideps :\t${name} (${label})
+ :
+]]
+
+local porcelain_template = [[
+?namespace:namespace\t${namespace}
+?package :package\t${package}
+?version :version\t${version}
+?summary :summary\t${summary}
+*detailed :detailed\t${detailed}
+?license :license\t${license}
+?homepage :homepage\t${homepage}
+?issues :issues\t${issues}
+?labels :labels\t${labels}
+?location :location\t${location}
+*commands :command\t${name}\t${file}
+*modules :module\t${name}\t${file}
+*bdeps :build_dependency\t${name}\t${label}
+*tdeps :test_dependency\t${name}\t${label}
+*deps :dependency\t${name}\t${label}
+*ideps :indirect_dependency\t${name}\t${label}
+]]
+
+local function keys_as_string(t, sep)
+ local keys = util.keys(t)
+ table.sort(keys)
+ return table.concat(keys, sep or " ")
+end
+
+local function word_wrap(line)
+ local width = tonumber(os.getenv("COLUMNS")) or 80
+ if width > 80 then width = 80 end
+ if #line > width then
+ local brk = width
+ while brk > 0 and line:sub(brk, brk) ~= " " do
+ brk = brk - 1
+ end
+ if brk > 0 then
+ return line:sub(1, brk-1) .. "\n" .. word_wrap(line:sub(brk+1))
+ end
+ end
+ return line
+end
+
+local function format_text(text)
+ text = text:gsub("^%s*",""):gsub("%s$", ""):gsub("\n[ \t]+","\n"):gsub("([^\n])\n([^\n])","%1 %2")
+ local paragraphs = util.split_string(text, "\n\n")
+ for n, line in ipairs(paragraphs) do
+ paragraphs[n] = word_wrap(line)
+ end
+ return (table.concat(paragraphs, "\n\n"):gsub("%s$", ""))
+end
+
+local function installed_rock_label(dep, tree)
+ local installed, version
+ local rocks_provided = util.get_rocks_provided()
+ if rocks_provided[dep.name] then
+ installed, version = true, rocks_provided[dep.name]
+ else
+ installed, version = search.pick_installed_rock(dep, tree)
+ end
+ return installed and "using "..version or "missing"
+end
+
+local function render(template, data)
+ local out = {}
+ for cmd, var, line in template:gmatch("(.)([a-z]*)%s*:([^\n]*)\n") do
+ line = line:gsub("\\t", "\t")
+ local d = data[var]
+ if cmd == " " then
+ table.insert(out, line)
+ elseif cmd == "?" or cmd == "*" or cmd == "!" then
+ if (cmd == "!" and d == nil)
+ or (cmd ~= "!" and (type(d) == "string"
+ or (type(d) == "table" and next(d)))) then
+ local n = cmd == "*" and #d or 1
+ for i = 1, n do
+ local tbl = cmd == "*" and d[i] or data
+ if type(tbl) == "string" then
+ tbl = tbl:gsub("%%", "%%%%")
+ end
+ table.insert(out, (line:gsub("${([a-z]+)}", tbl)))
+ end
+ end
+ end
+ end
+ return table.concat(out, "\n")
+end
+
+local function adjust_path(name, version, basedir, pathname, suffix)
+ pathname = dir.path(basedir, pathname)
+ local vpathname = path.versioned_name(pathname, basedir, name, version)
+ return (fs.exists(vpathname)
+ and vpathname
+ or pathname) .. (suffix or "")
+end
+
+local function modules_to_list(name, version, repo)
+ local ret = {}
+ local rock_manifest = manif.load_rock_manifest(name, version, repo)
+
+ local lua_dir = path.deploy_lua_dir(repo)
+ local lib_dir = path.deploy_lib_dir(repo)
+ repos.recurse_rock_manifest_entry(rock_manifest.lua, function(pathname)
+ table.insert(ret, {
+ name = path.path_to_module(pathname),
+ file = adjust_path(name, version, lua_dir, pathname),
+ })
+ end)
+ repos.recurse_rock_manifest_entry(rock_manifest.lib, function(pathname)
+ table.insert(ret, {
+ name = path.path_to_module(pathname),
+ file = adjust_path(name, version, lib_dir, pathname),
+ })
+ end)
+ table.sort(ret, function(a, b)
+ if a.name == b.name then
+ return a.file < b.file
+ end
+ return a.name < b.name
+ end)
+ return ret
+end
+
+local function commands_to_list(name, version, repo)
+ local ret = {}
+ local rock_manifest = manif.load_rock_manifest(name, version, repo)
+
+ local bin_dir = path.deploy_bin_dir(repo)
+ repos.recurse_rock_manifest_entry(rock_manifest.bin, function(pathname)
+ table.insert(ret, {
+ name = name,
+ file = adjust_path(name, version, bin_dir, pathname, cfg.wrapper_suffix),
+ })
+ end)
+ table.sort(ret, function(a, b)
+ if a.name == b.name then
+ return a.file < b.file
+ end
+ return a.name < b.name
+ end)
+ return ret
+end
+
+local function deps_to_list(dependencies, tree)
+ local ret = {}
+ for _, dep in ipairs(dependencies or {}) do
+ table.insert(ret, { name = tostring(dep), label = installed_rock_label(dep, tree) })
+ end
+ return ret
+end
+
+local function indirect_deps(mdeps, rdeps, tree)
+ local ret = {}
+ local direct_deps = {}
+ for _, dep in ipairs(rdeps) do
+ direct_deps[dep] = true
+ end
+ for dep_name in util.sortedpairs(mdeps or {}) do
+ if not direct_deps[dep_name] then
+ table.insert(ret, { name = tostring(dep_name), label = installed_rock_label(queries.new(dep_name), tree) })
+ end
+ end
+ return ret
+end
+
+local function show_rock(template, namespace, name, version, rockspec, repo, minfo, tree)
+ local desc = rockspec.description or {}
+ local data = {
+ namespace = namespace,
+ package = rockspec.package,
+ version = rockspec.version,
+ summary = desc.summary or "",
+ detailed = desc.detailed and util.split_string(format_text(desc.detailed), "\n"),
+ license = desc.license,
+ homepage = desc.homepage,
+ issues = desc.issues_url,
+ labels = desc.labels and table.concat(desc.labels, ", "),
+ location = path.rocks_tree_to_string(repo),
+ commands = commands_to_list(name, version, repo),
+ modules = modules_to_list(name, version, repo),
+ bdeps = deps_to_list(rockspec.build_dependencies, tree),
+ tdeps = deps_to_list(rockspec.test_dependencies, tree),
+ deps = deps_to_list(rockspec.dependencies, tree),
+ ideps = indirect_deps(minfo.dependencies, rockspec.dependencies, tree),
+ }
+ util.printout(render(template, data))
+end
+
+--- Driver function for "show" command.
+-- @return boolean: True if succeeded, nil on errors.
+function show.command(args)
+ local query = queries.new(args.rock, args.namespace, args.version, true)
+
+ local name, version, repo, repo_url = search.pick_installed_rock(query, args.tree)
+ if not name then
+ return nil, version
+ end
+ local tree = path.rocks_tree_to_string(repo)
+ local directory = path.install_dir(name, version, repo)
+ local namespace = path.read_namespace(name, version, tree)
+ local rockspec_file = path.rockspec_file(name, version, repo)
+ local rockspec, err = fetch.load_local_rockspec(rockspec_file)
+ if not rockspec then return nil,err end
+
+ local descript = rockspec.description or {}
+ local manifest, err = manif.load_manifest(repo_url)
+ if not manifest then return nil,err end
+ local minfo = manifest.repository[name][version][1]
+
+ if args.rock_tree then util.printout(tree)
+ elseif args.rock_namespace then util.printout(namespace)
+ elseif args.rock_dir then util.printout(directory)
+ elseif args.home then util.printout(descript.homepage)
+ elseif args.rock_license then util.printout(descript.license)
+ elseif args.issues then util.printout(descript.issues_url)
+ elseif args.labels then util.printout(descript.labels and table.concat(descript.labels, "\n"))
+ elseif args.modules then util.printout(keys_as_string(minfo.modules, "\n"))
+ elseif args.deps then
+ for _, dep in ipairs(rockspec.dependencies) do
+ util.printout(tostring(dep))
+ end
+ elseif args.build_deps then
+ for _, dep in ipairs(rockspec.build_dependencies) do
+ util.printout(tostring(dep))
+ end
+ elseif args.test_deps then
+ for _, dep in ipairs(rockspec.test_dependencies) do
+ util.printout(tostring(dep))
+ end
+ elseif args.rockspec then util.printout(rockspec_file)
+ elseif args.mversion then util.printout(version)
+ elseif args.porcelain then
+ show_rock(porcelain_template, namespace, name, version, rockspec, repo, minfo, args.tree)
+ else
+ show_rock(friendly_template, namespace, name, version, rockspec, repo, minfo, args.tree)
+ end
+ return true
+end
+
+return show
diff --git a/src/luarocks/cmd/test.lua b/src/luarocks/cmd/test.lua
new file mode 100644
index 0000000..b353bd8
--- /dev/null
+++ b/src/luarocks/cmd/test.lua
@@ -0,0 +1,48 @@
+
+--- Module implementing the LuaRocks "test" command.
+-- Tests a rock, compiling its C parts if any.
+local cmd_test = {}
+
+local util = require("luarocks.util")
+local test = require("luarocks.test")
+
+function cmd_test.add_to_parser(parser)
+ local cmd = parser:command("test", [[
+Run the test suite for the Lua project in the current directory.
+
+If the first argument is a rockspec, it will use it to determine the parameters
+for running tests; otherwise, it will attempt to detect the rockspec.
+
+Any additional arguments are forwarded to the test suite.
+To make sure that test suite flags are not interpreted as LuaRocks flags, use --
+to separate LuaRocks arguments from test suite arguments.]],
+ util.see_also())
+ :summary("Run the test suite in the current directory.")
+
+ cmd:argument("rockspec", "Project rockspec.")
+ :args("?")
+ cmd:argument("args", "Test suite arguments.")
+ :args("*")
+ cmd:flag("--prepare", "Only install dependencies needed for testing only, but do not run the test")
+
+ cmd:option("--test-type", "Specify the test suite type manually if it was "..
+ "not specified in the rockspec and it could not be auto-detected.")
+ :argname("<type>")
+end
+
+function cmd_test.command(args)
+ if args.rockspec and args.rockspec:match("rockspec$") then
+ return test.run_test_suite(args.rockspec, args.test_type, args.args, args.prepare)
+ end
+
+ table.insert(args.args, 1, args.rockspec)
+
+ local rockspec, err = util.get_default_rockspec()
+ if not rockspec then
+ return nil, err
+ end
+
+ return test.run_test_suite(rockspec, args.test_type, args.args, args.prepare)
+end
+
+return cmd_test
diff --git a/src/luarocks/cmd/unpack.lua b/src/luarocks/cmd/unpack.lua
new file mode 100644
index 0000000..a0ade4f
--- /dev/null
+++ b/src/luarocks/cmd/unpack.lua
@@ -0,0 +1,169 @@
+
+--- Module implementing the LuaRocks "unpack" command.
+-- Unpack the contents of a rock.
+local unpack = {}
+
+local fetch = require("luarocks.fetch")
+local fs = require("luarocks.fs")
+local util = require("luarocks.util")
+local build = require("luarocks.build")
+local dir = require("luarocks.dir")
+local search = require("luarocks.search")
+
+function unpack.add_to_parser(parser)
+ local cmd = parser:command("unpack", [[
+Unpacks the contents of a rock in a newly created directory.
+Argument may be a rock file, or the name of a rock in a rocks server.
+In the latter case, the rock version may be given as a second argument.]],
+ util.see_also())
+ :summary("Unpack the contents of a rock.")
+
+ cmd:argument("rock", "A rock file or the name of a rock.")
+ :action(util.namespaced_name_action)
+ cmd:argument("version", "Rock version.")
+ :args("?")
+
+ cmd:flag("--force", "Unpack files even if the output directory already exists.")
+ cmd:flag("--check-lua-versions", "If the rock can't be found, check repository "..
+ "and report if it is available for another Lua version.")
+end
+
+--- Load a rockspec file to the given directory, fetches the source
+-- files specified in the rockspec, and unpack them inside the directory.
+-- @param rockspec_file string: The URL for a rockspec file.
+-- @param dir_name string: The directory where to store and unpack files.
+-- @return table or (nil, string): the loaded rockspec table or
+-- nil and an error message.
+local function unpack_rockspec(rockspec_file, dir_name)
+ assert(type(rockspec_file) == "string")
+ assert(type(dir_name) == "string")
+
+ local rockspec, err = fetch.load_rockspec(rockspec_file)
+ if not rockspec then
+ return nil, "Failed loading rockspec "..rockspec_file..": "..err
+ end
+ local ok, err = fs.change_dir(dir_name)
+ if not ok then return nil, err end
+ local ok, sources_dir = fetch.fetch_sources(rockspec, true, ".")
+ if not ok then
+ return nil, sources_dir
+ end
+ ok, err = fs.change_dir(sources_dir)
+ if not ok then return nil, err end
+ ok, err = build.apply_patches(rockspec)
+ fs.pop_dir()
+ if not ok then return nil, err end
+ return rockspec
+end
+
+--- Load a .rock file to the given directory and unpack it inside it.
+-- @param rock_file string: The URL for a .rock file.
+-- @param dir_name string: The directory where to unpack.
+-- @param kind string: the kind of rock file, as in the second-level
+-- extension in the rock filename (eg. "src", "all", "linux-x86")
+-- @return table or (nil, string): the loaded rockspec table or
+-- nil and an error message.
+local function unpack_rock(rock_file, dir_name, kind)
+ assert(type(rock_file) == "string")
+ assert(type(dir_name) == "string")
+
+ local ok, err, errcode = fetch.fetch_and_unpack_rock(rock_file, dir_name)
+ if not ok then
+ return nil, err, errcode
+ end
+ ok, err = fs.change_dir(dir_name)
+ if not ok then return nil, err end
+ local rockspec_file = dir_name..".rockspec"
+ local rockspec, err = fetch.load_rockspec(rockspec_file)
+ if not rockspec then
+ return nil, "Failed loading rockspec "..rockspec_file..": "..err
+ end
+ if kind == "src" then
+ if rockspec.source.file then
+ local ok, err = fs.unpack_archive(rockspec.source.file)
+ if not ok then return nil, err end
+ ok, err = fetch.find_rockspec_source_dir(rockspec, ".")
+ if not ok then return nil, err end
+ ok, err = fs.change_dir(rockspec.source.dir)
+ if not ok then return nil, err end
+ ok, err = build.apply_patches(rockspec)
+ fs.pop_dir()
+ if not ok then return nil, err end
+ end
+ end
+ return rockspec
+end
+
+--- Create a directory and perform the necessary actions so that
+-- the sources for the rock and its rockspec are unpacked inside it,
+-- laid out properly so that the 'make' command is able to build the module.
+-- @param file string: A rockspec or .rock URL.
+-- @return boolean or (nil, string): true if successful or nil followed
+-- by an error message.
+local function run_unpacker(file, force)
+ assert(type(file) == "string")
+
+ local base_name = dir.base_name(file)
+ local dir_name, kind, extension = base_name:match("(.*)%.([^.]+)%.(rock)$")
+ if not extension then
+ dir_name, extension = base_name:match("(.*)%.(rockspec)$")
+ kind = "rockspec"
+ end
+ if not extension then
+ return nil, file.." does not seem to be a valid filename."
+ end
+
+ local exists = fs.exists(dir_name)
+ if exists and not force then
+ return nil, "Directory "..dir_name.." already exists."
+ end
+ if not exists then
+ local ok, err = fs.make_dir(dir_name)
+ if not ok then return nil, err end
+ end
+ local rollback = util.schedule_function(fs.delete, fs.absolute_name(dir_name))
+
+ local rockspec, err
+ if extension == "rock" then
+ rockspec, err = unpack_rock(file, dir_name, kind)
+ elseif extension == "rockspec" then
+ rockspec, err = unpack_rockspec(file, dir_name)
+ end
+ if not rockspec then
+ return nil, err
+ end
+ if kind == "src" or kind == "rockspec" then
+ fetch.find_rockspec_source_dir(rockspec, ".")
+ if rockspec.source.dir ~= "." then
+ local ok = fs.copy(rockspec.local_abs_filename, rockspec.source.dir, "read")
+ if not ok then
+ return nil, "Failed copying unpacked rockspec into unpacked source directory."
+ end
+ end
+ util.printout()
+ util.printout("Done. You may now enter directory ")
+ util.printout(dir.path(dir_name, rockspec.source.dir))
+ util.printout("and type 'luarocks make' to build.")
+ end
+ util.remove_scheduled_function(rollback)
+ return true
+end
+
+--- Driver function for the "unpack" command.
+-- @return boolean or (nil, string): true if successful or nil followed
+-- by an error message.
+function unpack.command(args)
+ local url, err
+ if args.rock:match(".*%.rock") or args.rock:match(".*%.rockspec") then
+ url = args.rock
+ else
+ url, err = search.find_src_or_rockspec(args.rock, args.namespace, args.version, args.check_lua_versions)
+ if not url then
+ return nil, err
+ end
+ end
+
+ return run_unpacker(url, args.force)
+end
+
+return unpack
diff --git a/src/luarocks/cmd/upload.lua b/src/luarocks/cmd/upload.lua
new file mode 100644
index 0000000..6b84e45
--- /dev/null
+++ b/src/luarocks/cmd/upload.lua
@@ -0,0 +1,128 @@
+
+local upload = {}
+
+local signing = require("luarocks.signing")
+local util = require("luarocks.util")
+local fetch = require("luarocks.fetch")
+local pack = require("luarocks.pack")
+local cfg = require("luarocks.core.cfg")
+local Api = require("luarocks.upload.api")
+
+function upload.add_to_parser(parser)
+ local cmd = parser:command("upload", "Pack a source rock file (.src.rock extension) "..
+ "and upload it and the rockspec to the public rocks repository.", util.see_also())
+ :summary("Upload a rockspec to the public rocks repository.")
+
+ cmd:argument("rockspec", "Rockspec for the rock to upload.")
+ cmd:argument("src-rock", "A corresponding .src.rock file; if not given it will be generated.")
+ :args("?")
+
+ cmd:flag("--skip-pack", "Do not pack and send source rock.")
+ cmd:option("--api-key", "Pass an API key. It will be stored for subsequent uses.")
+ :argname("<key>")
+ cmd:option("--temp-key", "Use the given a temporary API key in this "..
+ "invocation only. It will not be stored.")
+ :argname("<key>")
+ cmd:flag("--force", "Replace existing rockspec if the same revision of a "..
+ "module already exists. This should be used only in case of upload "..
+ "mistakes: when updating a rockspec, increment the revision number "..
+ "instead.")
+ cmd:flag("--sign", "Upload a signature file alongside each file as well.")
+ cmd:flag("--debug"):hidden(true)
+end
+
+local function is_dev_version(version)
+ return version:match("^dev") or version:match("^scm")
+end
+
+function upload.command(args)
+ local api, err = Api.new(args)
+ if not api then
+ return nil, err
+ end
+ if cfg.verbose then
+ api.debug = true
+ end
+
+ local rockspec, err, errcode = fetch.load_rockspec(args.rockspec)
+ if err then
+ return nil, err, errcode
+ end
+
+ util.printout("Sending " .. tostring(args.rockspec) .. " ...")
+ local res, err = api:method("check_rockspec", {
+ package = rockspec.package,
+ version = rockspec.version
+ })
+ if not res then return nil, err end
+
+ if not res.module then
+ util.printout("Will create new module (" .. tostring(rockspec.package) .. ")")
+ end
+ if res.version and not args.force then
+ return nil, "Revision "..rockspec.version.." already exists on the server. "..util.see_help("upload")
+ end
+
+ local sigfname
+ local rock_sigfname
+
+ if args.sign then
+ sigfname, err = signing.sign_file(args.rockspec)
+ if err then
+ return nil, "Failed signing rockspec: " .. err
+ end
+ util.printout("Signed rockspec: "..sigfname)
+ end
+
+ local rock_fname
+ if args.src_rock then
+ rock_fname = args.src_rock
+ elseif not args.skip_pack and not is_dev_version(rockspec.version) then
+ util.printout("Packing " .. tostring(rockspec.package))
+ rock_fname, err = pack.pack_source_rock(args.rockspec)
+ if not rock_fname then
+ return nil, err
+ end
+ end
+
+ if rock_fname and args.sign then
+ rock_sigfname, err = signing.sign_file(rock_fname)
+ if err then
+ return nil, "Failed signing rock: " .. err
+ end
+ util.printout("Signed packed rock: "..rock_sigfname)
+ end
+
+ local multipart = require("luarocks.upload.multipart")
+
+ res, err = api:method("upload", nil, {
+ rockspec_file = multipart.new_file(args.rockspec),
+ rockspec_sig = sigfname and multipart.new_file(sigfname),
+ })
+ if not res then return nil, err end
+
+ if res.is_new and #res.manifests == 0 then
+ util.printerr("Warning: module not added to root manifest due to name taken.")
+ end
+
+ local module_url = res.module_url
+
+ if rock_fname then
+ if (not res.version) or (not res.version.id) then
+ return nil, "Invalid response from server."
+ end
+ util.printout(("Sending " .. tostring(rock_fname) .. " ..."))
+ res, err = api:method("upload_rock/" .. ("%d"):format(res.version.id), nil, {
+ rock_file = multipart.new_file(rock_fname),
+ rock_sig = rock_sigfname and multipart.new_file(rock_sigfname),
+ })
+ if not res then return nil, err end
+ end
+
+ util.printout()
+ util.printout("Done: " .. tostring(module_url))
+ util.printout()
+ return true
+end
+
+return upload
diff --git a/src/luarocks/cmd/which.lua b/src/luarocks/cmd/which.lua
new file mode 100644
index 0000000..f50a43c
--- /dev/null
+++ b/src/luarocks/cmd/which.lua
@@ -0,0 +1,40 @@
+
+--- @module luarocks.which_cmd
+-- Driver for the `luarocks which` command.
+local which_cmd = {}
+
+local loader = require("luarocks.loader")
+local cfg = require("luarocks.core.cfg")
+local util = require("luarocks.util")
+
+function which_cmd.add_to_parser(parser)
+ local cmd = parser:command("which", 'Given a module name like "foo.bar", '..
+ "output which file would be loaded to resolve that module by "..
+ 'luarocks.loader, like "/usr/local/lua/'..cfg.lua_version..'/foo/bar.lua".',
+ util.see_also())
+ :summary("Tell which file corresponds to a given module name.")
+
+ cmd:argument("modname", "Module name.")
+end
+
+--- Driver function for "which" command.
+-- @return boolean This function terminates the interpreter.
+function which_cmd.command(args)
+ local pathname, rock_name, rock_version, where = loader.which(args.modname, "lp")
+
+ if pathname then
+ util.printout(pathname)
+ if where == "l" then
+ util.printout("(provided by " .. tostring(rock_name) .. " " .. tostring(rock_version) .. ")")
+ else
+ local key = rock_name
+ util.printout("(found directly via package." .. key.. " -- not installed as a rock?)")
+ end
+ return true
+ end
+
+ return nil, "Module '" .. args.modname .. "' not found."
+end
+
+return which_cmd
+
diff --git a/src/luarocks/cmd/write_rockspec.lua b/src/luarocks/cmd/write_rockspec.lua
new file mode 100644
index 0000000..871cdd4
--- /dev/null
+++ b/src/luarocks/cmd/write_rockspec.lua
@@ -0,0 +1,408 @@
+
+local write_rockspec = {}
+
+local builtin = require("luarocks.build.builtin")
+local cfg = require("luarocks.core.cfg")
+local dir = require("luarocks.dir")
+local fetch = require("luarocks.fetch")
+local fs = require("luarocks.fs")
+local persist = require("luarocks.persist")
+local rockspecs = require("luarocks.rockspecs")
+local type_rockspec = require("luarocks.type.rockspec")
+local util = require("luarocks.util")
+
+local lua_versions = {
+ "5.1",
+ "5.2",
+ "5.3",
+ "5.4",
+ "5.1,5.2",
+ "5.2,5.3",
+ "5.3,5.4",
+ "5.1,5.2,5.3",
+ "5.2,5.3,5.4",
+ "5.1,5.2,5.3,5.4"
+}
+
+function write_rockspec.cmd_options(parser)
+ return parser:option("--output", "Write the rockspec with the given filename.\n"..
+ "If not given, a file is written in the current directory with a "..
+ "filename based on given name and version.")
+ :argname("<file>"),
+ parser:option("--license", 'A license string, such as "MIT/X11" or "GNU GPL v3".')
+ :argname("<string>"),
+ parser:option("--summary", "A short one-line description summary.")
+ :argname("<txt>"),
+ parser:option("--detailed", "A longer description string.")
+ :argname("<txt>"),
+ parser:option("--homepage", "Project homepage.")
+ :argname("<txt>"),
+ parser:option("--lua-versions", 'Supported Lua versions. Accepted values are: "'..
+ table.concat(lua_versions, '", "')..'".')
+ :argname("<ver>")
+ :choices(lua_versions),
+ parser:option("--rockspec-format", 'Rockspec format version, such as "1.0" or "1.1".')
+ :argname("<ver>"),
+ parser:option("--tag", "Tag to use. Will attempt to extract version number from it."),
+ parser:option("--lib", "A comma-separated list of libraries that C files need to link to.")
+ :argname("<libs>")
+end
+
+function write_rockspec.add_to_parser(parser)
+ local cmd = parser:command("write_rockspec", [[
+This command writes an initial version of a rockspec file,
+based on a name, a version, and a location (an URL or a local path).
+If only two arguments are given, the first one is considered the name and the
+second one is the location.
+If only one argument is given, it must be the location.
+If no arguments are given, current directory is used as the location.
+LuaRocks will attempt to infer name and version if not given,
+using 'dev' as a fallback default version.
+
+Note that the generated file is a _starting point_ for writing a
+rockspec, and is not guaranteed to be complete or correct. ]], util.see_also())
+ :summary("Write a template for a rockspec file.")
+
+ cmd:argument("name", "Name of the rock.")
+ :args("?")
+ cmd:argument("version", "Rock version.")
+ :args("?")
+ cmd:argument("location", "URL or path to the rock sources.")
+ :args("?")
+
+ write_rockspec.cmd_options(cmd)
+end
+
+local function open_file(name)
+ return io.open(dir.path(fs.current_dir(), name), "r")
+end
+
+local function fetch_url(rockspec)
+ local file, temp_dir, err_code, err_file, err_temp_dir = fetch.fetch_sources(rockspec, false)
+ if err_code == "source.dir" then
+ file, temp_dir = err_file, err_temp_dir
+ elseif not file then
+ util.warning("Could not fetch sources - "..temp_dir)
+ return false
+ end
+ util.printout("File successfully downloaded. Making checksum and checking base dir...")
+ if dir.is_basic_protocol(rockspec.source.protocol) then
+ rockspec.source.md5 = fs.get_md5(file)
+ end
+ local inferred_dir, found_dir = fetch.find_base_dir(file, temp_dir, rockspec.source.url)
+ return true, found_dir or inferred_dir, temp_dir
+end
+
+local lua_version_dep = {
+ ["5.1"] = "lua ~> 5.1",
+ ["5.2"] = "lua ~> 5.2",
+ ["5.3"] = "lua ~> 5.3",
+ ["5.4"] = "lua ~> 5.4",
+ ["5.1,5.2"] = "lua >= 5.1, < 5.3",
+ ["5.2,5.3"] = "lua >= 5.2, < 5.4",
+ ["5.3,5.4"] = "lua >= 5.3, < 5.5",
+ ["5.1,5.2,5.3"] = "lua >= 5.1, < 5.4",
+ ["5.2,5.3,5.4"] = "lua >= 5.2, < 5.5",
+ ["5.1,5.2,5.3,5.4"] = "lua >= 5.1, < 5.5",
+}
+
+local simple_scm_protocols = {
+ git = true,
+ ["git+http"] = true,
+ ["git+https"] = true,
+ ["git+ssh"] = true,
+ hg = true,
+ ["hg+http"] = true,
+ ["hg+https"] = true,
+ ["hg+ssh"] = true,
+}
+
+local detect_url
+do
+ local function detect_url_from_command(program, args, directory)
+ local command = fs.Q(cfg.variables[program:upper()]).. " "..args
+ local pipe = io.popen(fs.command_at(directory, fs.quiet_stderr(command)))
+ if not pipe then return nil end
+ local url = pipe:read("*a"):match("^([^\r\n]+)")
+ pipe:close()
+ if not url then return nil end
+ if url:match("^[^@:/]+@[^@:/]+:.*$") then
+ local u, h, p = url:match("^([^@]+)@([^:]+):(.*)$")
+ url = program.."+ssh://"..u.."@"..h.."/"..p
+ elseif not util.starts_with(url, program.."://") then
+ url = program.."+"..url
+ end
+
+ if simple_scm_protocols[dir.split_url(url)] then
+ return url
+ end
+ end
+
+ local function detect_scm_url(directory)
+ return detect_url_from_command("git", "config --get remote.origin.url", directory) or
+ detect_url_from_command("hg", "paths default", directory)
+ end
+
+ detect_url = function(url_or_dir)
+ if url_or_dir:match("://") then
+ return url_or_dir
+ else
+ return detect_scm_url(url_or_dir) or "*** please add URL for source tarball, zip or repository here ***"
+ end
+ end
+end
+
+local function detect_homepage(url, homepage)
+ if homepage then
+ return homepage
+ end
+ local url_protocol, url_path = dir.split_url(url)
+
+ if simple_scm_protocols[url_protocol] then
+ for _, domain in ipairs({"github.com", "bitbucket.org", "gitlab.com"}) do
+ if util.starts_with(url_path, domain) then
+ return "https://"..url_path:gsub("%.git$", "")
+ end
+ end
+ end
+
+ return "*** please enter a project homepage ***"
+end
+
+local function detect_description()
+ local fd = open_file("README.md") or open_file("README")
+ if not fd then return end
+ local data = fd:read("*a")
+ fd:close()
+ local paragraph = data:match("\n\n([^%[].-)\n\n")
+ if not paragraph then paragraph = data:match("\n\n(.*)") end
+ local summary, detailed
+ if paragraph then
+ detailed = paragraph
+
+ if #paragraph < 80 then
+ summary = paragraph:gsub("\n", "")
+ else
+ summary = paragraph:gsub("\n", " "):match("([^.]*%.) ")
+ end
+ end
+ return summary, detailed
+end
+
+local licenses = {
+ [78656] = "MIT",
+ [49311] = "ISC",
+}
+
+local function detect_license(data)
+ local strip_copyright = (data:gsub("^Copyright [^\n]*\n", ""))
+ local sum = 0
+ for i = 1, #strip_copyright do
+ local num = string.byte(strip_copyright:sub(i,i))
+ if num > 32 and num <= 128 then
+ sum = sum + num
+ end
+ end
+ return licenses[sum]
+end
+
+local function check_license()
+ local fd = open_file("COPYING") or open_file("LICENSE") or open_file("MIT-LICENSE.txt")
+ if not fd then return nil end
+ local data = fd:read("*a")
+ fd:close()
+ local license = detect_license(data)
+ if license then
+ return license, data
+ end
+ return nil, data
+end
+
+local function fill_as_builtin(rockspec, libs)
+ rockspec.build.type = "builtin"
+
+ local incdirs, libdirs
+ if libs then
+ incdirs, libdirs = {}, {}
+ for _, lib in ipairs(libs) do
+ local upper = lib:upper()
+ incdirs[#incdirs+1] = "$("..upper.."_INCDIR)"
+ libdirs[#libdirs+1] = "$("..upper.."_LIBDIR)"
+ end
+ end
+
+ rockspec.build.modules, rockspec.build.install, rockspec.build.copy_directories = builtin.autodetect_modules(libs, incdirs, libdirs)
+end
+
+local function rockspec_cleanup(rockspec)
+ rockspec.source.file = nil
+ rockspec.source.protocol = nil
+ rockspec.source.identifier = nil
+ rockspec.source.dir = nil
+ rockspec.source.dir_set = nil
+ rockspec.source.pathname = nil
+ rockspec.variables = nil
+ rockspec.name = nil
+ rockspec.format_is_at_least = nil
+ rockspec.local_abs_filename = nil
+ rockspec.rocks_provided = nil
+ for _, list in ipairs({"dependencies", "build_dependencies", "test_dependencies"}) do
+ if rockspec[list] and not next(rockspec[list]) then
+ rockspec[list] = nil
+ end
+ end
+ for _, list in ipairs({"dependencies", "build_dependencies", "test_dependencies"}) do
+ if rockspec[list] then
+ for i, entry in ipairs(rockspec[list]) do
+ rockspec[list][i] = tostring(entry)
+ end
+ end
+ end
+end
+
+function write_rockspec.command(args)
+ local name, version = args.name, args.version
+ local location = args.location
+
+ if not name then
+ location = "."
+ elseif not version then
+ location = name
+ name = nil
+ elseif not location then
+ location = version
+ version = nil
+ end
+
+ if args.tag then
+ if not version then
+ version = args.tag:gsub("^v", "")
+ end
+ end
+
+ local protocol, pathname = dir.split_url(location)
+ if protocol == "file" then
+ if pathname == "." then
+ name = name or dir.base_name(fs.current_dir())
+ end
+ elseif dir.is_basic_protocol(protocol) then
+ local filename = dir.base_name(location)
+ local newname, newversion = filename:match("(.*)-([^-]+)")
+ if newname then
+ name = name or newname
+ version = version or newversion:gsub("%.[a-z]+$", ""):gsub("%.tar$", "")
+ end
+ else
+ name = name or dir.base_name(location):gsub("%.[^.]+$", "")
+ end
+
+ if not name then
+ return nil, "Could not infer rock name. "..util.see_help("write_rockspec")
+ end
+ version = version or "dev"
+
+ local filename = args.output or dir.path(fs.current_dir(), name:lower().."-"..version.."-1.rockspec")
+
+ local url = detect_url(location)
+ local homepage = detect_homepage(url, args.homepage)
+
+ local rockspec, err = rockspecs.from_persisted_table(filename, {
+ rockspec_format = args.rockspec_format,
+ package = name,
+ version = version.."-1",
+ source = {
+ url = url,
+ tag = args.tag,
+ },
+ description = {
+ summary = args.summary or "*** please specify description summary ***",
+ detailed = args.detailed or "*** please enter a detailed description ***",
+ homepage = homepage,
+ license = args.license or "*** please specify a license ***",
+ },
+ dependencies = {
+ lua_version_dep[args.lua_versions],
+ },
+ build = {},
+ })
+ assert(not err, err)
+ rockspec.source.protocol = protocol
+
+ if not next(rockspec.dependencies) then
+ util.warning("Please specify supported Lua versions with --lua-versions=<ver>. "..util.see_help("write_rockspec"))
+ end
+
+ local local_dir = location
+
+ if location:match("://") then
+ rockspec.source.file = dir.base_name(location)
+ if not dir.is_basic_protocol(rockspec.source.protocol) then
+ if version ~= "dev" then
+ rockspec.source.tag = args.tag or "v" .. version
+ end
+ end
+ rockspec.source.dir = nil
+ local ok, base_dir, temp_dir = fetch_url(rockspec)
+ if ok then
+ if base_dir ~= dir.base_name(location) then
+ rockspec.source.dir = base_dir
+ end
+ end
+ if base_dir then
+ local_dir = dir.path(temp_dir, base_dir)
+ else
+ local_dir = nil
+ end
+ end
+
+ if not local_dir then
+ local_dir = "."
+ end
+
+ local libs = nil
+ if args.lib then
+ libs = {}
+ rockspec.external_dependencies = {}
+ for lib in args.lib:gmatch("([^,]+)") do
+ table.insert(libs, lib)
+ rockspec.external_dependencies[lib:upper()] = {
+ library = lib
+ }
+ end
+ end
+
+ local ok, err = fs.change_dir(local_dir)
+ if not ok then return nil, "Failed reaching files from project - error entering directory "..local_dir end
+
+ if not (args.summary and args.detailed) then
+ local summary, detailed = detect_description()
+ rockspec.description.summary = args.summary or summary
+ rockspec.description.detailed = args.detailed or detailed
+ end
+
+ if not args.license then
+ local license, fulltext = check_license()
+ if license then
+ rockspec.description.license = license
+ elseif license then
+ util.title("Could not auto-detect type for project license:")
+ util.printout(fulltext)
+ util.printout()
+ util.title("Please fill in the source.license field manually or use --license.")
+ end
+ end
+
+ fill_as_builtin(rockspec, libs)
+
+ rockspec_cleanup(rockspec)
+
+ persist.save_from_table(filename, rockspec, type_rockspec.order)
+
+ util.printout()
+ util.printout("Wrote template at "..filename.." -- you should now edit and finish it.")
+ util.printout()
+
+ return true
+end
+
+return write_rockspec